项目简介

本项目使用vite + pinia + ts + vue3, 使用的node版本为20.10.0, 推荐使用pnpm安装依赖

本项目源码地址为: https://gitee.com/taozhiqiang/vite_ts_pinia.git 初次搭建, 其中有问题欢迎大家交流与指正, 有好的想法或者不足之处可留言, 看到会回复

部分样式代码及图片借鉴于: https://gitee.com/imsea/Geeker-Admin.git 侵删

API

使用axios库, 用类的方式二次封装, 使用了双token实现页面无感刷新, 以及接口参数请求加解密, 加解密使用随机加密秘钥, 每次请求的加解密秘钥是不相同的, 密钥使用非对称加密, 加密之后通过appId传递给后端, 同时生成一个唯一值(nonce)和时间戳(timestamp), 最后把appId, nonce, timestamp以及data数据按顺序拼接, 最后通过md5生成签名

api文件夹下的index.ts中做了接口统一导出, 使用的时候只需要引用index.ts文件就可直接使用请求

sass

样式使用sass, 其中添加了清除浮动, 多行文本隐藏, flex布局, 以及盒子边框流光效果的样式混入

BaseEditor

富文本编辑器使用wangEditor二次封装, 在其中添加了一个添加填空的插件, 接收两个参数excludeKeys(菜单栏需要忽略的配置)以及editorConfig(编辑器的配置), 该组件尚未完成后续会继续完善

1
2
3
4
5
6
7
8
9
<script lang="ts" setup>
import BaseEditor from '@/components/BaseEditor/index.vue'
import { ref } from 'vue'
const editValue = ref('')
const excludeKeys = ref(['|'])
const editorConfig = ref({
placeholder: '请输入内容...'
})
</script>
1
2
3
4
5
6
7
<template>
<BaseEditor
v-model="editValue"
:excludeKeys="excludeKeys"
:editorConfig="editorConfig"
/>
</template>

editorConfig

类型为IEditorConfig, 具体参数请查看官网文档 https://www.wangeditor.com/v5/editor-config.html#placeholder

excludeKeys

类型为字符串数组, 可选参数如下

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
[
'headerSelect',
'blockquote',
'bold',
'underline',
'italic',
'group-other-style',
'color',
'bgColor',
'fontSize',
'fontFamily',
'lineHeight',
'bulletedList',
'numberedList',
'todo',
'group-justify-style',
'group-indent-style',
'emotion',
'group-link-style',
'group-img-style',
'group-video-style',
'group-table-style',
'codeBlock',
'divider',
'undo',
'redo',
'fullScreen',
'gapFilling'
]

BaseIcon

使用@iconify/vue二次封装, 该组件接收三个参数icon(图标名称), color(颜色色值), size(大小), 支持element-plus图标以及网络图标, 网络图标可以参考以下两个链接
https://icones.js.org/collection/all
https://icon-sets.iconify.design

1
2
3
4
5
6
7
8
9
10
type TIcon = {		
icon: string,
color?: string,
size?: number
}
<script lang="ts" setup>
import { ref } from 'vue'
const color = ref('#f00')
const icon = ref('Plus')
</script>
1
2
3
4
5
6
7
<template>
<BaseIcon
:color="color"
:icon="icon"
:size="16"
/>
</template>

BaseForm

表单组件, 只需传入配置即可显示, 添加规则校验, 字典默认值自动回显, 导出三个方法: 校验输入是否完整以及是否正确输入(validate), 重置表单项(resetFields), 验证表单中的具体某一项(validateField)

使用示例

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
<script lang="ts" setup>
import {IForm} from '@/interface/form'
import {ref} from 'vue'

const form = ref<IForm>({
data: {
text: '',
number: '',
number1: 0,
number2: [],
select: '',
checkbox: [],
textarea: '',
slotName: '',
slotIcon: '',
slotInput: '',
radio: '',
editor: ''
},
otherConfig: {
labelWidth: '80px'
},
config: [
{
col: 12, // 占一行的多少宽度24: 100%, 12: 50%, 7: 33.3%, 6: 25%, 4: 20%
component: 'ElInput', // 类型
name: '纯文本', // 显示的名称
bind: 'text', // 绑定的值和data中对应,data中必须有该字段
trigger: 'blur', // 校验时机
isHide: false, // 是否隐藏, 优先级大于showHide, 设置为true之后页面上不显示但是提交的时候会有这个字段,可以用来设置隐藏的值
required: '请输入文本', // 是否必填和必填的提示
rules: [], // 其他的校验规则
attr: {
type: 'text',
placeholder: '请输入文本'
},
methods: {}
},
{
col: 12,
component: 'el-input',
name: '数字类型1',
bind: 'number',
trigger: 'blur',
required: '请输入数字',
rules: [],
attr: {
type: 'number',
placeholder: '请输入数字'
}
},
{
col: 12,
component: 'el-input-number',
name: '数字类型2',
bind: 'number1',
trigger: 'blur',
required: '请输入数字',
rules: [],
attr: {
placeholder: '请输入数字'
}
},
{
col: 12,
component: 'NumberRange',
name: '取值范围',
bind: 'number2',
trigger: 'blur',
required: '请输入数字',
rules: [],
attr: {
minRange: {
placeholder: '请输入' // 其余配置与el-input-number相同
},
maxRange: {
placeholder: '请输入' // 其余配置与el-input-number相同
}
}
},
{
col: 12,
component: 'el-select',
name: '下拉选择',
bind: 'select',
trigger: 'blur',
required: '请选择',
rules: [],
dictList: [
{
value: 'Option1',
label: 'Option1'
},
{
value: 'Option2',
label: 'Option2'
}
],
attr: {
type: 'number',
placeholder: '请选择'
}
},
{
col: 12,
component: 'ElCheckboxGroup',
name: '多选框组',
bind: 'checkbox',
trigger: 'blur',
required: '请选择多选框组',
rules: [],
dictList: [
{
value: 'Option1',
label: 'Option1'
},
{
value: 'Option2',
label: 'Option2'
}
],
attr: {}
},
{
col: 24,
component: 'ElInput',
name: '文本域',
bind: 'textarea',
trigger: 'blur',
required: '请输入文本域',
rules: [],
attr: {
type: 'textarea',
placeholder: '请输入文本域'
}
},
{
col: 12,
component: 'customSlot',
name: '插槽--按钮',
bind: 'slotBtn',
trigger: 'blur',
rules: []
},
{
col: 12,
component: 'customSlot',
name: '插槽--图标',
bind: 'slotIcon',
trigger: 'blur',
rules: []
},
{
col: 12,
component: 'customSlot',
name: '插槽--输入',
bind: 'slotInput',
trigger: 'blur',
required: '请输入插槽输入框',
rules: [{min: 3, max: 5, message: '长度在3-5之间', trigger: 'blur'}]
},
{
col: 12,
component: 'ElRadioGroup',
name: '单选框组',
bind: 'radio',
trigger: 'blur',
required: '请选择单选框组',
rules: [],
dictList: [
{
value: 'Option1',
label: 'Option1'
},
{
value: 'Option2',
label: 'Option2'
}
],
attr: {}
},
{
col: 24,
component: 'Editor',
name: '富文本',
bind: 'editor',
trigger: 'blur',
required: '请输入富文本'
}
]
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<BaseForm ref="baseFormRef" v-model="form.data" :form-config="form">
<template #slotBtn>
<el-button type="primary">插槽按钮</el-button>
</template>
<template #slotIcon>
<BaseIcon :size="20" color="#f00" icon="material-symbols:ac-unit"/>
</template>
<template #slotInput>
<el-input v-model="form.data.slotInput"/>
</template>
</BaseForm>
</template>

配置解析

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
import {FormProps} from 'element-plus'
import {FormItemProps} from 'element-plus/es/components/form/src/form-item'
import {ComputedRef} from 'vue'

interface IForm {
data: any // 绑定的数据项, 对象中的键需要和config中的bind字段一致
config: IFormConfig[] // 表单展示配置项
otherConfig?: { // 表单其他配置项
formConfig?: Partial<FormProps> // element文档el-form的配置
formItemConfig?: Partial<FormItemProps> // element文档el-form-item的配置
labelWidth?: string // 控制label的宽度单位为px 传入例如: '120px'
isRestSearch?: boolean // 搜索是否点击重置之后立即触发搜索,
showSearchBtn?: boolean // 是否需要重置查询按钮,true不显示
}
}

interface IFormConfig {
component: any // element组件的类型可使用'el-input'也可以使用'ELInput'或者直接使用组件的ELInput, 或者其他自定义组件, customSlot为插槽, 名字就是bind的值
name: string // 显示的名称
bind: string // 绑定的值和data中对应,data中必须有该字段
col?: 24 | 12 | 7 | 6 | 4 // 占一行的多少宽度24: 100%, 12: 50%, 7: 33.3%, 6: 25%, 4: 20%
trigger?: 'blur' | 'change' // 校验时机
isHide?: boolean // 是否隐藏, 优先级大于showHide, 设置为true之后页面上不显示但是提交的时候会有这个字段,可以用来设置隐藏的值
showHide?: (item: any) => boolean // 是否隐藏优先级低于isHide, 可以自己写规则
dictKey?: string // 匹配字典数据默认值
required?: string // 是否必填和必填的提示
rules?: any[] // 其他的校验规则
needToolTip?: {
content: string // 内容
placement?: string // 提示方向
size?: number // 图标大小
icon?: string // 图标
color?: string // 图标颜色
}
dictList?: ComputedRef<any[]> | Array // 字典数据
dictProps?: { // 字典显示配置值
label: string // 字典标签名
value: string // 字典值名称
}
attr?: object // 使用的element-plus组件中的值, 例如{placeholder: '请输入'}
methods?: object // 使用的element-plus组件中的方法, 例如{'change': () => {}}
}

BaseSearch

搜索组件, 基本配置与表单配置相同, 没有改变, 内部提供两个事件searchFn(搜索事件)和resetSearchFn(重置事件), 可通过设置isRestSearch是否在点击重置之后立即开始搜索,
可通过设置showSearchBtn是否展示搜索和重置按钮

BaseTable

表格组件, 支持el-table的所有属性, 额外添加外置按钮和内置按钮的处理, 支持展示html | dict | icon | img | tag | textColor | click类型的数据

使用示例

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
<script lang="ts" setup>
import { reactive } from 'vue'
import {IMyTableProps} from '@/interface/table'

const dictList = reactive([
{
dictLabel: '等级1',
dictValue: '1'
},
{
dictLabel: '等级2',
dictValue: '2'
},
{
dictLabel: '等级3',
dictValue: '3'
}
]) // 字典数据
const handleCurrentChange = (val: number) => {
console.log(val, '第几页')
}
const add = (item: any) => { // 新增按钮点击
console.log('新增', item)
}
const table = reactive<IMyTableProps>({
tableHeader: [
{
label: 'ID',
prop: 'id',
hidden: false,
childrenList: [
{
label: '插槽',
prop: 'btnSlot',
slotName: 'btnSlot',
width: 1000,
hidden: true
},
{
label: 'html',
prop: 'text',
textType: 'html'
},
{
label: 'dict',
prop: 'leave',
textType: 'dict',
dictList: dictList,
dictProps: {
label: 'dictLabel',
value: 'dictValue'
}
},
{
label: '第四层',
prop: 'four',
childrenList: [
{
label: 'icon',
prop: 'icon',
textType: 'icon',
hidden: true
},
{
label: 'img',
textType: 'img',
prop: 'imgList' // 数据是一个string数组
}
]
}
]
},
{
label: '嗨嗨',
slotName: 'btnSlot',
prop: 'hi',
width: 720,
childrenList: [
{
label: 'tag',
prop: 'tag',
textType: 'tag',
tagRound: true,
tagEffect: 'dark',
dictList,
dictProps: {
label: 'dictLabel',
value: 'dictValue'
}
},
{
label: 'textColor',
textType: 'textColor',
prop: 'textColor',
color: '#f00',
width: '100'
},
{
label: 'click',
textType: 'click',
prop: 'click',
handleClick(row: any, item: any, index: number) {
console.log(row, item, index)
}
},
{
label: '测试',
prop: 'test'
}
]
}
],
tableConfig: {
data: [
{
id: 1000,
one: '1',
text: '<h3>我是html</h3>',
textColor: '我是文字颜色',
click: '我是点击',
icon: 'material-symbols:15mp-sharp',
//imgList: 'https://ooo.0x0.ooo/2023/07/26/OiqOIG.jpg',
imgList: ['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg', 'https://ooo.0x0.ooo/2023/07/26/OiqOIG.jpg'],
leave: '2',
tag: '2',
test: 'test'
}
],
width: 120,
height: 689,
onMethods: {
select: (val: any) => {
console.log(val, '选中')
},
selectAll: (val: any) => {
console.log(val, '全选, 表格的事件都可以直接写,没有提示')
},
cellDblclick: (val: any) => {
console.log(val, '单元行双击')
}
}
},
innerBtn: [
{
name: '编辑',
method: (row, item) => console.log('编辑', row, item),
icon: 'Edit'
// code: "menu/add", // 按钮权限
}
],
outerBtn: [
{
name: '新增',
method: add,
icon: 'Plus'
// code: "menu/add", // 按钮权限
}
],
otherConfig: {
outerBtnAlign: 'left',
needIndex: '序号',
needSelection: true,
innerBtnFixed: 'right',
rightMenuShow: true
},
tablePage: {
pageSize: 20,
currentPage: 1,
total: 4000,
hideOnSinglePage: true,
'size-change': (val: number) => {
console.log(val, '每页多少条')
},
'current-change': handleCurrentChange
}
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<base-table
:inner-btn="table.innerBtn"
:other-config="table.otherConfig"
:outer-btn="table.outerBtn"
:table-config="table.tableConfig"
:table-header="table.tableHeader"
:table-page="table.tablePage"
>
<template #btnSlot="{scope}">
<el-button>按钮</el-button>
scope: {{ scope }}
</template>
</base-table>
</template>

配置解析

可查看项目文件中的interface文件夹下面的table.ts文件

BaseNotice

通知公告组件, 用于循环播放通知公告, 鼠标滑动上去会暂停, 可拖动, 可设置播放速度, 颜色等

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
type INotice = {
noticeList: any[] // 数据
icon?: string // 显示的图标
iconColor?: string // 图标颜色
iconSize?: number // 图标大小
speed?: number // 滚动数据
txtColor?: string // 文字颜色
txtProps?: string // 数据展示字段
width?: string // 宽
height?: string // 高
bgCol?: string // 背景色
}
<script lang="ts" setup>
import BaseNotice from '@/components/BaseNotice/index.vue'
import { ref } from 'vue'
const noticeList = ref([
{
id: 1,
content: '初次搭建, 其中有问题欢迎大家交流与指正',
value: '1'
},
{
id: 2,
content: '有好的想法或者不足之处可留言,看到会回复',
value: '2'
},
{
id: 3,
content: '个人博客地址https://itzhi.github.io',
value: '3'
}
])
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<template>
<base-notice
v-if="noticeList?.length"
:notice-list="noticeList"
icon="ph:speaker-high-fill"
icon-color="#f9ae3d"
:icon-size="22"
:speed="-1"
bg-col="#fdf6ec"
txt-color="#f9ae3d"
txt-props="content"
width="300px"
height="30px"
/>
</template>

BaseSimple

分片上传组件, 结合element-plus的上传组件, 可本地执行, 启动目录下simpleNode下的server服务就可以本地运行模拟分片上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// props接收的值
interface ISimpleProps {
fileType: 'image' | 'video' | 'audio' | 'document' | 'other' // 上传文件的类型
accept?: string // 限制上传文件的类型 优先级比fileType高
fileSize?: number // 限制上传文件的大小 默认5M
multiple?: boolean // 是否可以多选文件
limit?: number // 可以上传多少个文件
}
// fileType 对应的的枚举类型, 在没有传入accept的情况下默认使用以下类型, 当fileType为'other'的时候默认限制类型为 '.*'
enum fileTypeObj {
image = '.gif, .jpg, .jpeg, .png, .bmp, .webp',
video = '.mp4, .m3u8, .rmvb, .avi, .swf, .3gp, .mkv, .flv',
audio = '.mp3, .wav, .wma, .ogg, .aac, .flac',
document = '.doc, .txt, .docx, .pages, .epub, .pdf, .numbers, .csv, .xls, .xlsx, .keynote, .ppt, .pptx'
}
1
2
3
<template>
<BaseSimple :file-size="20" :limit="1" file-type="other"/>
</template>

BaseMap

地图组件(仅供参考) 未封装, 可根据实际情况自己修改