vue3笔记(40-1)新模版框架使用

近期换了个模版框架,这里记录下使用过程中的一些新知识。

unocss

UnoCSS 是即时原子 CSS 引擎,所有 CSS 工具都是通过预设提供的.
vite.config.ts中配置unocss插件:

1
2
3
4
5
6
7
import UnoCSS from 'unocss/vite'

export default ({ command, mode }: ConfigEnv): UserConfig => {
return {
plugins: [UnoCSS()]
}
}

导入全局样式

1
import 'virtual:uno.css'

创建uno.config.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import {
defineConfig,
toEscapedSelector as e,
presetUno,
presetIcons,
presetAttributify,
presetTypography
} from 'unocss'

export default defineConfig({
// 使用预设
presets: [
presetUno({ dark: 'class', attributify: false }),
...createPresetIcons(),
presetAttributify(), // 属性化预设
presetTypography() // 版式预设
],
// 自定义规则
rules: [
['m-1', { margin: '1px' }],
[/^m-([\.\d]+)$/, ([_, num]) => ({ margin: `${num}px` })],
[
/^text-color-primary$/,
([], { rawSelector }) => {
const selector = e(rawSelector)
return `
${selector} {
color: rgb(22, 93, 255);
}`
}
],
],
})

UnoCSS 默认字体大小为 4px,如果需要自定义,特别是针对移动端做一个适配,可以通过设计稿宽度计算出基准字体大小,然后通过postprocess函数对所有样式进行处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
postprocess: [
(util) => {
const remRE = /(-?[\.\d]+)rem/g;
const designWidth = 422; // 设计稿宽度
const defaultWidth = 384; // 默认浏览器窗口宽度
const rootFontSize = 42.2; // 基准字体大小
const unoDefaultFontSize = 4; // UnoCSS 默认字体大小
const scale = defaultWidth / designWidth;

util.entries.forEach((entry) => {
const value = entry[1];
if (value && typeof value === 'string' && remRE.test(value))
entry[1] = value.replace(remRE, (_, p1) => {
const computeRem = (rootFontSize / unoDefaultFontSize) * scale;
return `${(p1 / computeRem) * scale}rem`;
});
});
},
]

但是这样做有一定风险,如果用的是PC端框架,很多组件已经有自己的样式了,突然改变 root font-size 会导致样式失效。所以,还是建议使用 UnoCSS 预设的默认字体大小。

数据持久化

发现项目里有个插件 pinia-plugin-persistedstate, 在 Pinia 中使用这个插件进行状态持久化。在store/index.ts中具体使用方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const store = createPinia()

store.use(piniaPluginPersistedstate)

export const setupStore = (app: App<Element>) => {
app.use(store)
}

export { store }

store/user.ts中定义状态, 并设置persisttrue

1
2
3
4
5
6
7
8
9
10
11
export const useUserStore = defineStore('user', {
state: (): UserState => {
return {
account: '',
token: ''
}
},
getters: {},
actions: {},
persist: true
})

对于更细粒度的控制,可以向 persist 属性传递配置对象,列举两个常用的配置:

  1. storage: 指定用于保存数据的机制,默认为localStorage。
  2. paths: 数组形式,指定要持久化的状态字段路径。

但是在项目实际需求中,需要实现 token 24小时过期,虽然可以通过后端请求实现,但是我还是希望前端能在 24h 之后自动清除 token,查阅资料,这样的写法似乎没有可以直接配置时间的地方。如果需要传递参数时自动携带 cookie,还是需要通过 cookie 做一个中转,即在设置 cookie 时,带入时间,或者附录中还有一篇文章参考。

其他

  1. 新建空白 vue 页面

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <script setup lang="tsx">
    import { useI18n } from '@/hooks/web/useI18n'
    const { t } = useI18n()
    </script>

    <template>
    <div class="flex h-full"></div>
    </template>

    <style lang="scss" scoped></style>
  2. Table

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <Table
    v-model:pageSize="pageSize"
    v-model:currentPage="currentPage"
    :columns="columns"
    :data="dataList"
    :loading="loading"
    @register="tableRegister"
    @selection-change="handleSelectionChange"
    :reserveSelection="true"
    :row-key="getRowKey"
    :pagination="{
    total,
    layout: 'total, prev, pager, next'
    }"
    />
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    import { useTable } from '@/hooks/web/useTable'
    import { TableColumn } from '@/components/Table'

    const loading = ref(false)
    const batchArr = ref([])
    const { dataList, loading, total, currentPage, pageSize } = tableState
    const { getList } = tableMethods
    const { tableRegister, tableState, tableMethods } = useTable({
    fetchDataApi: async () => {
    const { currentPage, pageSize } = tableState
    const res = await getList(
    {
    page_number: unref(currentPage),
    page_limit: unref(pageSize),
    ...unref(searchParams)
    }
    )
    return {
    list: res?.list || [],
    total: res?.count
    }
    },
    request: {
    pageSize: 'page_limit',
    pageIndex: 'page_number'
    }
    })

    const columns = reactive<TableColumn[]>([
    {
    field: 'selection',
    type: 'selection'
    },
    {
    field: 'ame',
    label: t('common.name'),
    slots: {
    default: ({ row }) => {
    return (
    <div class="flex items-center">
    <span
    onClick={() => handleShowName(row)}
    >
    {row.name}
    </span>
    </div>
    )
    }
    }
    }
    ])

    // 批量选择
    const handleSelectionChange = (val) => {
    const batchArr = ref([])
    batchArr.value = val
    }

    const getRowKey = (row: any) => {
    return row.task_id
    }
  3. Search

    1
    <SearchBar :schema="searchSchema" @search="setSearchParams" @reset="setSearchParams" />
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    const searchSchema = reactive<FormSchema[]>([
    {
    field: 'name',
    label: t('common.name'),
    component: 'Input',
    componentProps: {
    placeholder: t('common.namePlaceHolder')
    }
    }
    ])
    // 查询参数初始值设置
    const searchParams = ref({
    name: ''
    })
    // 更新查询参数
    const setSearchParams = (params: any) => {
    if (JSON.stringify(params) == '{}') {
    searchParams.value = {
    name: ''
    }
    } else {
    searchParams.value = Object.assign({ sort_order: 'asc' }, searchParams.value, params)
    }
    tableState.currentPage.value = 1
    getList()
    }

附录
框架参考
UnoCSS中文网
unocss实现多端的rem适配
pinia-plugin-persistedstate官方文档
创建一个自定义过期时间的持久化Pinia