GoFrame学习随便记5 (quasar)(待续)
参考 B 站 --小霖同学-- 的教程入门一下quasar,然后我们给示例项目搞个页面。以下是前后端连接大概流程记录。
nvm alias default v24.12.0,nvm use default 把默认node版本切换到这个lts版本,因为版本太老可能不行。
在合适的位置 npm init quasar 来创建 quasar项目,需要回答项目根目录名称、和vue3有关的配置等等。我们基本用默认选项,只是组件选择把状态管理 pinia、请求组件axios、i18n 和代码 linting 都选上,我们用 JavaScript (不用 TypeScript)。建完项目,根据提示安装完依赖,用 VSCode 添加项目根目录到工作区,准备工作差不多完成了。(默认用的是 vite 而非 webpack,务必注意,两个打包工具对应需要的在配置文件中的格式不一样!可以从quasar官网文档查看各自不同)
cd PRJNAME, npx quasar dev 启动本地web服务器就可以看到默认页面了。
我们先观察一下项目目录结构:src/router/routes.js 是项目包含的路由,只有 / 和 404。/ 对应的组件是布局模板 layouts/MainLayout.vue,子组件包含页面pages/IndexPage.vue,子组件对应 path 为空,也就是布局模板里面的<router-view />默认渲染这个子组件(其实就是首页index)。这里的 layouts 和 pages 划分和通常的想法一样,也就是 pages的东西是嵌入layouts的一部分(主体部分)。
参考 https://blog.csdn.net/weixin_43815091/article/details/124951953 修改 api 的基础URL:quasar.config.js 修改 (axios基础url用相对路径,如果用协议号http开始的绝对路径,开发服务器就不会捕获并执行proxy代理。另外,我们路径额外加一个/api前缀并在proxy重写规则中去掉,如果用根目录/,配置会复杂一些。使用相对路径后,代理proxy会过滤请求,对于匹配上的请求,进行代理转发,即请求不再是浏览器到后端服务器,变成前端服务器到后端服务器,这样就避开了跨域问题)
build: {
.....................
env: {
apiBaseUrl: ctx.dev ? '/api' : 'http://localhost:8000',
},
.....................
devServer: {
// https: true,
open: true, // opens browser window automatically
proxy: {
'/api': {
target: 'http://localhost:8000',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
修改 boot/axios.js (让 api 成为一个axios实例)
const api = axios.create({
baseURL: process.env.apiBaseUrl,
timeout: 30000,
})
参考 https://help.crudapi.cn/guide.html (源码网址 https://gitee.com/crudapi/crudapi-admin-web/blob/master/src/api/user.js),我们新建 src/api 目录,差不多就是把 GoFrame 的 *.http 文件中的各API请求包装一下方便使用。文件 src/api/post.js 如下:其中的Promise使用了 async/await 语法糖而非 then/catch
import { api } from 'boot/axios'
// axios 参考 https://axios-http.com/zh/docs/handling_errors
const post = {
index: async function (params) {
try {
const response = await api.get('/post/index', {
params: params,
})
console.log(response.data)
return response.data
} catch (error) {
if (error.response) {
// 请求成功发出且服务器也响应了状态码,但状态代码超出了 2xx 的范围
console.log(error.response.data)
console.log(error.response.status)
console.log(error.response.headers)
} else if (error.request) {
// 请求已经成功发起,但没有收到响应
// `error.request` 在浏览器中是 XMLHttpRequest 的实例,
// 而在node.js中是 http.ClientRequest 的实例
console.log(error.request)
} else {
// 发送请求时出了点问题
console.log('Error', error.message)
}
console.log(error.config)
}
},
create: async function (params) {
try {
const response = await api.post('/post/create', params)
console.log(response.data)
return response.data
} catch (error) {
console.log(error)
}
},
update: async function (id, params) {
try {
const response = await api.post('/post/update/' + id, params)
console.log(response.data)
return response.data
} catch (error) {
console.log(error)
}
},
view: async function (id) {
try {
const response = await api.get('/post/view/' + id)
console.log(response.data)
return response.data
} catch (error) {
console.log(error)
}
},
delete: async function (id) {
try {
const response = await api.post('/post/delete/' + id)
console.log(response.data)
return response.data
} catch (error) {
console.log(error)
}
},
}
export { post }
创建页面 src/pages/PostPage.vue (复习一下 <script setup>语法糖,ref响应对象创建,生命周期钩子 onMounted,异步函数使用,quasar表格基本使用)
<template>
<q-page class="flex flex-center">
<div class="q-pa-md">
<q-table title="博客列表" :columns="columns" :rows="rows" row-key="id" />
</div>
</q-page>
</template>
<script setup>
import { post } from 'src/api/post'
import { onMounted, ref } from 'vue'
const columns = [
{
name: 'id',
label: 'ID',
field: 'id',
},
{
name: 'title',
label: '标题',
field: 'title',
},
{
name: 'content',
label: '标题',
field: 'content',
format: (val) => {
return val.substr(0, 20)
},
},
{
name: 'tags',
label: '标签',
field: 'tags',
},
{
name: 'status',
label: '状态',
field: 'status',
},
{
name: 'createdAt',
label: '创建时间',
field: 'createdAt',
sortable: true,
},
{
name: 'updatedAt',
label: '更新时间',
field: 'updatedAt',
},
{
name: 'authorId',
label: '作者',
field: 'authorId',
},
]
const rows = ref([])
onMounted(async () => {
let res = await post.index()
rows.value = res.data.list
})
</script>
路由 src/router/routes.js 添加
const routes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/IndexPage.vue') },
{ path: '/post', component: () => import('pages/PostPage.vue') },
{ path: '/comment', component: () => import('pages/CommentPage.vue') },
],
},
.........................
布局模板 src/layouts/MainLayout.vue中的链接改掉
const linksList = [
{
title: '博客',
caption: '我的各种博客',
icon: 'school',
link: '#/post',
},
{
title: '评论',
caption: '用户浏览器后评论',
icon: 'chat',
link: '#/comment',
},
...................
然后 npx quasar dev 启动本地服务器,浏览器自动打开(quasar.config.js中devServer部分莫默认设置是自动打开浏览器),点击“博客”菜单导航到 #/post,主视图中就用表格显示了博客。不过表格右下角关于页的提示信息默认是英文的,因为quasar框架的组件默认用英文,我么改一下配置文件 quasar.config.js (如果要编写自己的支持i18n的组件,可以参考https://quasar.dev/options/app-internationalization)
framework: {
config: {},
lang: 'zh-CN',
作为GUI库,我们大部分时候关心的是样式风格、布局、可视组件,分别对应官网文档 (Documentation | Quasar Framework)Style and Identity、 Layout and Grid、Vue Components。我们大概了解一下有哪些可视组件(对应文档中 Vue Components):
- Ajax Bar: 异步网络后台请求时,我们可以用这个让页面边缘显示进度(默认是页面底部)
- Avatar:常见用于头像,也可用作展示性小图标,可以控制尺寸、颜色、方圆
- Badge:徽章,主要是突出显示吸引注意的少量文字或图标,比如待阅读的数量,它是附属品,常和正文不在一个高度
- Banner:旗帜条(常为上部长条),用于突出显示信息或者可选操作,如“登录成功”
- Bar:贴顶条,用来容纳菜单,最大化、最小化、关闭等按钮
- Breadcrumbs:面包屑,可以让人知道当前导航位置
- Button、Button Group、Button Dropdown:按钮是最常用的可视化组件
- Card:卡片,这是常见的信息展示容器
- Carousel:转盘,可以在相对有限的空间用幻灯方式展示更多信息
- Chat Message:聊天消息
- Chip:一个界面块,用来容纳头像、文本、图标等东西,和徽章很像,但它是正文
- Circular Progress:彩色的圆形进度指示器
- Color Picker:颜色选择器
- Dialog:对话框。它通常需要两个部分,对话框部分绑定一个模型变量,触发者让这个变量为true 则显示对话框。
- Editor:编辑框,实现富文本编辑,WYSIWYG
- Expansion Item:可展开的列表项,初始折叠,点击对应项目后可展示隐藏的详情内容
- Floating Action Button:悬浮的动作按钮,在悬浮在主体内容的上面,常常显示一个小圆圈,点击可以弹出更多操作按钮
- 表单组件: Input Textfield(文本框)、Select(下拉菜单)、File picker(文件选择)、Form(表单)、Field(装饰器、容器)、Radio(单选框)、Checkbox(复选框)、Toggle(开关指示)、Button Toggle(选中指示,类似Radio)、Option Group(用于分组radio/checkbox/toggle 的容器)、Slider(滑动条表示数量)、Ranger(滑动条指示范围)、Time Picker(时间选择)、Date Picker(日期选择)
- Icon:方便在其他组件中嵌入图标
- Img:方便嵌入图片,有加载进度指示
- Infinite Scroll:无穷滚动让用户滚动时加载新的内容
- Inner Loading:长加载耗时的时候显示动画,让用户可以看到应用在后台进行加载工作
- Intersection:和 Intersection quasar指令相关,主要是DOM对象进入和移出视口过程中,能节约内存
- Knob:圆形的数量展示,可以用鼠标或触控改变它的值,例如用于音量控制
- Linear Progress:彩色的加载条,用来提醒用户某个动作在背后发生
- List and List Items:用来进行列表展示,列表项可以是很复杂的
- Markup Table:是对table的包装,呈现为Material风格,只用于少量简单的表格数据展示,需要复杂分页、排序、过滤功能的表格,应该用 QTable组件
- Menu:菜单,通常是按钮弹出式
- Resize Observer、Scroll Observer:尺寸改变、滚动时激发相应消息
- Pagination:分页组件,外观上就是页码列表和上下翻页箭头
- Parallax:视差滚动,滚动时背景图片滚动略慢造成一定视觉效果
- Popup Edit:弹出式在地编辑,即点击要编辑目标,编辑器就在那个位置打开。可以和Table表格组件配合使用。
- Popup Proxy:可以将弹出内容(如QMenu、QTooltip、QDialog、自定义浮层、上下文菜单等)与目标组件(按钮、输入框等)关联显示
- Pull to refresh:下拉刷新
- Rating:评级
- Responsive:强制让内容保持纵横比
- Scroll Area:可滚动区域,可以有自己的滚动条而不是浏览器提供的
- Separator:分隔条,可以是横线,也可以是竖线
- Skeleton:动态效果占位符,用于加载完之前显示
- Slide Item:是列表项 QItem的增强版,可以向左、向右、向下、向上拖拉以便触发特定动作
- Slide Transition:作用于单个DOM的滑动过渡效果
- Space:填充弹性盒子所有可用的空间,quasar组件都是弹性盒子
- Spinners:动态视觉特性的图标,让用户感觉系统持续在工作
- Splitter:把容器分隔成可拖拉改变大小的部分
- Stepper:用于展示步骤,让人知道当前做了哪几步,还剩几步
- Table:表格
- Tabs:选项页,实现在同一个位置多个内容的切换显示
- Tab Panels:选项面板,和Tabs有点像,但它针对面板而非整页,主要是为了在有限空间显示更多信息
- Timeline:时间线,stepper是横向展示步骤,这个是纵向按时间展示事件信息
- Toolbar:工具条,最常用于页头和页脚,和 bar 相比,它偏于布局。
- Tooltip:工具提示,鼠标移上去展示提示信息
- Tree:树形结构,用来展示层次数据
- Uploader:对于简单上传一个不大的文件,QFile更简单,QUploader主要是复杂的控制,例如多个文件,添加头部
- Video:方便嵌入视频
- Virtual Scroll:只显示长列表中部分数据,当用户在容器内滚动时更新可视数据,这样所需内存最小且渲染代价小
大概了解一下布局。quasar布局是基于弹性盒子(Flexbox)的,quasar提供了一些基于弹性盒子的CSS类,它们应用到父容器或者内容子项目完成布局。对于父容器,需要设定主轴方向(row [reverse]/row inline/column [reverse]/column inline)、包裹策略(溢出是否自动换行换列 wrap/no-wrap/reverse-wrap)、对齐方式(justify-xxx 内部子项间分布、items-xxx 沿主轴对齐、content-xxx整体内容在父容器的分布)。对于子项目,需要设定分布尺寸(quasar基于12列/行设计,col-n 表示占用n列,col-auto 尽量少占,col 有空间就多占,col-grow 至少占用所需空间,还有空间就增长,col-shrink 至多占用所需空间,空间不足就收缩)、自身对齐(self-xxx 可以让单个子项覆盖父容器的 items-xxx 对齐)、排序号(order-first/order-last/style="order: 2" 可以让布局排序不按源码中的顺序来)。quasar是基于响应式设计的,所以CSS类中有屏幕宽度大小(断点)设定:xs 超小屏是 [0px, 600px),sm 小屏是 [600px, 1024px),md 中屏是 [1024px, 1440px),lg 大屏是 [1440px, 1920px),xl 超大屏是 [1920px,+\infinity) 。屏幕断点也用于内外边距留空,例如 q-pa-xs、q-py-md-md 。quasar布局的核心概念是 行 row、列 col、间距 gutter。gutter的两种类型:q-gutter-{size}(需要间隔的元素不使用 col-*、offset-*)、q-col-gutter-{size}(需要间隔的元素使用了 col-* 、offset-*),其中尺寸后缀 -{size} 可以取 -none/-xs/-sm/-md/-lg/-xl,它表示屏幕尺寸,只是表示间隔相对大小。q-gutter-{size}的逻辑是:额外用一个<div class="q-gutter-{size}"> 容器包裹需要间隔的子项,而 q-gutter-{size} 会对父容器应用负值的上边距和左边距,对每个子项应用正值(-none对应0)的上边距和左边距。q-col-gutter-{size}的逻辑类似。之所以需要额外的容器是因为会对父容器产生负值的top/left外边距,没有这个额外容器可能产生预期之外的副作用。总体布局的组件包括 Header、Footer、Drawer、Page (QPage必须被QPageContainer包裹,一般QPageContainer在布局文件,而 QPage在独立页面文件) 等。
大概了解一下样式。标题样式有 text-h1、text-h2、……、text-h6,副标题有 text-subtitle1、text-subtitle2。字体粗细有 text-weight-thin/light/regular/medium/bold/bolder。对齐辅助CSS类有 text-right/left/center/justify。字体风格辅助 text-bold/italic/no-wrap/strike/uppercase/lowercase/capitalize。品牌主色 primary蓝、secondary蓝绿、accent紫、dark黑、positive绿、negative红、info青、warning黄。颜色列表:red红、pink粉红、purple紫、deep-purple深紫、indigo靛蓝、blue蓝、light-blue浅蓝、cyan青、teal蓝绿、green绿、light-green浅绿、lime黄绿、yellow黄、amber琥珀色、orange橙、deep-orange橘红、brown棕、grey灰、blue-grey蓝灰。这些颜色用于字体(text-*)和背景(bg-*),颜色也可以使用定义了的变量,如 $primary、$success。阴影 no-shadow/shadow-{n}/shadow-transition/inset-shadow/inset-shadow-down。可见性 disabled/hidden/invisible/transparent/dimmed/ellipsis/z-top/z-max/print-only/print-hide。定位。变量。
quasar组件、布局、样式可以慢慢学,路由细节也可以慢慢学,还有两个问题:1、如何用pinia实现状态管理,哪些东西需要进行状态管理;2、登录获取令牌、在请求中携带令牌和令牌超时前刷新问题。
关于 pinia,主要概念是:1、stores/index.js 中 defineStore 中用 createPinia() 创建了pinia 实例。2、对于某个对象,例如 channel 对象,就是类似如下,store内是三个部分 state(管理的状态变量)、getters(只读函数)、actions(异步请求)
export const useChannelStore = defineStore('channel-store', {
state: () => ({
/** @type {{id:integer,networkVideoRecorderId:integer,enabled:boolean,order:integer,channelNo:integer,
* channelName:string,filePrefix:string, createdAt:datetime, updatedAt:datetime }[]} */
channels: [],
/** @type {{
* enabled: 'all' | 'enabled' | 'disabled',
* nvrIds: integer[] | null
* }} */
filter: {
enabled: 'all',
nvrIds: null, // null = all
},
}),
getters: {
filteredChannels() {
return this.channels.filter((channel) => {
.........................
})
},
},
actions: {
async fetchChannels() {
try {
let res = await channel.index()
if (res.code === 0) {
this.channels = res.data.list
} else {
console.error('error fetchChannels in channel.js', res.message)
}
} catch (error) {
console.log(error)
}
},
async updateChannel(id, params) {
try {
let res = await channel.update(id, params)
if (res.code === 0) {
if (res.data.rows_affected === 1) {
return 1
} else {
console.error('0 rows updated in updateChannel')
return 0
}
} else {
console.error('error updating channel with id', id, ', error:', res.message)
}
} catch (e) {
console.error(e)
}
return -1
},
},
})
3、使用 store:const storeChannel = useChannelStore()
本质上,状态管理就是既可以实现对变量值的缓存,又可以让变量值的变动从同一个入口进入,方便观察和诊断。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐



所有评论(0)