176 Commits
v1.8.1 ... main

Author SHA1 Message Date
ahjung
3a469f1aca README.md 更新 2026-01-19 21:32:32 +08:00
ahjung
3bd0d28e05 示例结构调整,避免页面出现空白 2025-10-10 15:31:43 +08:00
ahjung
447bdcc355 README update 2025-09-21 21:33:30 +08:00
ahjung
8bf849b803 README update 2025-09-17 09:15:34 +08:00
ahjung
02930e5208 Merge remote-tracking branch 'origin/main' 2025-08-12 17:28:38 +08:00
ahjung
ffca3ed61f 2.1.0-publish 2025-08-12 17:28:18 +08:00
Ah jung
0f92c953cb Update README.md 2025-08-12 17:27:04 +08:00
ahjung
00247ee7b9 调整构建target 2025-08-12 16:40:20 +08:00
ahjung
449761796c 2.1.0 2025-08-12 16:10:10 +08:00
Ah jung
c96789f1ff Merge pull request #313 from tu6ge/main
feat: vscode 推荐 naive-UI 智能提示插件
2025-04-21 15:37:39 +08:00
tu6ge
301ca1a0df feat: vscode 推荐 naive-UI 智能提示插件 2025-04-21 15:33:15 +08:00
Ah jung
c5c28e958d Merge pull request #310 from emeiziying/bugfix/env-type
🐞 fix(env): fix env type
2025-03-24 15:53:48 +08:00
Ah jung
2f97cbee06 Merge pull request #311 from emeiziying/bugfix/redirect
🐞 fix(tabs): fix redirect tag while refresh in header
2025-03-24 15:52:43 +08:00
BlushGo
764cb71f39 🐞 fix(tabs): fix redirect tag while refresh in header 2025-03-19 23:02:46 +08:00
BlushGo
6d0aa46f20 🐞 fix(env): fix env type 2025-03-19 22:58:44 +08:00
Ah jung
f68ec16563 Update base.ts 修正 RedirectName 重复 2025-02-11 13:22:10 +08:00
ahjung
79c3cb5d4d README update 2024-11-12 03:28:15 +08:00
ahjung
7eb081ae87 README.md-新增交流qq群2 2024-11-01 15:43:38 +08:00
Ah jung
cc2a911f2a Merge pull request #298 from Mr-BeanSir/main
修复封装state时忘记修改html导致的报错
2024-10-21 12:10:32 +08:00
J.Bean
b88c047643 统一account页面与system中state的封装 2024-10-18 18:10:35 +08:00
J.Bean
01f9ba1046 修复封装state时忘记修改html导致的报错 2024-10-18 18:05:21 +08:00
ahjung
f729a5b8ba Fix Type Error 2024-10-17 21:02:08 +08:00
ahjung
7a62de39c2 2.0.0 2024-09-14 22:39:11 +08:00
Ah jung
1ae5372396 Merge pull request #294 from xiaohuanshu/patch-1
fix user logout error
2024-08-15 13:15:07 +08:00
xiaohuanshu
0bbc29023b fix user logout error 2024-08-13 17:44:24 +08:00
ahjung
84f55a8116 donate path update 2024-06-29 17:46:14 +08:00
ahjung
19df8d5f09 1.9.2 2024-06-29 17:35:10 +08:00
Ah jung
489f791caa Update README.md 2024-06-11 21:43:40 +08:00
ahjung
2f72f34bbe 优化已知错误 2024-05-12 11:02:47 +08:00
ahjung
3d4d554733 文案修改 2024-04-10 14:44:27 +08:00
ahjung
38fe255306 basicModal 组件 导出 modalMethods 方法 2024-04-10 14:36:36 +08:00
ahjung
58e99c183b README 文件更新 2024-04-09 16:43:58 +08:00
ahjung
ee8aed2f62 1.9.1 2024-04-09 16:28:28 +08:00
Ah jung
260397f3ae Merge pull request #279 from thatsgolden/patch-1
feat(BasicForm): trigger query via Enter key
2024-04-09 15:45:58 +08:00
Golden
fa983a9b64 feat(BasicForm): trigger query via Enter key
- Allow push Enter key to query the table form.
2024-03-04 13:36:55 +08:00
Ah jung
4c49e28596 Merge pull request #272 from Mevolove/main
style: 加宽基础表单,分步表单和上传图片中label的宽度
2024-02-18 13:38:49 +08:00
Mevol
9a5fe5249c style: 加宽基础表单,分步表单和上传图片中label的宽度 2024-01-16 15:19:10 +08:00
Ah jung
02235bf6fc Merge pull request #265 from NogiRuka/style/center-upload-icon-and-text-in-basicForm
style(BasicUpload): center upload icon and text in BasicForm
2023-10-25 19:31:11 +08:00
“乃木流架”
0656035857 style(BasicUpload): center upload icon and text in BasicForm 2023-10-21 23:54:14 +09:00
Ah jung
81e8222461 Merge pull request #257 from L1yp/fix-transition-hot-refresh
fix: 修复transition组件导致的白屏(重现: 热更新transition下的组件script代码切换tab即可)
2023-08-10 15:07:26 +08:00
Lyp
09411a927e fix: 修复transition组件导致的白屏(重现: 热更新transition下的组件script代码切换tab即可) 2023-08-10 14:55:16 +08:00
ahjung
347cd91735 1.9.0 2023-08-10 13:54:53 +08:00
ahjung
9b2effbc55 README 更新 2023-08-08 21:45:41 +08:00
Ah jung
d72f42dcd8 Merge pull request #249 from little-alei/main
优化ts类型
2023-07-29 13:16:20 +08:00
抠脚本人
165c358ef5 取消TableActionType的泛型 2023-07-29 12:31:31 +08:00
抠脚本人
fc181ce543 修改eslint 中 no-unused-vars未尤大推荐推荐,避免多余,错误的检测 2023-07-29 12:27:27 +08:00
抠脚本人
f04b9ba7f9 BasicColumn泛型增加默认类型 2023-07-29 12:25:17 +08:00
抠脚本人
ee0e507e47 Table组件columns增加泛型 2023-07-28 11:39:35 +08:00
抠脚本人
9d18715e90 调整vite/client的加载方式 2023-07-28 11:24:07 +08:00
Ah jung
74f0e4764e Merge pull request #242 from cyunrei/fix_margins
fix: adjust margins to prevent text from being cut off
2023-05-11 11:10:26 +08:00
Cyunrei
4cbdd9bc66 fix: adjust margins to prevent text from being cut off 2023-05-11 10:56:08 +08:00
Ah jung
e03b1bdfca Merge pull request #241 from MyFaith/main
fix: incorrect component name
2023-05-10 15:41:42 +08:00
MyFaith
b6a350e1be fix: incorrect component name 2023-05-09 15:34:17 +08:00
Ah jung
503437a433 Merge pull request #237 from bantangzm/fix-role
fix: formRef is not used and undefined
2023-05-04 13:38:43 +08:00
bantangzm
d4518849bf fix: formRef is not used and undefined 2023-04-28 10:12:37 +08:00
Ah jung
39178761b1 Merge pull request #233 from WangHansen/refactor/1.8.3
refactor: remove unused files, bug fixes, name changes
2023-04-24 18:33:20 +08:00
Hansen Wang
934de6b1e6 Add animation less back 2023-04-24 03:57:10 +08:00
Hansen Wang
58d13a242d Remove unused styles 2023-04-24 02:50:30 +08:00
Hansen Wang
5d891c1f44 refactor: remove unused files, bug fixes and name changes 2023-04-24 02:09:39 +08:00
Ah jung
e1528823f7 Merge pull request #232 from CodeListener/fix/utils
fix(utils): fix type error
2023-04-11 18:15:24 +08:00
yangguang
1fa35db71b fix(utils): fix type error 2023-04-11 14:20:18 +08:00
Ah jung
80729d925e Merge pull request #228 from Elben9/fix-projectStoreGetters
fix: projectSetting内getters getCrumbsSetting返回对象错误
2023-03-27 17:38:07 +08:00
chenxuehai@alltuu.com
e228184b72 fix: pinia的projectSetting内getters getCrumbsSetting返回对象错误 2023-03-24 17:42:17 +08:00
Ah jung
2f541106f2 Update README.md 2023-03-15 11:04:20 +08:00
Ah jung
5f81f4cc77 Update README.md 2023-03-15 11:03:59 +08:00
Ah jung
b4f32f50c5 Update README.md 2023-03-12 14:01:50 +08:00
Ah jung
5d180b6fd5 Merge pull request #225 from Elben9/fix-importMetaGlobTypeError
fix: add type for importMetaGlob
2023-03-12 13:58:12 +08:00
chenxuehai@alltuu.com
3d6465346c fix: add type for importMetaGlob 2023-03-10 11:23:06 +08:00
Ah jung
dc98b96ce2 Merge pull request #221 from zmlnf/fix/pageHeader
fix: reload page warning
2023-03-09 08:55:14 +08:00
Ah jung
3f84af912f Merge pull request #220 from zmlnf/fix/basicList
fix: rules type missMatch
2023-03-09 08:54:31 +08:00
Ah jung
8dcd66b654 Merge pull request #219 from zmlnf/fix/createDrawer
fix: type missMatch
2023-03-09 08:54:05 +08:00
Ah jung
4bdc54a77f Merge pull request #217 from Elben9/feat-importMetaUpdate
feat: ImportMeta中globEager已废弃,改为glob('*', { eager: true })
2023-03-09 08:53:37 +08:00
Ah jung
0ceb7437a4 Merge pull request #211 from shark-lajiao/main
fix:keepAliveComponents
2023-03-09 08:53:12 +08:00
Ah jung
b984828f33 Merge pull request #213 from Stream-web/achuang-admin
fix:修改loginDesc
2023-03-09 08:52:48 +08:00
许诺
1d10826760 Merge branch 'jekip:main' into main 2023-03-08 13:28:44 +08:00
ziming.tang
74334de7e0 fix: reload page warning 2023-02-23 14:20:06 +08:00
ziming.tang
e153837497 fix: rules type missMatch 2023-02-23 14:00:17 +08:00
ziming.tang
d0ec44b48c fix: type missMatch 2023-02-23 13:55:59 +08:00
chenxuehai@alltuu.com
e9efbb5e67 feat: ImportMeta中globEager已废弃,改为glob('*', { eager: true }) 2023-02-21 17:31:58 +08:00
Ah jung
aa90e0a468 Update README.md 2023-02-09 09:16:58 +08:00
15398271468
9049f743a8 fix:修改loginDesc 2023-01-01 22:15:07 +08:00
鲨鱼辣椒
1f858929d6 fix:keepAliveComponents 2022-12-30 10:21:03 +08:00
Ah jung
5a50bc0dc4 Merge pull request #209 from 377342263/main
修复当pagination传Boolean类型时,表格高度计算有误的问题
2022-12-21 18:02:36 +08:00
黄富鑫
a35318f808 修复当pagination传Boolean类型时,表格高度计算有误的问题 2022-12-21 16:17:53 +08:00
Ah jung
7a18f58f4a Merge pull request #207 from xiangshu233/main
perf: 单独拆分 naive 全局 API 挂载方法
2022-12-19 11:44:40 +08:00
xiangshu233
3fb8c85112 perf: 单独拆分 naive 全局 API 挂载方法 2022-12-17 15:39:40 +08:00
Ah jung
921dd91eda Merge pull request #206 from xiangshu233/main
解决 eslint 版本升级导致所有 index.vue 会报错的问题
2022-12-15 18:01:33 +08:00
xiangshu233
bcd2480f69 解决 eslint 版本升级导致所有 index.vue 会报错的问题 2022-12-15 17:53:40 +08:00
Ah jung
590d1615cb Merge pull request #205 from vastsa/main
fix:两杯都*1000,导致最终过期时间为7000天
2022-12-13 16:44:16 +08:00
lan-air
23dcfe000b fix:两杯都*1000,导致最终过期时间为7000天 2022-12-11 19:14:25 +08:00
Ah jung
47e47c8a59 Merge pull request #201 from yuhengshen/main
fix: tabs-view 宽度计算错误
2022-12-07 09:06:35 +08:00
yuhengshen
719ad34a94 fix: 类型错误修正 2022-11-28 23:40:01 +08:00
yuhengshen
a0deba504a fix: tabs-view 容器宽度计算错误 2022-11-28 23:39:09 +08:00
xiaoma
25d6134923 1.8.2 2022-11-24 09:19:19 +08:00
Ah jung
a646de72e2 Merge pull request #200 from shark-lajiao/main
feat:防抖节流等指令
2022-11-23 14:04:37 +08:00
chen shu tao
e63963cf08 feat:防抖节流等指令 2022-11-23 13:40:09 +08:00
Ah jung
c43ec3ef1c Merge pull request #198 from Qujh97/main
fix: 修复暗黑模式下左侧菜单主题色不生效问题
2022-11-18 10:31:44 +08:00
qu
4f0a7967f7 fix: 修复暗黑模式下左侧菜单主题色不生效问题 2022-11-18 10:28:51 +08:00
Ah jung
1c594b49c0 Merge pull request #196 from elonehoo/main
提升了包的版本,做了迁移
2022-11-12 08:51:25 +08:00
elonehoo
b68103455b fix:modified to the previous injection 2022-11-11 23:41:56 +08:00
elonehoo
724af94c08 revert:upgraded the version of all packages to the latest version, and made an upgrade before cross-version 2022-11-11 23:38:37 +08:00
Ah jung
af68d4bc67 Merge pull request #195 from 15982650876/feat/shark
feat:add 增加了菜单font图标的适配
2022-11-10 21:05:19 +08:00
chen shu tao
2fb36695db feat:add 增加了菜单font图标的适配 2022-11-10 13:15:17 +08:00
xiaoma
32116be1f2 fix icon error 2022-11-04 10:18:51 +08:00
Ah jung
4a47fa2f7d Merge pull request #192 from honorsuper/patch-5
feat: 左侧菜单,过滤children下面的item为hidden状态
2022-11-03 17:07:49 +08:00
Ah jung
89f68789ff Merge pull request #193 from honorsuper/patch-6
feat: 类型修改
2022-11-03 17:07:32 +08:00
honorsuper
4c0de9d5c2 feat: 类型修改 2022-11-03 11:30:59 +08:00
honorsuper
1e72351dea feat: 左侧菜单,过滤children下面的item为hidden状态 2022-11-02 10:48:12 +08:00
Ah jung
da24659944 Update Storage.ts 2022-10-31 18:23:39 +08:00
Ah jung
eb21a99df1 Merge pull request #190 from thelostword/refactor/naive-global-api
refactor: naive-ui版本升级,naive全局API挂载方式修改
2022-10-29 15:36:39 +08:00
Ah jung
c598d3f7bc Create README.md 2022-10-29 14:32:21 +08:00
losting
fa0163a385 naive全局API挂载方式修改 2022-10-28 18:26:51 +08:00
losting
57fef38bfe 删除头部注释 2022-10-28 18:25:34 +08:00
losting
d62ca84328 删除头部注释 2022-10-28 18:24:35 +08:00
thelostword
c504e61d70 refactor: naive-ui版本升级,naive全局API挂载方式修改 2022-10-28 18:15:51 +08:00
Ah jung
c0d3e11346 Merge pull request #189 from thelostword/refactor/setupNaive
refactor: naive-ui 组件注册优化
2022-10-25 16:21:21 +08:00
thelostword
4c27623a0f refactor: naive-ui 组件注册优化 2022-10-25 15:59:13 +08:00
Ah jung
0107c5f035 Update README.md 2022-10-09 14:57:55 +08:00
Ah jung
6134a4c125 Merge pull request #182 from godkun/main
fix: 替换项目文档地址
2022-10-08 08:59:25 +08:00
godkun
00dd7409b1 fix: 替换项目文档地址 2022-10-03 16:48:39 +08:00
Ah jung
a7ad4a3634 Update README.md 2022-09-30 14:59:31 +08:00
Ah jung
327f8de00b Merge pull request #179 from honorsuper/patch-4
pre: 代码优化
2022-09-24 09:04:29 +08:00
honorsuper
026e4c1acd pre: 代码优化 2022-09-23 09:28:58 +08:00
Ah jung
9ba51e4a34 Merge pull request #170 from soup-Lee/main
fix: 修复面包屑只有一个title的时候左边会多出一个空span的问题
2022-08-26 09:13:34 +08:00
“青菜白玉汤”
bc2d2b4e94 fix: 修复面包屑只有一个title的时候左边会多出一个空span的问题 2022-08-19 17:05:06 +08:00
Ah jung
e2f90cbf88 Merge pull request #169 from honorsuper/patch-3
feat: add GraphicComponent
2022-08-19 10:38:40 +08:00
honorsuper
2da9b8a465 feat: add GraphicComponent 2022-08-19 10:26:11 +08:00
Ah jung
f99ecea202 Merge pull request #163 from jerry4718/main
修正isAsyncFunction的类型描述
2022-08-17 18:02:31 +08:00
Ah jung
e3326a0f11 Merge pull request #165 from honorsuper/patch-2
docs: 文案修改
2022-08-17 18:02:05 +08:00
Ah jung
775f5b63af Merge pull request #167 from soup-Lee/main
fix: 修复顶级路由中面包屑多了一个‘/’的问题
2022-08-17 18:01:21 +08:00
“青菜白玉汤”
874fd3437d fix: 修复顶级路由中面包屑多了一个‘/’的问题 2022-08-17 17:01:05 +08:00
honorsuper
7644c59799 docs: 文案修改 2022-08-15 15:31:10 +08:00
码梦天涯
f8ed3d1607 修正isAsyncFunction的类型描述 2022-08-10 09:45:31 +08:00
Ah jung
0c8bb030de Merge pull request #161 from honorsuper/patch-1
feat: 增加主动销毁echarts实例方法
2022-08-09 10:21:54 +08:00
honorsuper
41a6f59617 feat: 增加主动销毁echarts实例方法 2022-08-09 10:13:36 +08:00
Ah jung
1a7eaf7efb Merge pull request #159 from gp0119/main
fix(BasicUpload): 修复默认图片不显示bug,默认图片加载错误时删除按钮不显示bug
2022-08-01 08:12:57 +08:00
ganpeng
5714233d57 fix(BasicUpload): 修复默认图片不显示bug,默认图片加载错误时删除按钮不显示bug 2022-07-28 15:01:17 +08:00
Ah jung
5e2452f2df Merge pull request #156 from yirandidi/main
fix: 路由配置activeMenu的bug
2022-07-25 20:11:23 +08:00
yiran
e9d3e1affe Merge branch 'main' of https://github.com/yirandidi/naive-ui-admin 2022-07-25 18:20:21 +08:00
yiran
f6d419b94f 🐞 fix: 路由配置activeMenu的bug
https://github.com/jekip/naive-ui-admin/issues/151
2022-07-25 18:20:16 +08:00
Ah jung
cc0d1824f2 Merge pull request #155 from yirandidi/main
 feat: 添加网站配置文件
2022-07-25 18:03:42 +08:00
yiran
299f8c89a5 feat: 添加网站配置文件 2022-07-25 17:52:33 +08:00
Ah jung
9e40480b9b Merge pull request #153 from aqi-lu/feat/aqi
fix: 修复ts类型错误
2022-07-24 21:19:14 +08:00
AQI
c20ee24cd8 fix: 修复ts类型错误 2022-07-23 18:15:38 +08:00
Ah jung
eb27f19087 Merge pull request #149 from xiangshu233/main
BaseTableAction 新增 icon 配置项 + 表格斑马纹
2022-07-14 16:52:21 +08:00
xiangshu233
3cbb6061c5 BaseTableActin 新增 icon 配置项 + 表格斑马纹 2022-07-14 16:31:31 +08:00
Ah jung
e07e36ce5d Merge pull request #141 from yulimchen/login-styles
fix(login): 登录页手机端样式问题
2022-06-24 16:52:56 +08:00
yulimchen
0f8b0d3557 fix(login): 登录页手机端样式问题 2022-06-24 16:36:03 +08:00
Ah jung
28d2696061 Merge pull request #139 from baixi233/fixbug
fixbug
2022-06-21 22:31:12 +08:00
baixi233
b2c22996ce fix tagsview 2022-06-21 11:30:59 +08:00
Ah jung
f616852053 Merge pull request #137 from cyunrei/fix_placeholder
fix: 修复错误的placeholder提示信息
2022-06-14 11:31:47 +08:00
Cyunrei
92316285bb fix: 修复错误的placeholder提示信息 2022-06-14 11:23:28 +08:00
Ah jung
1077a20cbd Merge pull request #136 from Zheng-Changfu/fix/table-fetch-interceptor-statement
fix(function):修复 useDataSource 中请求前请求后回调未在prop中声明、isFunction支持普通、箭头函数判断
2022-06-14 08:52:55 +08:00
郑常富
7b9b391adc fix(function):修复table中hook(useDataSource)中请求前请求后回调未在prop中声明、isFunction支持普通、箭头函数判断 2022-06-13 22:02:37 +08:00
Ah jung
88ada7ebee Merge pull request #134 from xiangshu233/main
修复 useECharts Hooks 报错和暗色模式 bug
2022-06-08 20:00:11 +08:00
xiangshu233
a7257568cf 修复 useECharts Hooks 报错和暗色模式 bug 2022-06-08 19:56:43 +08:00
Ah jung
403a844668 Merge pull request #133 from aqi-lu/feat/aqi
feat(function): add 菜单权限添加删除
2022-06-06 08:46:33 +08:00
Ah jung
197e63cb51 Merge pull request #131 from fanck0605/patch-1
fix: 修复错误的 URL 正则表达式
2022-06-06 08:45:52 +08:00
AQI
6c3273e214 feat(function): add 菜单权限添加删除 2022-06-05 21:34:55 +08:00
Chuck
71be58fc1f fix: 修复错误的 URL 正则表达式 2022-06-02 23:12:54 +08:00
Ah jung
e3e14b8259 Merge pull request #130 from gp0119/main
fix: 删除无用代码和修复一些错误
2022-06-01 16:24:55 +08:00
ganpeng
5f625aa305 fix: 删掉无用的css 2022-06-01 14:02:54 +08:00
ganpeng
8167d4165b fix: 修复card组件prop参数值类型错误 2022-06-01 14:02:11 +08:00
ganpeng
873e0d3e43 fix: 修复错误 2022-06-01 13:57:02 +08:00
ganpeng
f20f4209a3 fix: 修复ts类型错误 2022-06-01 13:54:32 +08:00
ganpeng
363ed7ae9e fix: 删除无用代码 2022-06-01 13:49:38 +08:00
Ah jung
a21cc8aec8 Merge pull request #126 from Zheng-Changfu/feat/table-fetch-interceptor
feat(function): add table-fetch(before and after interceptor)
2022-05-31 15:55:18 +08:00
Ah jung
0f229aea13 Merge pull request #128 from gp0119/main
fix: 修复ts类型找不到错误
2022-05-31 15:54:38 +08:00
ganpeng
89151afce3 fix: 修复ts类型找不到错误 2022-05-30 22:08:58 +08:00
Ah jung
ffac6c0d78 Merge pull request #127 from zhouyuf/main
fix: 修复dashboard页mock返回数据显示错误
2022-05-30 11:07:09 +08:00
zhouyuf
c257ca0948 fix: 修复dashboard页mock返回数据显示错误 2022-05-28 17:24:00 +08:00
zhouyuf
9bcd6d9700 Merge remote-tracking branch 'upstream/main' 2022-05-28 17:02:55 +08:00
郑常富
a66f56b9ce feat(function): add table-fetch(before and after interceptor) 2022-05-28 15:03:16 +08:00
xiaoma
9e58578706 version update 2022-05-11 11:45:00 +08:00
zhouyuf
e6505c88b7 Merge pull request #2 from jekip/main
update
2021-08-14 19:50:20 +08:00
zhouyuf
55ee389184 Merge pull request #1 from jekip/main
update
2021-08-08 20:18:36 +08:00
179 changed files with 10891 additions and 16792 deletions

3
.env
View File

@@ -6,6 +6,3 @@ VITE_GLOB_APP_TITLE = AdminPro
# spa shortname
VITE_GLOB_APP_SHORT_NAME = AdminPro
# 生产环境 开启mock
VITE_GLOB_PROD_MOCK = true

View File

@@ -4,11 +4,11 @@ VITE_PORT = 8001
# 网站根目录
VITE_PUBLIC_PATH = /
# 是否开启mock
# 是否开启 mock
VITE_USE_MOCK = true
# 网站前缀
VITE_BASE_URL = /
# 是否开启控制台打印 mock 请求信息
VITE_LOGGER_MOCK = true
# 是否删除console
VITE_DROP_CONSOLE = true
@@ -20,11 +20,11 @@ VITE_DROP_CONSOLE = true
# API 接口地址
VITE_GLOB_API_URL =
# 图片上传地址
VITE_GLOB_UPLOAD_URL=
# 图片前缀地址
VITE_GLOB_IMG_URL=
# 接口前缀
VITE_GLOB_API_URL_PREFIX = /api
# 文件上传地址
VITE_GLOB_UPLOAD_URL=
# 文件前缀地址
VITE_GLOB_FILE_URL=

View File

@@ -4,24 +4,21 @@ VITE_USE_MOCK = true
# 网站根目录
VITE_PUBLIC_PATH = /
# 网站前缀
VITE_BASE_URL = /
# 是否删除console
VITE_DROP_CONSOLE = true
# API
VITE_GLOB_API_URL =
# 接口前缀
VITE_GLOB_API_URL_PREFIX = /api
# 图片上传地址
VITE_GLOB_UPLOAD_URL=
# 图片前缀地址
VITE_GLOB_IMG_URL=
# 接口前缀
VITE_GLOB_API_URL_PREFIX = /api
# 是否启用gzip压缩或brotli压缩
# 可选: gzip | brotli | none
# 如果你需要多种形式,你可以用','来分隔

View File

@@ -25,6 +25,7 @@ module.exports = defineConfig({
],
rules: {
'vue/script-setup-uses-vars': 'error',
'vue/multi-word-component-names': 'off',
'@typescript-eslint/ban-ts-ignore': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/no-explicit-any': 'off',
@@ -37,19 +38,12 @@ module.exports = defineConfig({
'@typescript-eslint/ban-types': 'off',
'@typescript-eslint/no-non-null-assertion': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
],
'@typescript-eslint/no-unused-vars': ['error', { varsIgnorePattern: '.*', args: 'none' }],
'no-unused-vars': [
'error',
{
argsIgnorePattern: '^_',
varsIgnorePattern: '^_',
},
// we are only using this rule to check for unused arguments since TS
// catches unused variables but not args.
{ varsIgnorePattern: '.*', args: 'none' },
],
'space-before-function-paren': 'off',

1
.gitignore vendored
View File

@@ -18,6 +18,7 @@ pnpm-debug.log*
# Editor directories and files
.idea
.vscode
!.vscode/extensions.json
*.suo
*.ntvs*
*.njsproj

5
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,5 @@
{
"recommendations": [
"tu6ge.naive-ui-intelligence",
]
}

View File

@@ -1,11 +1,69 @@
# CHANGELOG
## 2.1.0
- 优化 `登录页面` 排版
- 新增 `构建分包策略`
- 新增 `useLocalSetting` hook
- 依赖升级
## 2.0.0
- 新增 `alova` 请求库
- 新增 `@faker-js/faker` 可配合 `mock` 数据模拟
- 新增 `VITE_USE_MOCK` 环境变量-开启 `mock`
- 新增 `demo` 实例,新增/编辑角色
- 移除 `axios` 请求封装,使用 `alova` 代替
- 移除 `vite-plugin-mock` 使用 `@alova/mock` 代替
- 移除 `VITE_GLOB_PROD_MOCK` 环境变量
- 变更 `VITE_GLOB_IMG_URL` 环境变量变更成 `VITE_GLOB_FILE_URL`
- 优化 `BasicTable` 组件相关样式
- 优化 `TS` 类型定义
- 依赖升级
## 1.9.2
- 升级 `vite``5.x` 版本
- 优化 `BasicTable` 组件,编辑样式
- 新增 `BasicTable` 组件,支持 `striped` 入参
- 依赖升级
## 1.9.1
- 优化 `typeSctipt` 类型定义
- 优化 `setup` 语法
- 依赖升级
## 1.9.0
- 新增 `BasicForm` 组件,支持 `setLoading`, `setSchema` 方法
- 新增 `countField` 总数字段名配置
- 优化 `yarn` 切换至 `pnpm`
- 优化 `BasicForm` 组件,验证返回值
- 优化 `BasicTable` 组件
- 修复 `TableAction组件左右间隔不生效` 关闭[253](https://github.com/jekip/naive-ui-admin/issues/253)
- 修复 `BasicTable组件没有数据会一直请求接口` 关闭[#251](https://github.com/jekip/naive-ui-admin/issues/251)
- 修复 `useModal+useForm组件的bug` 关闭[#250](https://github.com/jekip/naive-ui-admin/issues/250)
- 修复 `手机端侧边导航风格不一致bug` 关闭[#247](https://github.com/jekip/naive-ui-admin/issues/247
- 移除 `yarn.lock` 文件
- 依赖升级
## 1.8.2
- ### ✨ Features
- 新增 `directive` 示例
- 依赖升级
### 🐛 Bug Fixes
- 修复 `样式异常` 图片
## 1.8.1
- ### ✨ Features
- 新增 `clean:cache` 删除缓存命令
- 新增 `clean:lib` 删除 `node_modules` 命令
- 依赖升级
### 🐛 Bug Fixes
- 修复 `开发环境` 运行控制台错误提示

108
README.md
View File

@@ -1,58 +1,63 @@
## 简介
## 🚀 简介
[Naive Ui Admin](https://github.com/jekip/naive-ui-admin) 完全免费且可商用,基于 [Vue3.0](https://github.com/vuejs/vue-next)、[Vite](https://github.com/vitejs/vite)、 [Naive UI](https://www.naiveui.com/)、[TypeScript](https://www.typescriptlang.org/) 的中后台解决方案,它使用了最新的前端技术栈,并提炼了典型的业务模型,页面,包括二次封装组件、动态菜单、权限校验、粒子化权限控制等功能,它可以帮助你快速搭建企业级中后台项目, 相信不管是从新技术使用还是其他方面,都能帮助到你
`Naive Ui Admin` 是一款 完全免费 且可商用的中后台解决方案,基于 🌟 `Vue3.x` 🌟、🚀 `Vite` 🚀、✨ [Naive UI](https://www.naiveui.com/) ✨ 和 🎉 `TypeScript` 🎉
它融合了最新的前端技术栈,提炼了典型的业务模型和页面,包括二次封装组件、动态菜单、权限校验等功能,助力快速搭建企业级中后台项目
## 特性
- 二次封装实用高扩展性组件
- 响应式、多主题多配置,快速集成,开箱即用
- 最新技术栈,使用 `Vue3``Typescript``Pinia``Vite` 等前端前沿技术
- 强大的鉴权系统,对路由、菜单、功能点等支持`三种鉴权模式`,满足不同的业务鉴权需求
- 持续更新,实用性页面模板功能和交互,随意搭配组合,让构建页面变得简单化
## 🌈 特性
📦 二次封装实用高扩展性组件
🎨 响应式、多主题多配置,快速集成,开箱即用
🚀 强大的鉴权系统,支持 三种鉴权模式,满足多样业务需求
🌐 持续更新的实用性页面模板和交互设计,简化页面构建
## 预览
- [naive-ui-admin](https://naive-ui-admin.vercel.app)
## 🎥 预览
- [naive-ui-admin](https://gratis.naiveadmin.com)
账号admin密码123456随意
## 提示
如果这个版本的功能和组件,并不能满足您的需求,不妨看看,我们全新 `NaiveAdmin v2` 他或许能让您眼前一亮O(∩_∩)O哈哈~
## 🚀 Naive Admin - 开箱即用的企业级前后端框架 `商业版本`
[NaiveAdmin 官网](https://www.naiveadmin.com)
> **✨ 多版本选择 · 四年持续迭代**
> 配套前后端支持 Java/Php 语言,支持单体和微服务多租户架构
> [详情→官网](https://www.naiveadmin.com) | [更新日志](https://www.yuque.com/u5825/zaqu0e)
[NaiveAdmin v2 预览](https://pro.naiveadmin.com)
---
[NaiveAdmin v2 变更日志](https://www.naiveadmin.com/guide/changelog)
## 🔥 为什么选择 NaiveAdmin 商业版?
- **省时间**:内置丰富扩展组件与业务模板,不写一行样板代码即可开始业务开发
- **经实战**:已落地电网、跨境 ERP、SaaS 等 30+ 场景
- **可扩展**:插件式菜单 / 按钮 / 数据权限新增业务模块「0 侵入」
## 新品
---
### Antd vue
## 🖥️ 纯前端版本
新产品,如果您选的技术栈是 `Antd` 的话,不妨看看
| 版本 | 技术栈 | 配套后端 | 预览地址 |
|-----|-------|---------|-------------|
| **🆕 Naive UI Max** | Vu3 + Ts + NaiveUI | 否 | [https://max.naiveadmin.com](https://max.naiveadmin.com) |
| **Naive UI Plus** | Vu3 + Ts + NaiveUI | 支持Java/PHP | [https://plus.naiveadmin.com](https://plus.naiveadmin.com) |
[NaiveAdmin Antd 预览](https://antd.naiveadmin.com)
## 🔌 前后端版本
### Arco vue
| 版本 | 技术栈 | 预览地址 |
|------|------------------|--------------------------------------------------------------|
| **🆕Naive UI Max** | Vu3 + Ts + NaiveUI | [https://max-full.naiveadmin.com](https://max-full.naiveadmin.com) |
| **Naive UI Plus** | Vu3 + Ts + NaiveUI | [https://plus-full.naiveadmin.com](https://plus-full.naiveadmin.com) |
新产品,智能设计体系,连接轻盈体验
## 🏢 多租户版本
[NaiveAdmin Arco 预览](https://arco.naiveadmin.com)
### Element Plus
新产品,面向设计师和开发者的组件库
[Element Plus Admin 预览](https://element.naiveadmin.com)
以上版本同时具备 `NaiveAdmin v2` 功能/组件/页面,一如既往、开箱即用,欢迎前往查看。
| 版本 | 技术栈 | 适用场景 | 预览地址 |
|-----------------------------|-----------------------------|----------------|-------------------------------------------|
| **Vue3** | Vu3 + Ts + NaiveUI + Java | 构建企业级 Saas 化系统 | [https://tenant.naiveadmin.com](https://tenant.naiveadmin.com) |
| **React** | React + Ts + Ant + Java | 构建企业级 Saas 化系统 | [https://compose.warden.vip](https://compose.warden.vip) |
## 文档
## 📚 文档
[v1文档地址](https://naive-ui-admin-docs.vercel.app)
[开源版本文档](https://docs.naiveadmin.com)
## 准备
## 🛠 准备
- [node](http://nodejs.org/) 和 [git](https://git-scm.com/) -项目开发环境
- [Vite](https://vitejs.dev/) - 熟悉 vite 特性
@@ -60,10 +65,11 @@
- [TypeScript](https://www.typescriptlang.org/) - 熟悉`TypeScript`基本语法
- [Es6+](http://es6.ruanyifeng.com/) - 熟悉 es6 基本语法
- [Vue-Router-Next](https://next.router.vuejs.org/) - 熟悉 vue-router 基本使用
- [Naive-ui-admin](https://www.naiveui.com/) - ui 基本使用
- [NaiveUi](https://www.naiveui.com/) - ui 基本使用
- [Mock.js](https://github.com/nuysoft/Mock) - mockjs 基本语法
## 使用
## 🏗️ 使用
- 获取项目代码
@@ -76,30 +82,30 @@ git clone https://github.com/jekip/naive-ui-admin.git
```bash
cd naive-ui-admin
yarn install
pnpm install
```
- 运行
```bash
yarn dev
pnpm run dev
```
- 打包
```bash
yarn build
pnpm build
```
## 更新日志
## 📜 更新日志
[CHANGELOG](./CHANGELOG.md)
## 如何贡献
## 🤝 如何贡献
非常欢迎你的加入![提一个 Issue](https://github.com/jekip/naive-ui-admin/issues) 或者提交一个 Pull Request
非常欢迎你的加入![提一个 Issue](https://github.com/jekip/naive-ui-admin/issues) 或者提交一个 `Pull Request`
**Pull Request:**
@@ -109,7 +115,7 @@ yarn build
4. 推送您的分支: `git push origin feat/xxxx`
5. 提交`pull request`
## Git 贡献提交规范
## 📋 Git 贡献提交规范
- 参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
@@ -127,7 +133,7 @@ yarn build
- `types` 类型定义文件更改
- `wip` 开发中
## 浏览器支持
## 🌐 浏览器支持
本地开发推荐使用`Chrome 80+` 浏览器
@@ -137,18 +143,18 @@ yarn build
| :-: | :-: | :-: | :-: | :-: |
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
## 维护者
## 👥 维护者
[@Ah jung](https://github.com/jekip)
## 交流
## 💬 交流
`Naive Ui Admin` 使用或其他问题,可以在群内讨论或提问。
有关 `Naive Ui Admin` 使用或其他问题,可以加入讨论群交流问题
![abelianGroup](https://user-images.githubusercontent.com/19426584/160335146-c28dd205-4600-4d62-b2c6-6456034ab7b1.jpg)
QQ1群328347666 (已满)
QQ2群741353560
## 赞助
#### 如果你觉得这个项目帮助到了你,你可以帮作者一杯果汁表示鼓励 🍹。
![donate](https://jekip.github.io/docs/images/sponsor.png)
## 💖 赞助
#### 如果项目有帮到你,不妨请作者一杯咖啡吧!
![donate](https://assets.naiveadmin.com/assets/images/sponsor.png)
[Paypal Me](https://www.paypal.com/paypalme/majunping)

View File

@@ -2,9 +2,9 @@
* Plugin to minimize and use ejs template syntax in index.html.
* https://github.com/anncwb/vite-plugin-html
*/
import type { Plugin } from 'vite';
import type { PluginOption } from 'vite';
import html from 'vite-plugin-html';
import { createHtmlPlugin } from 'vite-plugin-html';
import pkg from '../../../package.json';
import { GLOB_CONFIG_FILE_NAME } from '../../constant';
@@ -18,11 +18,11 @@ export function configHtmlPlugin(env: ViteEnv, isBuild: boolean) {
return `${path || '/'}${GLOB_CONFIG_FILE_NAME}?v=${pkg.version}-${new Date().getTime()}`;
};
const htmlPlugin: Plugin[] = html({
const htmlPlugin: PluginOption[] = createHtmlPlugin({
minify: isBuild,
inject: {
// Inject data into ejs template
injectData: {
data: {
title: VITE_GLOB_APP_TITLE,
},
// Embed the generated app.config.js file

View File

@@ -1,4 +1,4 @@
import type { Plugin } from 'vite';
import type { Plugin, PluginOption } from 'vite';
import Components from 'unplugin-vue-components/vite';
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers';
@@ -6,13 +6,12 @@ import vue from '@vitejs/plugin-vue';
import vueJsx from '@vitejs/plugin-vue-jsx';
import { configHtmlPlugin } from './html';
import { configMockPlugin } from './mock';
import { configCompressPlugin } from './compress';
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock) {
const { VITE_USE_MOCK, VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean) {
const { VITE_BUILD_COMPRESS, VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE } = viteEnv;
const vitePlugins: (Plugin | Plugin[])[] = [
const vitePlugins: (Plugin | Plugin[] | PluginOption[])[] = [
// have to
vue(),
// have to
@@ -28,9 +27,6 @@ export function createVitePlugins(viteEnv: ViteEnv, isBuild: boolean, prodMock)
// vite-plugin-html
vitePlugins.push(configHtmlPlugin(viteEnv, isBuild));
// vite-plugin-mock
VITE_USE_MOCK && vitePlugins.push(configMockPlugin(isBuild, prodMock));
if (isBuild) {
// rollup-plugin-gzip
vitePlugins.push(

View File

@@ -1,19 +0,0 @@
/**
* Mock plugin for development and production.
* https://github.com/anncwb/vite-plugin-mock
*/
import { viteMockServe } from 'vite-plugin-mock';
export function configMockPlugin(isBuild: boolean, prodMock: boolean) {
return viteMockServe({
ignore: /^\_/,
mockPath: 'mock',
localEnabled: !isBuild,
prodEnabled: isBuild && prodMock,
injectCode: `
import { setupProdMockServer } from '../mock/_createProductionServer';
setupProdMockServer();
`,
});
}

View File

@@ -1,22 +0,0 @@
/**
* Introduces component library styles on demand.
* https://github.com/anncwb/vite-plugin-style-import
*/
import styleImport from 'vite-plugin-style-import';
export function configStyleImportPlugin(isBuild: boolean) {
if (!isBuild) return [];
const styleImportPlugin = styleImport({
libs: [
{
libraryName: 'ant-design-vue',
esModule: true,
resolveStyle: (name) => {
return `ant-design-vue/es/${name}/style/index`;
},
},
],
});
return styleImportPlugin;
}

View File

@@ -1,57 +0,0 @@
module.exports = {
ignores: [(commit) => commit.includes('init')],
extends: ['@commitlint/config-conventional'],
parserPreset: {
parserOpts: {
headerPattern: /^(\w*|[\u4e00-\u9fa5]*)(?:[\(\](.*)[\)\])?[\:\] (.*)/,
headerCorrespondence: ['type', 'scope', 'subject'],
referenceActions: [
'close',
'closes',
'closed',
'fix',
'fixes',
'fixed',
'resolve',
'resolves',
'resolved',
],
issuePrefixes: ['#'],
noteKeywords: ['BREAKING CHANGE'],
fieldPattern: /^-(.*?)-$/,
revertPattern: /^Revert\s"([\s\S]*)"\s*This reverts commit (\w*)\./,
revertCorrespondence: ['header', 'hash'],
warn() {},
mergePattern: null,
mergeCorrespondence: null,
},
},
rules: {
'body-leading-blank': [2, 'always'],
'footer-leading-blank': [1, 'always'],
'header-max-length': [2, 'always', 108],
'subject-empty': [2, 'never'],
'type-empty': [2, 'never'],
'type-enum': [
2,
'always',
[
'feat',
'fix',
'perf',
'style',
'docs',
'test',
'refactor',
'build',
'ci',
'chore',
'revert',
'wip',
'workflow',
'types',
'release',
],
],
},
};

View File

@@ -1,18 +0,0 @@
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
const modules = import.meta.globEager('./**/*.ts');
const mockModules: any[] = [];
Object.keys(modules).forEach((key) => {
if (key.includes('/_')) {
return;
}
mockModules.push(...modules[key].default);
});
/**
* Used in a production environment. Need to manually import all modules
*/
export function setupProdMockServer() {
createProdMockServer(mockModules);
}

View File

@@ -1,44 +1,44 @@
import { Random } from 'mockjs';
import { defineMock } from '@alova/mock';
import { faker } from '@faker-js/faker';
import { resultSuccess } from '../_util';
const consoleInfo = {
function getRandom(options) {
return Number(faker.commerce.price(options));
}
const result = {
//访问量
visits: {
dayVisits: Random.float(10000, 99999, 2, 2),
rise: Random.float(10, 99),
decline: Random.float(10, 99),
amount: Random.float(99999, 999999, 3, 5),
dayVisits: getRandom({ min: 10000, max: 99999, dec: 2 }),
rise: getRandom({ min: 10000, max: 99999, dec: 0 }),
decline: getRandom({ min: 10000, max: 99999, dec: 0 }),
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
},
//销售额
saleroom: {
weekSaleroom: Random.float(10000, 99999, 2, 2),
amount: Random.float(99999, 999999, 2, 2),
degree: Random.float(10, 99),
weekSaleroom: getRandom({ min: 10000, max: 99999, dec: 2 }),
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
degree: getRandom({ min: 10000, max: 99999, dec: 0 }),
},
//订单量
orderLarge: {
weekLarge: Random.float(10000, 99999, 2, 2),
rise: Random.float(10, 99),
decline: Random.float(10, 99),
amount: Random.float(99999, 999999, 2, 2),
weekLarge: getRandom({ min: 10000, max: 99999, dec: 2 }),
rise: getRandom({ min: 10000, max: 99999, dec: 0 }),
decline: getRandom({ min: 10000, max: 99999, dec: 0 }),
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
},
//成交额度
volume: {
weekLarge: Random.float(10000, 99999, 2, 2),
rise: Random.float(10, 99),
decline: Random.float(10, 99),
amount: Random.float(99999, 999999, 2, 2),
weekLarge: getRandom({ min: 10000, max: 99999, dec: 2 }),
rise: getRandom({ min: 10000, max: 99999, dec: 0 }),
decline: getRandom({ min: 10000, max: 99999, dec: 0 }),
amount: getRandom({ min: 10000, max: 99999, dec: 2 }),
},
};
export default [
//主控台 卡片数据
{
url: '/api/dashboard/console',
timeout: 1000,
method: 'get',
response: () => {
return resultSuccess(consoleInfo);
},
export default defineMock({
// 主控台数据
'/api/dashboard/console': () => {
return resultSuccess(result);
},
];
});

View File

@@ -1,7 +1,9 @@
import { defineMock } from '@alova/mock';
import { resultSuccess } from '../_util';
import type { ListDate } from '@/api/system/menu';
const menuList = () => {
const result: any[] = [
const result: ListDate[] = [
{
label: 'Dashboard',
key: 'dashboard',
@@ -74,16 +76,11 @@ const menuList = () => {
return result;
};
export default [
{
url: '/api/menu/list',
timeout: 1000,
method: 'get',
response: () => {
const list = menuList();
return resultSuccess({
list,
});
},
export default defineMock({
'/api/menu/list': () => {
const list = menuList();
return resultSuccess({
list,
});
},
];
});

View File

@@ -1,11 +1,13 @@
import { defineMock } from '@alova/mock';
import { faker } from '@faker-js/faker';
import { resultSuccess, doCustomTimes } from '../_util';
import dayjs from 'dayjs';
function getMenuKeys() {
const keys = ['dashboard', 'console', 'workplace', 'basic-form', 'step-form', 'detail'];
const newKeys = [];
doCustomTimes(parseInt(Math.random() * 6), () => {
const key = keys[Math.floor(Math.random() * keys.length)];
newKeys.push(key);
newKeys.push(key as never);
});
return Array.from(new Set(newKeys));
}
@@ -14,32 +16,30 @@ const roleList = (pageSize) => {
const result: any[] = [];
doCustomTimes(pageSize, () => {
result.push({
id: '@integer(10,100)',
name: '@cname()',
explain: '@cname()',
isDefault: '@boolean()',
id: faker.string.numeric(4),
name: faker.person.firstName(),
explain: faker.lorem.sentence({ min: 2, max: 4 }),
isDefault: faker.helpers.arrayElement([true, false]),
menu_keys: getMenuKeys(),
create_date: `@date('yyyy-MM-dd hh:mm:ss')`,
'status|1': ['normal', 'enable', 'disable'],
create_date: dayjs(faker.date.anytime()).format('YYYY-MM-DD HH:mm'),
status: faker.helpers.arrayElement(['normal', 'enable', 'disable']),
});
});
return result;
};
export default [
{
url: '/api/role/list',
timeout: 1000,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 10 } = query;
const list = roleList(Number(pageSize));
return resultSuccess({
page: Number(page),
pageSize: Number(pageSize),
pageCount: 60,
list,
});
},
export default defineMock({
'/api/role/list': ({ query }) => {
const { page = 1, pageSize = 10, name } = query;
const list = roleList(Number(pageSize));
// 并非真实,只是为了模拟搜索结果
const count = name ? 30 : 60;
return resultSuccess({
page: Number(page),
pageSize: Number(pageSize),
pageCount: count,
itemCount: count * Number(pageSize),
list,
});
},
];
});

View File

@@ -1,40 +1,39 @@
import { Random } from 'mockjs';
import { resultSuccess, doCustomTimes } from '../_util';
const tableList = (pageSize) => {
import { defineMock } from '@alova/mock';
import { faker } from '@faker-js/faker';
import { doCustomTimes, resultSuccess } from '../_util';
import dayjs from 'dayjs';
function tableList(pageSize: number) {
const result: any[] = [];
doCustomTimes(pageSize, () => {
result.push({
id: '@integer(10,999999)',
beginTime: '@datetime',
endTime: '@datetime',
address: '@city()',
name: '@cname()',
avatar: Random.image('400x400', Random.color(), Random.color(), Random.first()),
date: `@date('yyyy-MM-dd')`,
time: `@time('HH:mm')`,
'no|100000-10000000': 100000,
'status|1': [true, false],
id: faker.string.numeric(4),
name: faker.person.firstName(),
sex: faker.person.sexType(),
avatar: `https://picsum.photos/200/200?v=${faker.string.numeric(4)}`,
email: faker.internet.email({ firstName: 'admin' }),
city: faker.location.city(),
status: faker.helpers.arrayElement(['close', 'refuse', 'pass']),
type: faker.helpers.arrayElement(['person', 'company']),
// createDate: faker.helpers.arrayElement(dateStrs),
createDate: dayjs(faker.date.anytime()).format('YYYY-MM-DD HH:mm'),
});
});
return result;
};
}
export default [
//表格数据列表
{
url: '/api/table/list',
timeout: 1000,
method: 'get',
response: ({ query }) => {
const { page = 1, pageSize = 10 } = query;
const list = tableList(Number(pageSize));
return resultSuccess({
page: Number(page),
pageSize: Number(pageSize),
pageCount: 60,
list,
});
},
export default defineMock({
// 表格数据列表
'/api/table/list': ({ query }) => {
const { page = 1, pageSize = 10, name } = query;
const list = tableList(Number(pageSize));
// 并非真实,只是为了模拟搜索结果
const count = name ? 30 : 60;
return resultSuccess({
page: Number(page),
pageSize: Number(pageSize),
pageCount: count,
itemCount: count * Number(pageSize),
list,
});
},
];
});

View File

@@ -1,5 +1,6 @@
import Mock from 'mockjs';
import { resultSuccess } from '../_util';
import { defineMock } from '@alova/mock';
const Random = Mock.Random;
@@ -37,23 +38,7 @@ const adminInfo = {
],
};
export default [
{
url: '/api/login',
timeout: 1000,
method: 'post',
response: () => {
return resultSuccess({ token });
},
},
{
url: '/api/admin_info',
timeout: 1000,
method: 'get',
response: () => {
// const token = getRequestToken(request);
// if (!token) return resultError('Invalid token');
return resultSuccess(adminInfo);
},
},
];
export default defineMock({
'[POST]/api/login': () => resultSuccess({ token }),
'/api/admin_info': () => resultSuccess(adminInfo),
});

View File

@@ -1,3 +1,4 @@
import { defineMock } from '@alova/mock';
import { resultSuccess } from '../_util';
const menusList = [
@@ -40,13 +41,6 @@ const menusList = [
},
];
export default [
{
url: '/api/menus',
timeout: 1000,
method: 'get',
response: () => {
return resultSuccess(menusList);
},
},
];
export default defineMock({
'/api/menus': () => resultSuccess(menusList),
});

View File

@@ -1,6 +1,6 @@
{
"name": "naive-ui-admin",
"version": "1.8.0",
"version": "2.1.0",
"author": {
"name": "Ahjung",
"email": "735878602@qq.com",
@@ -8,94 +8,93 @@
},
"private": true,
"scripts": {
"bootstrap": "yarn install",
"serve": "npm run dev",
"bootstrap": "pnpm install",
"serve": "pnpm run dev",
"dev": "vite",
"build": "vite build && esno ./build/script/postBuild.ts",
"build:no-cache": "yarn clean:cache && npm run build",
"report": "cross-env REPORT=true npm run build",
"preview": "npm run build && vite preview",
"build:no-cache": "pnpm clean:cache && pnpm run build",
"report": "cross-env REPORT=true pnpm run build",
"preview": "pnpm run build && vite preview",
"preview:dist": "vite preview",
"clean:cache": "rimraf node_modules/.cache/ && rimraf node_modules/.vite",
"clean:lib": "rimraf node_modules",
"build typecheck": "vuedx-typecheck . && vite build",
"deploy": "gh-pages -d dist",
"lint:eslint": "eslint \"{src,mock}/**/*.{vue,ts,tsx}\" --fix",
"lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,json,tsx,css,less,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/",
"lint:lint-staged": "lint-staged -c ./.husky/lintstagedrc.js",
"lint:pretty": "pretty-quick --staged",
"test prod gzip": "http-server dist --cors --gzip -c-1"
"lint:pretty": "pretty-quick --staged"
},
"dependencies": {
"@vicons/antd": "^0.10.0",
"@vicons/ionicons5": "^0.10.0",
"@vueup/vue-quill": "^1.0.0-beta.8",
"@vueuse/core": "^5.3.0",
"axios": "^0.21.4",
"blueimp-md5": "^2.19.0",
"date-fns": "^2.28.0",
"echarts": "^5.3.2",
"@alova/mock": "^2.0.17",
"@vicons/antd": "^0.12.0",
"@vicons/ionicons5": "^0.12.0",
"@vueup/vue-quill": "^1.2.0",
"@vueuse/core": "^9.13.0",
"alova": "^3.3.4",
"date-fns": "^2.30.0",
"dayjs": "^1.11.18",
"echarts": "^5.6.0",
"element-resize-detector": "^1.2.4",
"lodash": "^4.17.21",
"lodash-es": "^4.17.21",
"mitt": "^2.1.0",
"mockjs": "^1.1.0",
"naive-ui": "^2.28.4",
"pinia": "^2.0.14",
"qs": "^6.10.3",
"vfonts": "^0.1.0",
"vue": "^3.2.33",
"vue-router": "^4.0.15",
"vue-types": "^4.1.1",
"vuedraggable": "^4.1.0"
"naive-ui": "^2.43.1",
"pinia": "^2.3.1",
"qs": "^6.14.0",
"vue": "^3.5.21",
"vue-router": "^4.5.1",
"vue-types": "^4.2.1"
},
"devDependencies": {
"@commitlint/cli": "^12.1.4",
"@commitlint/config-conventional": "^12.1.4",
"@types/lodash": "^4.14.182",
"@types/node": "^15.14.9",
"@typescript-eslint/eslint-plugin": "^4.33.0",
"@typescript-eslint/parser": "^4.33.0",
"@vitejs/plugin-vue": "^1.10.2",
"@vitejs/plugin-vue-jsx": "^1.3.10",
"@vue/compiler-sfc": "^3.2.33",
"@vue/eslint-config-typescript": "^7.0.0",
"autoprefixer": "^10.4.7",
"commitizen": "^4.2.4",
"core-js": "^3.22.5",
"dotenv": "^10.0.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.5.0",
"eslint-define-config": "1.0.9",
"eslint-plugin-jest": "^24.7.0",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-vue": "^7.20.0",
"esno": "^0.7.3",
"gh-pages": "^3.2.3",
"husky": "^6.0.0",
"jest": "^27.5.1",
"less": "^4.1.2",
"less-loader": "^9.1.0",
"lint-staged": "^11.2.6",
"postcss": "^8.4.13",
"prettier": "^2.6.2",
"pretty-quick": "^3.1.3",
"@commitlint/cli": "^17.8.1",
"@commitlint/config-conventional": "^17.8.1",
"@faker-js/faker": "^9.9.0",
"@types/lodash": "^4.17.20",
"@types/lodash-es": "^4.17.12",
"@types/node": "^18.19.126",
"@typescript-eslint/eslint-plugin": "^5.62.0",
"@typescript-eslint/parser": "^5.62.0",
"@vitejs/plugin-vue": "^3.2.0",
"@vitejs/plugin-vue-jsx": "^2.1.1",
"@vue/compiler-sfc": "^3.5.21",
"@vue/eslint-config-typescript": "^11.0.3",
"autoprefixer": "^10.4.21",
"commitizen": "^4.3.1",
"core-js": "^3.45.1",
"cross-env": "^7.0.3",
"dotenv": "^16.6.1",
"eslint": "^8.57.1",
"eslint-config-prettier": "^8.10.2",
"eslint-define-config": "1.12.0",
"eslint-plugin-jest": "^27.9.0",
"eslint-plugin-prettier": "^4.2.5",
"eslint-plugin-vue": "^9.33.0",
"esno": "^4.8.0",
"gh-pages": "^4.0.0",
"husky": "^8.0.3",
"jest": "^29.7.0",
"less": "^4.4.1",
"less-loader": "^11.1.4",
"lint-staged": "^13.3.0",
"postcss": "^8.5.6",
"prettier": "^2.8.8",
"pretty-quick": "^3.3.1",
"rimraf": "^3.0.2",
"stylelint": "^13.13.1",
"stylelint-config-prettier": "^8.0.2",
"stylelint-config-standard": "^22.0.0",
"stylelint-order": "^4.1.0",
"stylelint-scss": "^3.21.0",
"tailwindcss": "^2.2.19",
"typescript": "^4.6.4",
"unplugin-vue-components": "^0.17.21",
"vite": "^2.9.8",
"vite-plugin-compression": "^0.3.6",
"vite-plugin-html": "^2.1.2",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-style-import": "^1.4.1",
"vue-eslint-parser": "^7.11.0"
"stylelint": "^14.16.1",
"stylelint-config-prettier": "^9.0.5",
"stylelint-config-standard": "^29.0.0",
"stylelint-order": "^5.0.0",
"stylelint-scss": "^4.7.0",
"tailwindcss": "^3.4.17",
"typescript": "^4.9.5",
"unplugin-vue-components": "^0.22.12",
"vite": "^5.4.20",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-html": "^3.2.2",
"vite-plugin-style-import": "^2.0.0",
"vue-demi": "^0.13.11",
"vue-draggable-next": "^2.3.0",
"vue-eslint-parser": "^9.4.3",
"vuedraggable": "^4.1.0"
},
"lint-staged": {
"*.{vue,js,ts,tsx}": "eslint --fix"
@@ -125,6 +124,6 @@
},
"homepage": "https://github.com/jekip/naive-ui-admin",
"engines": {
"node": "^12 || >=14"
"node": ">=16"
}
}

13218
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -21,16 +21,16 @@
import { zhCN, dateZhCN, darkTheme } from 'naive-ui';
import { LockScreen } from '@/components/Lockscreen';
import { AppProvider } from '@/components/Application';
import { useLockscreenStore } from '@/store/modules/lockscreen';
import { useScreenLockStore } from '@/store/modules/screenLock.js';
import { useRoute } from 'vue-router';
import { useDesignSettingStore } from '@/store/modules/designSetting';
import { lighten } from '@/utils/index';
const route = useRoute();
const useLockscreen = useLockscreenStore();
const useScreenLock = useScreenLockStore();
const designStore = useDesignSettingStore();
const isLock = computed(() => useLockscreen.isLock);
const lockTime = computed(() => useLockscreen.lockTime);
const isLock = computed(() => useScreenLock.isLocked);
const lockTime = computed(() => useScreenLock.lockTime);
/**
* @type import('naive-ui').GlobalThemeOverrides
@@ -43,6 +43,7 @@
primaryColor: appTheme,
primaryColorHover: lightenStr,
primaryColorPressed: lightenStr,
primaryColorSuppl: appTheme,
},
LoadingBar: {
colorLoading: appTheme,
@@ -52,21 +53,21 @@
const getDarkTheme = computed(() => (designStore.darkTheme ? darkTheme : undefined));
let timer;
let timer: NodeJS.Timer;
const timekeeping = () => {
clearInterval(timer);
if (route.name == 'login' || isLock.value) return;
// 设置不锁屏
useLockscreen.setLock(false);
useScreenLock.setLock(false);
// 重置锁屏时间
useLockscreen.setLockTime();
useScreenLock.setLockTime();
timer = setInterval(() => {
// 锁屏倒计时递减
useLockscreen.setLockTime(lockTime.value - 1);
useScreenLock.setLockTime(lockTime.value - 1);
if (lockTime.value <= 0) {
// 设置锁屏
useLockscreen.setLock(true);
useScreenLock.setLock(true);
return clearInterval(timer);
}
}, 1000);
@@ -80,7 +81,3 @@
document.removeEventListener('mousedown', timekeeping);
});
</script>
<style lang="less">
@import 'styles/index.less';
</style>

View File

@@ -1,9 +1,35 @@
import { http } from '@/utils/http/axios';
import { Alova } from '@/utils/http/alova/index';
export interface TypeVisits {
dayVisits: number;
rise: number;
decline: number;
amount: number;
}
export interface TypeSaleroom {
weekSaleroom: number;
amount: number;
degree: number;
}
export interface TypeOrderLarge {
weekLarge: number;
rise: number;
decline: number;
amount: number;
}
export interface TypeConsole {
visits: TypeVisits;
//销售额
saleroom: TypeSaleroom;
//订单量
orderLarge: TypeOrderLarge;
//成交额度
volume: TypeOrderLarge;
}
//获取主控台信息
export function getConsoleInfo() {
return http.request({
url: '/dashboard/console',
method: 'get',
});
return Alova.Get<TypeConsole>('/dashboard/console');
}

View File

@@ -1,13 +1,20 @@
import { http } from '@/utils/http/axios';
import { Alova } from '@/utils/http/alova/index';
export interface ListDate {
label: string;
key: string;
type: number;
subtitle: string;
openType: number;
auth: string;
path: string;
children?: ListDate[];
}
/**
* @description: 根据用户id获取用户菜单
*/
export function adminMenus() {
return http.request({
url: '/menus',
method: 'GET',
});
return Alova.Get('/menus');
}
/**
@@ -15,9 +22,7 @@ export function adminMenus() {
* @param params
*/
export function getMenuList(params?) {
return http.request({
url: '/menu/list',
method: 'GET',
return Alova.Get<{ list: ListDate[] }>('/menu/list', {
params,
});
}

View File

@@ -1,11 +1,8 @@
import { http } from '@/utils/http/axios';
import { Alova } from '@/utils/http/alova/index';
/**
* @description: 角色列表
*/
export function getRoleList() {
return http.request({
url: '/role/list',
method: 'GET',
});
export function getRoleList(params) {
return Alova.Get('/role/list', { params });
}

View File

@@ -1,24 +1,13 @@
import { http } from '@/utils/http/axios';
export interface BasicResponseModel<T = any> {
code: number;
message: string;
result: T;
}
export interface BasicPageParams {
pageNumber: number;
pageSize: number;
total: number;
}
import { Alova } from '@/utils/http/alova/index';
/**
* @description: 获取用户信息
*/
export function getUserInfo() {
return http.request({
url: '/admin_info',
method: 'get',
return Alova.Get<InResult>('/admin_info', {
meta: {
isReturnNativeResponse: true,
},
});
}
@@ -26,14 +15,15 @@ export function getUserInfo() {
* @description: 用户登录
*/
export function login(params) {
return http.request<BasicResponseModel>(
return Alova.Post<InResult>(
'/login',
{
url: '/login',
method: 'POST',
params,
},
{
isTransformResponse: false,
meta: {
isReturnNativeResponse: true,
},
}
);
}
@@ -42,25 +32,14 @@ export function login(params) {
* @description: 用户修改密码
*/
export function changePassword(params, uid) {
return http.request(
{
url: `/user/u${uid}/changepw`,
method: 'POST',
params,
},
{
isTransformResponse: false,
}
);
return Alova.Post(`/user/u${uid}/changepw`, { params });
}
/**
* @description: 用户登出
*/
export function logout(params) {
return http.request({
url: '/login/logout',
method: 'POST',
return Alova.Post('/login/logout', {
params,
});
}

View File

@@ -1,10 +1,6 @@
import { http } from '@/utils/http/axios';
import { Alova } from '@/utils/http/alova/index';
//获取table
export function getTableList(params) {
return http.request({
url: '/table/list',
method: 'get',
params,
});
return Alova.Get('/table/list', { params });
}

View File

@@ -1,27 +1,16 @@
<template>
<n-loading-bar-provider>
<n-dialog-provider>
<DialogContent />
<n-notification-provider>
<n-message-provider>
<MessageContent />
<slot slot="default"></slot>
</n-message-provider>
</n-notification-provider>
</n-dialog-provider>
</n-loading-bar-provider>
<n-dialog-provider>
<n-notification-provider>
<n-message-provider>
<slot name="default"></slot>
</n-message-provider>
</n-notification-provider>
</n-dialog-provider>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import {
NDialogProvider,
NNotificationProvider,
NMessageProvider,
NLoadingBarProvider,
} from 'naive-ui';
import { MessageContent } from '@/components/MessageContent';
import { DialogContent } from '@/components/DialogContent';
import { NDialogProvider, NNotificationProvider, NMessageProvider } from 'naive-ui';
export default defineComponent({
name: 'Application',
@@ -29,9 +18,6 @@
NDialogProvider,
NNotificationProvider,
NMessageProvider,
NLoadingBarProvider,
MessageContent,
DialogContent,
},
setup() {
return {};

View File

@@ -3,12 +3,14 @@
{{ value }}
</span>
</template>
<script lang="ts">
import { defineComponent, ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
<script lang="ts" setup>
import { ref, computed, watchEffect, unref, onMounted, watch } from 'vue';
import { useTransition, TransitionPresets } from '@vueuse/core';
import { isNumber } from '@/utils/is';
const props = {
defineOptions({ name: 'CountTo' });
const props = defineProps({
startVal: { type: Number, default: 0 },
endVal: { type: Number, default: 2021 },
duration: { type: Number, default: 1500 },
@@ -36,75 +38,72 @@
* Digital animation
*/
transition: { type: String, default: 'linear' },
};
});
export default defineComponent({
name: 'CountTo',
props,
emits: ['onStarted', 'onFinished'],
setup(props, { emit }) {
const source = ref(props.startVal);
const disabled = ref(false);
let outputValue = useTransition(source);
const emit = defineEmits(['onStarted', 'onFinished']);
const value = computed(() => formatNumber(unref(outputValue)));
const source = ref(props.startVal);
const disabled = ref(false);
let outputValue = useTransition(source);
watchEffect(() => {
source.value = props.startVal;
});
const value = computed(() => formatNumber(unref(outputValue)));
watch([() => props.startVal, () => props.endVal], () => {
if (props.autoplay) {
start();
}
});
watchEffect(() => {
source.value = props.startVal;
});
onMounted(() => {
props.autoplay && start();
});
watch([() => props.startVal, () => props.endVal], () => {
if (props.autoplay) {
start();
}
});
function start() {
run();
source.value = props.endVal;
onMounted(() => {
props.autoplay && start();
});
function start() {
run();
source.value = props.endVal;
}
function reset() {
source.value = props.startVal;
run();
}
function run() {
outputValue = useTransition(source, {
disabled,
duration: props.duration,
onFinished: () => emit('onFinished'),
onStarted: () => emit('onStarted'),
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
});
}
function formatNumber(num: number | string) {
if (!num && num !== 0) {
return '';
}
const { decimals, decimal, separator, suffix, prefix } = props;
num = Number(num).toFixed(decimals);
num += '';
const x = num.split('.');
let x1 = x[0];
const x2 = x.length > 1 ? decimal + x[1] : '';
const rgx = /(\d+)(\d{3})/;
if (separator && !isNumber(separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + separator + '$2');
}
}
return prefix + x1 + x2 + suffix;
}
function reset() {
source.value = props.startVal;
run();
}
function run() {
outputValue = useTransition(source, {
disabled,
duration: props.duration,
onFinished: () => emit('onFinished'),
onStarted: () => emit('onStarted'),
...(props.useEasing ? { transition: TransitionPresets[props.transition] } : {}),
});
}
function formatNumber(num: number | string) {
if (!num) {
return '';
}
const { decimals, decimal, separator, suffix, prefix } = props;
num = Number(num).toFixed(decimals);
num += '';
const x = num.split('.');
let x1 = x[0];
const x2 = x.length > 1 ? decimal + x[1] : '';
const rgx = /(\d+)(\d{3})/;
if (separator && !isNumber(separator)) {
while (rgx.test(x1)) {
x1 = x1.replace(rgx, '$1' + separator + '$2');
}
}
return prefix + x1 + x2 + suffix;
}
return { value, start, reset };
},
defineExpose({
reset,
});
</script>

View File

@@ -1,3 +0,0 @@
import DialogContent from './index.vue';
export { DialogContent };

View File

@@ -1,12 +0,0 @@
<template></template>
<script lang="ts">
import { useDialog } from 'naive-ui';
export default {
name: 'DialogContent',
setup() {
//挂载在 window 方便与在js中使用
window['$dialog'] = useDialog();
},
};
</script>

View File

@@ -8,7 +8,7 @@
{{ schema.label }}
<n-tooltip trigger="hover" :style="schema.labelMessageStyle">
<template #trigger>
<n-icon size="18" class="cursor-pointer text-gray-400">
<n-icon size="18" class="text-gray-400 cursor-pointer">
<QuestionCircleOutlined />
</n-icon>
</template>
@@ -90,6 +90,7 @@
v-bind="getSubmitBtnOptions"
@click="handleSubmit"
:loading="loadingSub"
attr-type="submit"
>{{ getProps.submitButtonText }}</n-button
>
<n-button
@@ -138,7 +139,7 @@
import { deepMerge } from '@/utils';
export default defineComponent({
name: 'BasicUpload',
name: 'BasicForm',
components: { DownOutlined, UpOutlined, QuestionCircleOutlined },
props: {
...basicProps,
@@ -230,7 +231,6 @@
});
const { handleFormValues, initDefault } = useFormValues({
getProps,
defaultFormModel,
getSchema,
formModel,

View File

@@ -1,4 +1,4 @@
import { ComponentType } from '/types/index';
import { ComponentType } from './types/index';
/**
* @description: 生成placeholder

View File

@@ -80,6 +80,15 @@ export function useForm(props?: Props): UseFormReturnType {
const form = await getForm();
return form.validate(nameList);
},
setLoading: (value: boolean) => {
loadedRef.value = value;
},
setSchema: async (values) => {
const form = await getForm();
form.setSchema(values);
},
};
return [register, methods];

View File

@@ -32,23 +32,27 @@ export function useFormEvents({
}
// 提交
async function handleSubmit(e?: Event): Promise<void> {
async function handleSubmit(e?: Event): Promise<object | boolean> {
e && e.preventDefault();
loadingSub.value = true;
const { submitFunc } = unref(getProps);
if (submitFunc && isFunction(submitFunc)) {
await submitFunc();
return;
loadingSub.value = false;
return false;
}
const formEl = unref(formElRef);
if (!formEl) return;
if (!formEl) return false;
try {
await validate();
const values = getFieldsValue();
loadingSub.value = false;
emit('submit', formModel);
return true;
} catch (error) {
emit('submit', values);
return values;
} catch (error: any) {
emit('submit', false);
loadingSub.value = false;
console.error(error);
return false;
}
}
@@ -96,6 +100,10 @@ export function useFormEvents({
});
}
function setLoading(value: boolean): void {
loadingSub.value = value;
}
return {
handleSubmit,
validate,
@@ -103,5 +111,6 @@ export function useFormEvents({
getFieldsValue,
clearValidate,
setFieldsValue,
setLoading,
};
}

View File

@@ -41,16 +41,19 @@ export interface FormProps {
submitFunc?: () => Promise<void>;
submitOnReset?: boolean;
baseGridStyle?: CSSProperties;
collapsedRows?: number;
}
export interface FormActionType {
submit: () => Promise<any>;
setProps: (formProps: Partial<FormProps>) => Promise<void>;
setFieldsValue: <T>(values: T) => Promise<void>;
setSchema: (schemaProps: Partial<FormSchema[]>) => Promise<void>;
setFieldsValue: (values: Recordable) => void;
clearValidate: (name?: string | string[]) => Promise<void>;
getFieldsValue: () => Recordable;
resetFields: () => Promise<void>;
validate: (nameList?: any[]) => Promise<any>;
setLoading: (status: boolean) => void;
}
export type RegisterFn = (formInstance: FormActionType) => void;

View File

@@ -1,3 +0,0 @@
import LoadingContent from './index.vue';
export { LoadingContent };

View File

@@ -1,12 +0,0 @@
<template></template>
<script lang="ts">
import { useLoadingBar } from 'naive-ui';
export default {
name: 'LoadingContent',
setup() {
//挂载在 window 方便与在js中使用
window['$loading'] = useLoadingBar();
},
};
</script>

View File

@@ -60,11 +60,11 @@
</template>
</n-input>
<div class="w-full flex" v-if="isLoginError">
<div class="flex w-full" v-if="isLoginError">
<span class="text-red-500">{{ errorMsg }}</span>
</div>
<div class="w-full mt-1 flex justify-around">
<div class="flex justify-around w-full mt-1">
<div><a @click="showLogin = false">返回</a></div>
<div><a @click="goLogin">重新登录</a></div>
<div><a @click="onLogin">进入系统</a></div>
@@ -91,11 +91,11 @@
import { useOnline } from '@/hooks/useOnline';
import { useTime } from '@/hooks/useTime';
import { useBattery } from '@/hooks/useBattery';
import { useLockscreenStore } from '@/store/modules/lockscreen';
import { useUserStore } from '@/store/modules/user';
import { useScreenLockStore } from '@/store/modules/screenLock';
import { UserInfoType, useUserStore } from '@/store/modules/user';
export default defineComponent({
name: 'Lockscreen',
name: 'ScreenLock',
components: {
LockOutlined,
LoadingOutlined,
@@ -106,7 +106,7 @@
recharge,
},
setup() {
const useLockscreen = useLockscreenStore();
const useScreenLock = useScreenLockStore();
const userStore = useUserStore();
// 获取时间
@@ -117,7 +117,7 @@
const route = useRoute();
const { battery, batteryStatus, calcDischargingTime, calcChargingTime } = useBattery();
const userInfo: object = userStore.getUserInfo || {};
const userInfo: UserInfoType = userStore.getUserInfo || {};
const username = userInfo['username'] || '';
const state = reactive({
showLogin: false,
@@ -146,7 +146,7 @@
const { code, message } = await userStore.login(params);
if (code === ResultEnum.SUCCESS) {
onLockLogin(false);
useLockscreen.setLock(false);
useScreenLock.setLock(false);
} else {
state.errorMsg = message;
state.isLoginError = true;
@@ -157,7 +157,7 @@
//重新登录
const goLogin = () => {
onLockLogin(false);
useLockscreen.setLock(false);
useScreenLock.setLock(false);
router.replace({
path: '/login',
query: {

View File

@@ -135,22 +135,6 @@
}
}
@width: ~`Math.round(Math.random() * 100) ` px;
@left: calc(15px + `Math.round(Math.random(70)) `);
each(range(15), {
.xiaoma-@{value} {
height: (@value * 50px);
}
li:nth-child(@{index}) {
top: 50%;
left: @left;
width: @width;
height: @width;
transform: translate(-50%, -50%);
/*animation: moveToTop (Math.random(6) + 3s) ease-in-out -(Math.random(5000) / 1000s) infinite;*/
}
});
@keyframes rotate {
50% {
border-radius: 45% / 42% 38% 58% 49%;

View File

@@ -1,3 +0,0 @@
import MessageContent from './index.vue';
export { MessageContent };

View File

@@ -1,12 +0,0 @@
<template></template>
<script lang="ts">
import { useMessage } from 'naive-ui';
export default {
name: 'MessageContent',
setup() {
//挂载在 window 方便与在js中使用
window['$message'] = useMessage();
},
};
</script>

View File

@@ -21,16 +21,7 @@
</template>
<script lang="ts" setup>
import {
getCurrentInstance,
ref,
nextTick,
unref,
computed,
useAttrs,
defineEmits,
defineProps,
} from 'vue';
import { getCurrentInstance, ref, nextTick, unref, computed, useAttrs } from 'vue';
import { basicProps } from './props';
import startDrag from '@/utils/Drag';
import { deepMerge } from '@/utils';
@@ -41,7 +32,7 @@
const props = defineProps({ ...basicProps });
const emit = defineEmits(['on-close', 'on-ok', 'register']);
const propsRef = ref(<Partial<ModalProps> | null>null);
const propsRef = ref<Partial<ModalProps> | null>(null);
const isModal = ref(false);
const subLoading = ref(false);
@@ -93,7 +84,6 @@
function handleSubmit() {
subLoading.value = true;
console.log(subLoading.value);
emit('on-ok');
}
@@ -108,6 +98,8 @@
if (instance) {
emit('register', modalMethods);
}
defineExpose(modalMethods);
</script>
<style lang="less">

View File

@@ -1,4 +1,4 @@
import { ref, onUnmounted, unref, getCurrentInstance, watch, nextTick } from 'vue';
import { ref, unref, getCurrentInstance, watch } from 'vue';
import { isProdMode } from '@/utils/env';
import { ModalMethods, UseModalReturnType } from '../type';
import { getDynamicProps } from '@/utils';

View File

@@ -2,26 +2,37 @@
<div class="table-toolbar">
<!--顶部左侧区域-->
<div class="flex items-center table-toolbar-left">
<template v-if="title">
<template v-if="props.title">
<div class="table-toolbar-left-title">
{{ title }}
<n-tooltip trigger="hover" v-if="titleTooltip">
{{ props.title }}
<n-tooltip trigger="hover" v-if="props.titleTooltip">
<template #trigger>
<n-icon size="18" class="ml-1 text-gray-400 cursor-pointer">
<QuestionCircleOutlined />
</n-icon>
</template>
{{ titleTooltip }}
{{ props.titleTooltip }}
</n-tooltip>
</div>
</template>
<slot name="tableTitle"></slot>
</div>
<div class="flex items-center table-toolbar-right">
<div class="flex items-center leading-none table-toolbar-right">
<!--顶部右侧区域-->
<slot name="toolbar"></slot>
<!--斑马纹-->
<n-tooltip trigger="hover">
<template #trigger>
<div class="mr-2 table-toolbar-right-icon">
<n-switch v-model:value="isStriped" @update:value="setStriped" />
</div>
</template>
<span>表格斑马纹</span>
</n-tooltip>
<n-divider vertical />
<!--刷新-->
<n-tooltip trigger="hover">
<template #trigger>
@@ -61,6 +72,7 @@
<n-data-table
ref="tableElRef"
v-bind="getBindValues"
:striped="isStriped"
:pagination="pagination"
@update:page="updatePage"
@update:page-size="updatePageSize"
@@ -72,18 +84,8 @@
</div>
</template>
<script lang="ts">
import {
ref,
defineComponent,
reactive,
unref,
toRaw,
computed,
toRefs,
onMounted,
nextTick,
} from 'vue';
<script lang="ts" setup>
import { ref, unref, toRaw, computed, onMounted, nextTick } from 'vue';
import { ReloadOutlined, ColumnHeightOutlined, QuestionCircleOutlined } from '@vicons/antd';
import { createTableContext } from './hooks/useTableContext';
@@ -120,177 +122,150 @@
},
];
export default defineComponent({
components: {
ReloadOutlined,
ColumnHeightOutlined,
ColumnSetting,
QuestionCircleOutlined,
},
props: {
...basicProps,
},
emits: [
'fetch-success',
'fetch-error',
'update:checked-row-keys',
'edit-end',
'edit-cancel',
'edit-row-end',
'edit-change',
],
setup(props, { emit }) {
const deviceHeight = ref(150);
const tableElRef = ref<ComponentRef>(null);
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
let paginationEl: HTMLElement | null;
const emit = defineEmits([
'fetch-success',
'fetch-error',
'update:checked-row-keys',
'edit-end',
'edit-cancel',
'edit-row-end',
'edit-change',
]);
const tableData = ref<Recordable[]>([]);
const innerPropsRef = ref<Partial<BasicTableProps>>();
const props = defineProps({ ...basicProps });
const deviceHeight = ref(150);
const tableElRef = ref<ComponentRef>(null);
const wrapRef = ref<Nullable<HTMLDivElement>>(null);
let paginationEl: HTMLElement | null;
const isStriped = ref(props.striped || false);
const tableData = ref<Recordable[]>([]);
const innerPropsRef = ref<Partial<BasicTableProps>>();
const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
});
const { getLoading, setLoading } = useLoading(getProps);
const { getPaginationInfo, setPagination } = usePagination(getProps);
const { getDataSourceRef, getDataSource, getRowKey, reload } = useDataSource(
getProps,
{
getPaginationInfo,
setPagination,
tableData,
setLoading,
},
emit
);
const { getPageColumns, setColumns, getColumns, getCacheColumns, setCacheColumnsField } =
useColumns(getProps);
const state = reactive({
tableSize: unref(getProps as any).size || 'medium',
isColumnSetting: false,
});
//页码切换
function updatePage(page) {
setPagination({ page: page });
reload();
}
//分页数量切换
function updatePageSize(size) {
setPagination({ page: 1, pageSize: size });
reload();
}
//密度切换
function densitySelect(e) {
state.tableSize = e;
}
//选中行
function updateCheckedRowKeys(rowKeys) {
emit('update:checked-row-keys', rowKeys);
}
//获取表格大小
const getTableSize = computed(() => state.tableSize);
//组装表格信息
const getBindValues = computed(() => {
const tableData = unref(getDataSourceRef);
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
return {
...unref(getProps),
loading: unref(getLoading),
columns: toRaw(unref(getPageColumns)),
rowKey: unref(getRowKey),
data: tableData,
size: unref(getTableSize),
remote: true,
'max-height': maxHeight,
};
});
//获取分页信息
const pagination = computed(() => toRaw(unref(getPaginationInfo)));
function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
}
const tableAction = {
reload,
setColumns,
setLoading,
setProps,
getColumns,
getPageColumns,
getCacheColumns,
setCacheColumnsField,
emit,
};
const getCanResize = computed(() => {
const { canResize } = unref(getProps);
return canResize;
});
async function computeTableHeight() {
const table = unref(tableElRef);
if (!table) return;
if (!unref(getCanResize)) return;
const tableEl: any = table?.$el;
const headEl = tableEl.querySelector('.n-data-table-thead ');
const { bottomIncludeBody } = getViewportOffset(headEl);
const headerH = 64;
let paginationH = 2;
let marginH = 24;
if (!isBoolean(pagination)) {
paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement;
if (paginationEl) {
const offsetHeight = paginationEl.offsetHeight;
paginationH += offsetHeight || 0;
} else {
paginationH += 28;
}
}
let height =
bottomIncludeBody - (headerH + paginationH + marginH + (props.resizeHeightOffset || 0));
const maxHeight = props.maxHeight;
height = maxHeight && maxHeight < height ? maxHeight : height;
deviceHeight.value = height;
}
useWindowSizeFn(computeTableHeight, 280);
onMounted(() => {
nextTick(() => {
computeTableHeight();
});
});
createTableContext({ ...tableAction, wrapRef, getBindValues });
return {
...toRefs(state),
tableElRef,
getBindValues,
getDataSource,
densityOptions,
reload,
densitySelect,
updatePage,
updatePageSize,
pagination,
tableAction,
};
},
const getProps = computed(() => {
return { ...props, ...unref(innerPropsRef) } as BasicTableProps;
});
const tableSize = ref(unref(getProps as any).size || 'medium');
const { getLoading, setLoading } = useLoading(getProps);
const { getPaginationInfo, setPagination } = usePagination(getProps);
const { getDataSourceRef, getDataSource, getRowKey, reload } = useDataSource(
getProps,
{
getPaginationInfo,
setPagination,
tableData,
setLoading,
},
emit
);
const { getPageColumns, setColumns, getColumns, getCacheColumns, setCacheColumnsField } =
useColumns(getProps);
//页码切换
function updatePage(page) {
setPagination({ page: page });
reload();
}
//分页数量切换
function updatePageSize(size) {
setPagination({ page: 1, pageSize: size });
reload();
}
//密度切换
function densitySelect(e) {
tableSize.value = e;
}
//获取表格大小
const getTableSize = computed(() => tableSize.value);
//组装表格信息
const getBindValues = computed(() => {
const tableData = unref(getDataSourceRef);
const maxHeight = tableData.length ? `${unref(deviceHeight)}px` : 'auto';
return {
...unref(getProps),
loading: unref(getLoading),
columns: toRaw(unref(getPageColumns)),
rowKey: unref(getRowKey),
data: tableData,
size: unref(getTableSize),
remote: true,
'max-height': maxHeight,
title: '', // 重置为空 避免绑定到 table 上面
};
});
//获取分页信息
const pagination = computed(() => toRaw(unref(getPaginationInfo)));
function setProps(props: Partial<BasicTableProps>) {
innerPropsRef.value = { ...unref(innerPropsRef), ...props };
}
const setStriped = (value: boolean) => (isStriped.value = value);
const tableAction = {
reload,
setColumns,
setLoading,
setProps,
getColumns,
getDataSource,
getPageColumns,
getCacheColumns,
setCacheColumnsField,
emit,
};
const getCanResize = computed(() => {
const { canResize } = unref(getProps);
return canResize;
});
async function computeTableHeight() {
const table = unref(tableElRef);
if (!table) return;
if (!unref(getCanResize)) return;
const tableEl: any = table?.$el;
const headEl = tableEl.querySelector('.n-data-table-thead ');
const { bottomIncludeBody } = getViewportOffset(headEl);
const headerH = 64;
let paginationH = 2;
let marginH = 24;
if (!isBoolean(unref(pagination))) {
paginationEl = tableEl.querySelector('.n-data-table__pagination') as HTMLElement;
if (paginationEl) {
const offsetHeight = paginationEl.offsetHeight;
paginationH += offsetHeight || 0;
} else {
paginationH += 28;
}
}
let height =
bottomIncludeBody - (headerH + paginationH + marginH + (props.resizeHeightOffset || 0));
const maxHeight = props.maxHeight;
height = maxHeight && maxHeight < height ? maxHeight : height;
deviceHeight.value = height;
}
useWindowSizeFn(computeTableHeight, 280);
onMounted(() => {
nextTick(() => {
computeTableHeight();
});
});
createTableContext({ ...tableAction, wrapRef, getBindValues });
defineExpose(tableAction);
</script>
<style lang="less" scoped>
.table-toolbar {

View File

@@ -2,7 +2,12 @@
<div class="tableAction">
<div class="flex items-center justify-center">
<template v-for="(action, index) in getActions" :key="`${index}-${action.label}`">
<n-button v-bind="action" class="mx-2">{{ action.label }}</n-button>
<n-button v-bind="action" class="mx-1">
{{ action.label }}
<template #icon v-if="action.hasOwnProperty('icon')">
<n-icon :component="action.icon" />
</template>
</n-button>
</template>
<n-dropdown
v-if="dropDownActions && getDropdownList.length"
@@ -11,7 +16,7 @@
@select="select"
>
<slot name="more"></slot>
<n-button v-bind="getMoreProps" class="mx-2" v-if="!$slots.more" icon-placement="right">
<n-button v-bind="getMoreProps" class="mx-1" v-if="!$slots.more" icon-placement="right">
<div class="flex items-center">
<span>更多</span>
<n-icon size="14" class="ml-1">
@@ -75,7 +80,7 @@
const getDropdownList = computed(() => {
return (toRaw(props.dropDownActions) || [])
.filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
return hasPermission(action.auth as string[]) && isIfShow(action);
})
.map((action) => {
const { popConfirm } = action;
@@ -108,7 +113,7 @@
const getActions = computed(() => {
return (toRaw(props.actions) || [])
.filter((action) => {
return hasPermission(action.auth) && isIfShow(action);
return hasPermission(action.auth as string[]) && isIfShow(action);
})
.map((action) => {
const { popConfirm } = action;

View File

@@ -1,12 +1,6 @@
<template>
<div class="editable-cell">
<div v-show="!isEdit" class="editable-cell-content" @click="handleEdit">
{{ getValues }}
<n-icon class="edit-icon" v-if="!column.editRow">
<FormOutlined />
</n-icon>
</div>
<div class="flex editable-cell-content" v-show="isEdit" v-click-outside="onClickOutside">
<div class="flex editable-cell-content" v-if="isEdit" v-click-outside="onClickOutside">
<div class="editable-cell-content-comp">
<CellComponent
v-bind="getComponentProps"
@@ -17,18 +11,24 @@
:class="getWrapperClass"
ref="elRef"
@options-change="handleOptionsChange"
@pressEnter="handleEnter"
@press-enter="handleEnter"
/>
</div>
<div class="editable-cell-action" v-if="!getRowEditable">
<n-icon class="mx-2 cursor-pointer">
<n-icon class="mx-2 cursor-pointer" title="保存">
<CheckOutlined @click="handleSubmit" />
</n-icon>
<n-icon class="mx-2 cursor-pointer">
<n-icon class="mx-2 cursor-pointer" title="取消">
<CloseOutlined @click="handleCancel" />
</n-icon>
</div>
</div>
<div v-else class="flex items-center editable-cell-content" @click="handleEdit">
{{ getValues }}
<n-icon class="ml-1 edit-icon" v-if="!column.editRow">
<FormOutlined />
</n-icon>
</div>
</div>
</template>
<script lang="ts">
@@ -51,7 +51,6 @@
import { EventEnum } from '@/components/Table/src/componentMap';
import { parseISO, format } from 'date-fns';
import { Fn, LabelValueOptions } from '/#/index';
export default defineComponent({
name: 'EditableCell',

View File

@@ -101,7 +101,7 @@
VerticalRightOutlined,
VerticalLeftOutlined,
} from '@vicons/antd';
import Draggable from 'vuedraggable/src/vuedraggable';
import Draggable from 'vuedraggable';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
interface Options {

View File

@@ -60,7 +60,7 @@ export function useColumns(propsRef: ComputedRef<BasicTableProps>) {
const title: any = column.title;
column.title = () => {
return renderTooltip(
h('span', {}, [
h('div', { class: 'flex items-center' }, [
h('span', { style: { 'margin-right': '5px' } }, title),
h(
NIcon,

View File

@@ -1,7 +1,7 @@
import { ref, ComputedRef, unref, computed, onMounted, watchEffect, watch } from 'vue';
import type { BasicTableProps } from '../types/table';
import type { PaginationProps } from '../types/pagination';
import { isBoolean } from '@/utils/is';
import { isBoolean, isFunction, isArray } from '@/utils/is';
import { APISETTING } from '../const';
export function useDataSource(
@@ -46,14 +46,14 @@ export function useDataSource(
async function fetch(opt?) {
try {
setLoading(true);
const { request, pagination }: any = unref(propsRef);
const { request, pagination, beforeRequest, afterRequest }: any = unref(propsRef);
if (!request) return;
//组装分页信息
const pageField = APISETTING.pageField;
const sizeField = APISETTING.sizeField;
const totalField = APISETTING.totalField;
const listField = APISETTING.listField;
const itemCount = APISETTING.countField;
let pageParams = {};
const { page = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps;
@@ -64,32 +64,45 @@ export function useDataSource(
pageParams[sizeField] = pageSize;
}
const params = {
let params = {
...pageParams,
...opt,
};
if (beforeRequest && isFunction(beforeRequest)) {
// The params parameter can be modified by outsiders
params = (await beforeRequest(params)) || params;
}
const res = await request(params);
const resultTotal = res[totalField] || 0;
const resultTotal = res[totalField];
const currentPage = res[pageField];
const total = res[itemCount];
const results = res[listField] ? res[listField] : [];
// 如果数据异常,需获取正确的页码再次执行
if (resultTotal) {
if (page > resultTotal) {
const currentTotalPage = Math.ceil(total / pageSize);
if (page > currentTotalPage) {
setPagination({
[pageField]: resultTotal,
page: currentTotalPage,
itemCount: total,
});
fetch(opt);
return await fetch(opt);
}
}
const resultInfo = res[listField] ? res[listField] : [];
let resultInfo = res[listField] ? res[listField] : [];
if (afterRequest && isFunction(afterRequest)) {
// can modify the data returned by the interface for processing
resultInfo = (await afterRequest(resultInfo)) || resultInfo;
}
dataSourceRef.value = resultInfo;
setPagination({
[pageField]: currentPage,
[totalField]: resultTotal,
page: currentPage,
pageCount: resultTotal,
itemCount: total,
});
if (opt && opt[pageField]) {
setPagination({
[pageField]: opt[pageField] || 1,
page: opt[pageField] || 1,
});
}
emit('fetch-success', {
@@ -100,9 +113,9 @@ export function useDataSource(
console.error(error);
emit('fetch-error', error);
dataSourceRef.value = [];
// setPagination({
// pageCount: 0,
// });
setPagination({
pageCount: 0,
});
} finally {
setLoading(false);
}

View File

@@ -1,28 +1,40 @@
import type { PaginationProps } from '../types/pagination';
import type { BasicTableProps } from '../types/table';
import { computed, unref, ref, ComputedRef } from 'vue';
import { computed, unref, ref, ComputedRef, watch } from 'vue';
import { isBoolean } from '@/utils/is';
import { APISETTING, DEFAULTPAGESIZE, PAGESIZES } from '../const';
import { DEFAULTPAGESIZE, PAGESIZES } from '../const';
export function usePagination(refProps: ComputedRef<BasicTableProps>) {
const configRef = ref<PaginationProps>({});
const show = ref(true);
watch(
() => unref(refProps).pagination,
(pagination) => {
if (!isBoolean(pagination) && pagination) {
configRef.value = {
...unref(configRef),
...(pagination ?? {}),
};
}
}
);
const getPaginationInfo = computed((): PaginationProps | boolean => {
const { pagination } = unref(refProps);
if (!unref(show) || (isBoolean(pagination) && !pagination)) {
return false;
}
const { totalField } = APISETTING;
return {
pageSize: DEFAULTPAGESIZE,
pageSizes: PAGESIZES,
page: 1, //当前页
pageSize: DEFAULTPAGESIZE, //分页大小
pageSizes: PAGESIZES, // 每页条数
showSizePicker: true,
showQuickJumper: true,
prefix: (pagingInfo) => `${pagingInfo.itemCount}`, // 不需要可以通过 pagination 重置或者删除
...(isBoolean(pagination) ? {} : pagination),
...unref(configRef),
pageCount: unref(configRef)[totalField],
};
});

View File

@@ -25,10 +25,18 @@ export const basicProps = {
default: () => [],
required: true,
},
beforeRequest: {
type: Function as PropType<(...arg: any[]) => void | Promise<any>>,
default: null,
},
request: {
type: Function as PropType<(...arg: any[]) => Promise<any>>,
default: null,
},
afterRequest: {
type: Function as PropType<(...arg: any[]) => void | Promise<any>>,
default: null,
},
rowKey: {
type: [String, Function] as PropType<string | ((record) => string)>,
default: undefined,
@@ -48,4 +56,5 @@ export const basicProps = {
},
canResize: propTypes.bool.def(true),
resizeHeightOffset: propTypes.number.def(0),
striped: propTypes.bool.def(false),
};

View File

@@ -5,4 +5,5 @@ export type ComponentType =
| 'NCheckbox'
| 'NSwitch'
| 'NDatePicker'
| 'NTimePicker';
| 'NTimePicker'
| 'NCascader';

View File

@@ -1,8 +1,10 @@
export interface PaginationProps {
page?: number;
pageCount?: number;
pageSize?: number;
pageSizes?: number[];
showSizePicker?: boolean;
showQuickJumper?: boolean;
page?: number; //受控模式下的当前页
itemCount?: number; //总条数
pageCount?: number; //总页数
pageSize?: number; //受控模式下的分页大小
pageSizes?: number[]; //每页条数, 可自定义
showSizePicker?: boolean; //是否显示每页条数的选择器
showQuickJumper?: boolean; //是否显示快速跳转
prefix?: any; //分页前缀
}

View File

@@ -1,6 +1,6 @@
import type { TableBaseColumn } from 'naive-ui/lib/data-table/src/interface';
import type { InternalRowData, TableBaseColumn } from 'naive-ui/lib/data-table/src/interface';
import { ComponentType } from './componentType';
export interface BasicColumn extends TableBaseColumn {
export interface BasicColumn<T = InternalRowData> extends TableBaseColumn<T> {
//编辑表格
edit?: boolean;
editRow?: boolean;
@@ -34,4 +34,5 @@ export interface BasicTableProps {
actionColumn: any[];
canResize: boolean;
resizeHeightOffset: number;
loading: boolean;
}

View File

@@ -1,10 +1,13 @@
import { NButton } from 'naive-ui';
import type { Component } from 'vue';
import { PermissionsEnum } from '@/enums/permissionsEnum';
export interface ActionItem extends NButton.props {
export interface ActionItem extends Partial<InstanceType<typeof NButton>> {
onClick?: Fn;
label?: string;
color?: 'success' | 'error' | 'warning';
icon?: string;
type?: 'success' | 'error' | 'warning' | 'info' | 'primary' | 'default';
// 设定 color 后会覆盖 type 的样式
color?: string;
icon?: Component;
popConfirm?: PopConfirm;
disabled?: boolean;
divider?: boolean;
@@ -20,5 +23,5 @@ export interface PopConfirm {
cancelText?: string;
confirm: Fn;
cancel?: Fn;
icon?: string;
icon?: Component;
}

View File

@@ -31,6 +31,7 @@
v-if="imgList.length < maxNumber"
>
<n-upload
class="w-auto"
v-bind="$props"
:file-list-style="{ display: 'none' }"
@before-upload="beforeUpload"
@@ -109,10 +110,11 @@
watch(
() => props.value,
() => {
imgList.value = props.value.map((item) => {
state.imgList = props.value.map((item) => {
return getImgUrl(item);
});
}
},
{ immediate: true }
);
//预览
@@ -236,6 +238,7 @@
&-info {
position: relative;
height: 100%;
width: 100%;
padding: 0;
overflow: hidden;

View File

@@ -0,0 +1,9 @@
import logoImage from '@/assets/images/logo.png';
import loginImage from '@/assets/images/account-logo.png';
export const websiteConfig = Object.freeze({
title: 'NaiveUiAdmin',
logo: logoImage,
loginImage: loginImage,
loginDesc: 'Naive Ui Admin 中后台前端/设计解决方案',
});

34
src/directives/copy.ts Normal file
View File

@@ -0,0 +1,34 @@
/**
* v-copy
* 复制某个值至剪贴板
* 接收参数string类型/Ref<string>类型/Reactive<string>类型
*/
import type { Directive, DirectiveBinding } from 'vue';
interface ElType extends HTMLElement {
copyData: string | number;
__handleClick__: any;
}
const copy: Directive = {
mounted(el: ElType, binding: DirectiveBinding) {
el.copyData = binding.value;
el.addEventListener('click', handleClick);
},
updated(el: ElType, binding: DirectiveBinding) {
el.copyData = binding.value;
},
beforeUnmount(el: ElType) {
el.removeEventListener('click', el.__handleClick__);
},
};
function handleClick(this: any) {
const input = document.createElement('input');
input.value = this.copyData.toLocaleString();
document.body.appendChild(input);
input.select();
document.execCommand('Copy');
document.body.removeChild(input);
console.log('复制成功', this.copyData);
}
export default copy;

View File

@@ -0,0 +1,31 @@
/**
* v-debounce
* 按钮防抖指令可自行扩展至input
* 接收参数function类型
*/
import type { Directive, DirectiveBinding } from 'vue';
interface ElType extends HTMLElement {
__handleClick__: () => any;
}
const debounce: Directive = {
mounted(el: ElType, binding: DirectiveBinding) {
if (typeof binding.value !== 'function') {
throw 'callback must be a function';
}
let timer: NodeJS.Timeout | null = null;
el.__handleClick__ = function () {
if (timer) {
clearInterval(timer);
}
timer = setTimeout(() => {
binding.value();
}, 500);
};
el.addEventListener('click', el.__handleClick__);
},
beforeUnmount(el: ElType) {
el.removeEventListener('click', el.__handleClick__);
},
};
export default debounce;

View File

@@ -0,0 +1,49 @@
/*
需求:实现一个拖拽指令,可在父元素区域任意拖拽元素。
思路:
1、设置需要拖拽的元素为absolute其父元素为relative。
2、鼠标按下(onmousedown)时记录目标元素当前的 left 和 top 值。
3、鼠标移动(onmousemove)时计算每次移动的横向距离和纵向距离的变化值,并改变元素的 left 和 top 值
4、鼠标松开(onmouseup)时完成一次拖拽
使用:在 Dom 上加上 v-draggable 即可
<div class="dialog-model" v-draggable></div>
*/
import type { Directive } from 'vue';
interface ElType extends HTMLElement {
parentNode: any;
}
const draggable: Directive = {
mounted: function (el: ElType) {
el.style.cursor = 'move';
el.style.position = 'absolute';
el.onmousedown = function (e) {
const disX = e.pageX - el.offsetLeft;
const disY = e.pageY - el.offsetTop;
document.onmousemove = function (e) {
let x = e.pageX - disX;
let y = e.pageY - disY;
const maxX = el.parentNode.offsetWidth - el.offsetWidth;
const maxY = el.parentNode.offsetHeight - el.offsetHeight;
if (x < 0) {
x = 0;
} else if (x > maxX) {
x = maxX;
}
if (y < 0) {
y = 0;
} else if (y > maxY) {
y = maxY;
}
el.style.left = x + 'px';
el.style.top = y + 'px';
};
document.onmouseup = function () {
document.onmousemove = document.onmouseup = null;
};
};
},
};
export default draggable;

View File

@@ -0,0 +1,49 @@
/**
* v-longpress
* 长按指令,长按时触发事件
*/
import type { Directive, DirectiveBinding } from 'vue';
const directive: Directive = {
mounted(el: HTMLElement, binding: DirectiveBinding) {
if (typeof binding.value !== 'function') {
throw 'callback must be a function';
}
// 定义变量
let pressTimer: any = null;
// 创建计时器( 2秒后执行函数
const start = (e: any) => {
if (e.button) {
if (e.type === 'click' && e.button !== 0) {
return;
}
}
if (pressTimer === null) {
pressTimer = setTimeout(() => {
handler(e);
}, 1000);
}
};
// 取消计时器
const cancel = () => {
if (pressTimer !== null) {
clearTimeout(pressTimer);
pressTimer = null;
}
};
// 运行函数
const handler = (e: MouseEvent | TouchEvent) => {
binding.value(e);
};
// 添加事件监听器
el.addEventListener('mousedown', start);
el.addEventListener('touchstart', start);
// 取消计时器
el.addEventListener('click', cancel);
el.addEventListener('mouseout', cancel);
el.addEventListener('touchend', cancel);
el.addEventListener('touchcancel', cancel);
},
};
export default directive;

View File

@@ -0,0 +1,41 @@
/*
需求:防止按钮在短时间内被多次点击,使用节流函数限制规定时间内只能点击一次。
思路:
1、第一次点击立即调用方法并禁用按钮等延迟结束再次激活按钮
2、将需要触发的方法绑定在指令上
使用:给 Dom 加上 v-throttle 及回调函数即可
<button v-throttle="debounceClick">节流提交</button>
*/
import type { Directive, DirectiveBinding } from 'vue';
interface ElType extends HTMLElement {
__handleClick__: () => any;
disabled: boolean;
}
const throttle: Directive = {
mounted(el: ElType, binding: DirectiveBinding) {
if (typeof binding.value !== 'function') {
throw 'callback must be a function';
}
let timer: NodeJS.Timeout | null = null;
el.__handleClick__ = function () {
if (timer) {
clearTimeout(timer);
}
if (!el.disabled) {
el.disabled = true;
binding.value();
timer = setTimeout(() => {
el.disabled = false;
}, 1000);
}
};
el.addEventListener('click', el.__handleClick__);
},
beforeUnmount(el: ElType) {
el.removeEventListener('click', el.__handleClick__);
},
};
export default throttle;

View File

@@ -1,3 +1,3 @@
import { useAsync } from './use-async';
import { useAsync } from './useAsync';
export { useAsync };

View File

@@ -1,8 +1,9 @@
import type { GlobConfig } from '/#/config';
import type { GlobConfig, LocalConfig } from '/#/config';
import { warn } from '@/utils/log';
import { getAppEnvConfig } from '@/utils/env';
import { warn } from '@/utils/log';
// 这里的 useGlobSetting 用于获取全局配置,以下环境变量 带 VITE_GLOB_开头 会打包到 app.config 中去
export const useGlobSetting = (): Readonly<GlobConfig> => {
const {
VITE_GLOB_APP_TITLE,
@@ -10,8 +11,7 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
VITE_GLOB_APP_SHORT_NAME,
VITE_GLOB_API_URL_PREFIX,
VITE_GLOB_UPLOAD_URL,
VITE_GLOB_PROD_MOCK,
VITE_GLOB_IMG_URL,
VITE_GLOB_FILE_URL,
} = getAppEnvConfig();
if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
@@ -21,14 +21,25 @@ export const useGlobSetting = (): Readonly<GlobConfig> => {
}
// Take global configuration
const glob: Readonly<GlobConfig> = {
return {
title: VITE_GLOB_APP_TITLE,
apiUrl: VITE_GLOB_API_URL,
shortName: VITE_GLOB_APP_SHORT_NAME,
urlPrefix: VITE_GLOB_API_URL_PREFIX,
uploadUrl: VITE_GLOB_UPLOAD_URL,
prodMock: VITE_GLOB_PROD_MOCK,
imgUrl: VITE_GLOB_IMG_URL,
fileUrl: VITE_GLOB_FILE_URL,
};
};
// 这里的 useLocalSetting 用于获取本地配置,以下环境变量不会打包到 app.config 中去
export const useLocalSetting = (): Readonly<LocalConfig> => {
const { VITE_USE_MOCK, VITE_LOGGER_MOCK } = import.meta.env;
function strToBoolean(val): boolean {
return val === 'true';
}
return {
useMock: strToBoolean(VITE_USE_MOCK),
loggerMock: strToBoolean(VITE_LOGGER_MOCK),
};
return glob as Readonly<GlobConfig>;
};

View File

@@ -4,39 +4,39 @@ import { useProjectSettingStore } from '@/store/modules/projectSetting';
export function useProjectSetting() {
const projectStore = useProjectSettingStore();
const getNavMode = computed(() => projectStore.navMode);
const navMode = computed(() => projectStore.navMode);
const getNavTheme = computed(() => projectStore.navTheme);
const navTheme = computed(() => projectStore.navTheme);
const getIsMobile = computed(() => projectStore.isMobile);
const isMobile = computed(() => projectStore.isMobile);
const getHeaderSetting = computed(() => projectStore.headerSetting);
const headerSetting = computed(() => projectStore.headerSetting);
const getMultiTabsSetting = computed(() => projectStore.multiTabsSetting);
const multiTabsSetting = computed(() => projectStore.multiTabsSetting);
const getMenuSetting = computed(() => projectStore.menuSetting);
const menuSetting = computed(() => projectStore.menuSetting);
const getCrumbsSetting = computed(() => projectStore.crumbsSetting);
const crumbsSetting = computed(() => projectStore.crumbsSetting);
const getPermissionMode = computed(() => projectStore.permissionMode);
const permissionMode = computed(() => projectStore.permissionMode);
const getShowFooter = computed(() => projectStore.showFooter);
const showFooter = computed(() => projectStore.showFooter);
const getIsPageAnimate = computed(() => projectStore.isPageAnimate);
const isPageAnimate = computed(() => projectStore.isPageAnimate);
const getPageAnimateType = computed(() => projectStore.pageAnimateType);
const pageAnimateType = computed(() => projectStore.pageAnimateType);
return {
getNavMode,
getNavTheme,
getIsMobile,
getHeaderSetting,
getMultiTabsSetting,
getMenuSetting,
getCrumbsSetting,
getPermissionMode,
getShowFooter,
getIsPageAnimate,
getPageAnimateType,
navMode,
navTheme,
isMobile,
headerSetting,
multiTabsSetting,
menuSetting,
crumbsSetting,
permissionMode,
showFooter,
isPageAnimate,
pageAnimateType,
};
}

View File

@@ -1,5 +1,5 @@
import { ref, onMounted, onUnmounted } from 'vue';
import { debounce } from 'lodash';
import { debounce } from 'lodash-es';
/**
* description: 获取页面宽度

View File

@@ -10,23 +10,27 @@ import { useBreakpoint } from '@/hooks/event/useBreakpoint';
import echarts from '@/utils/lib/echarts';
// import { useRootSetting } from '@/hooks/setting/useRootSetting';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
export function useECharts(
elRef: Ref<HTMLDivElement>,
theme: 'light' | 'dark' | 'default' = 'light'
theme: 'light' | 'dark' | 'default' = 'default'
) {
// const { getDarkMode } = useRootSetting();
const getDarkMode = 'light';
const { getDarkTheme: getSysDarkTheme } = useDesignSetting();
const getDarkTheme = computed(() => {
const sysTheme: string = getSysDarkTheme.value ? 'dark' : 'light';
return theme === 'default' ? sysTheme : theme;
});
let chartInstance: echarts.ECharts | null = null;
let resizeFn: Fn = resize;
const cacheOptions = ref<EChartsOption>({});
const cacheOptions = ref({});
let removeResizeFn: Fn = () => {};
resizeFn = useDebounceFn(resize, 200);
const getOptions = computed((): EChartsOption => {
if (getDarkMode !== 'dark') {
if (getDarkTheme.value !== 'dark') {
return cacheOptions.value;
}
return {
@@ -67,7 +71,7 @@ export function useECharts(
nextTick(() => {
useTimeoutFn(() => {
if (!chartInstance) {
initCharts(getDarkMode.value as 'default');
initCharts(getDarkTheme.value as 'default');
if (!chartInstance) return;
}
@@ -83,7 +87,7 @@ export function useECharts(
}
watch(
() => getDarkMode.value,
() => getDarkTheme.value,
(theme) => {
if (chartInstance) {
chartInstance.dispose();
@@ -93,18 +97,20 @@ export function useECharts(
}
);
tryOnUnmounted(() => {
tryOnUnmounted(disposeInstance);
function getInstance(): echarts.ECharts | null {
if (!chartInstance) {
initCharts(getDarkTheme.value as 'default');
}
return chartInstance;
}
function disposeInstance() {
if (!chartInstance) return;
removeResizeFn();
chartInstance.dispose();
chartInstance = null;
});
function getInstance(): echarts.ECharts | null {
if (!chartInstance) {
initCharts(getDarkMode.value as 'default');
}
return chartInstance;
}
return {
@@ -112,5 +118,6 @@ export function useECharts(
resize,
echarts,
getInstance,
disposeInstance,
};
}

View File

@@ -206,7 +206,7 @@
<n-divider title-placement="center">动画</n-divider>
<div class="drawer-setting-item">
<div class="drawer-setting-item-title"> 用动画 </div>
<div class="drawer-setting-item-title"> 用动画 </div>
<div class="drawer-setting-item-action">
<n-switch v-model:value="settingStore.isPageAnimate" />
</div>
@@ -259,8 +259,7 @@
title: props.title,
isDrawer: false,
placement: 'right',
alertText:
'该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置,建议在生产环境关闭该布局预览功能。',
alertText: '该功能主要实时预览各种布局效果,更多完整配置在 projectSetting.ts 中设置',
appThemeList: designStore.appThemeList,
});

View File

@@ -6,11 +6,11 @@
v-if="navMode === 'horizontal' || (navMode === 'horizontal-mix' && mixMenu)"
>
<div class="logo" v-if="navMode === 'horizontal'">
<img src="~@/assets/images/logo.png" alt="" />
<h2 v-show="!collapsed" class="title">NaiveUiAdmin</h2>
<img :src="websiteConfig.logo" alt="" />
<h2 v-show="!collapsed" class="title">{{ websiteConfig.title }}</h2>
</div>
<AsideMenu
v-model:collapsed="collapsed"
:collapsed="collapsed"
v-model:location="getMenuLocation"
:inverted="getInverted"
mode="horizontal"
@@ -21,7 +21,7 @@
<!-- 菜单收起 -->
<div
class="ml-1 layout-header-trigger layout-header-trigger-min"
@click="() => $emit('update:collapsed', !collapsed)"
@click="handleMenuCollapsed"
>
<n-icon size="18" v-if="collapsed">
<MenuUnfoldOutlined />
@@ -42,8 +42,11 @@
</div>
<!-- 面包屑 -->
<n-breadcrumb v-if="crumbsSetting.show">
<template v-for="routeItem in breadcrumbList" :key="routeItem.name">
<n-breadcrumb-item>
<template
v-for="routeItem in breadcrumbList"
:key="routeItem.name === RedirectName ? void 0 : routeItem.name"
>
<n-breadcrumb-item v-if="routeItem.meta.title">
<n-dropdown
v-if="routeItem.children.length"
:options="routeItem.children"
@@ -72,7 +75,7 @@
<div
class="layout-header-trigger layout-header-trigger-min"
v-for="item in iconList"
:key="item.icon.name"
:key="item.icon"
>
<n-tooltip placement="bottom">
<template #trigger>
@@ -98,12 +101,13 @@
<div class="layout-header-trigger layout-header-trigger-min">
<n-dropdown trigger="hover" @select="avatarSelect" :options="avatarOptions">
<div class="avatar">
<n-avatar round>
{{ username }}
<n-avatar :src="websiteConfig.logo">
<template #icon>
<UserOutlined />
</template>
</n-avatar>
<n-divider vertical />
<span>{{ username }}</span>
</div>
</n-dropdown>
</div>
@@ -125,16 +129,18 @@
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs, ref, computed, unref } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import components from './components';
import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
import { TABS_ROUTES } from '@/store/mutation-types';
import { useUserStore } from '@/store/modules/user';
import { useLockscreenStore } from '@/store/modules/lockscreen';
import ProjectSetting from './ProjectSetting.vue';
import { AsideMenu } from '@/layout/components/Menu';
import { websiteConfig } from '@/config/website.config';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
import { AsideMenu } from '@/layout/components/Menu';
import { RedirectName } from '@/router/constant';
import { useScreenLockStore } from '@/store/modules/screenLock';
import { useUserStore } from '@/store/modules/user';
import { TABS_ROUTES } from '@/store/mutation-types';
import { NDialogProvider, useDialog, useMessage } from 'naive-ui';
import { computed, defineComponent, reactive, ref, toRefs, unref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import components from './components';
import ProjectSetting from './ProjectSetting.vue';
export default defineComponent({
name: 'PageHeader',
@@ -147,39 +153,38 @@
type: Boolean,
},
},
setup(props) {
emits: ['update:collapsed'],
setup(props, { emit }) {
const userStore = useUserStore();
const useLockscreen = useLockscreenStore();
const useLockscreen = useScreenLockStore();
const message = useMessage();
const dialog = useDialog();
const { getNavMode, getNavTheme, getHeaderSetting, getMenuSetting, getCrumbsSetting } =
useProjectSetting();
const { username } = userStore?.info || {};
const { navMode, navTheme, headerSetting, menuSetting, crumbsSetting } = useProjectSetting();
const drawerSetting = ref();
const state = reactive({
username: username || '',
username: userStore?.info?.username ?? '',
fullscreenIcon: 'FullscreenOutlined',
navMode: getNavMode,
navTheme: getNavTheme,
headerSetting: getHeaderSetting,
crumbsSetting: getCrumbsSetting,
navMode,
navTheme,
headerSetting,
crumbsSetting,
});
const getInverted = computed(() => {
const navTheme = unref(getNavTheme);
return ['light', 'header-dark'].includes(navTheme) ? props.inverted : !props.inverted;
return ['light', 'header-dark'].includes(unref(navTheme))
? props.inverted
: !props.inverted;
});
const mixMenu = computed(() => {
return unref(getMenuSetting).mixMenu;
return unref(menuSetting).mixMenu;
});
const getChangeStyle = computed(() => {
const { collapsed } = props;
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
const { minMenuWidth, menuWidth } = unref(menuSetting);
return {
left: collapsed ? `${minMenuWidth}px` : `${menuWidth}px`,
width: `calc(100% - ${collapsed ? `${minMenuWidth}px` : `${menuWidth}px`})`,
@@ -319,6 +324,10 @@
openDrawer();
}
function handleMenuCollapsed() {
emit('update:collapsed', !props.collapsed);
}
return {
...toRefs(state),
iconList,
@@ -336,6 +345,9 @@
getInverted,
getMenuLocation,
mixMenu,
websiteConfig,
handleMenuCollapsed,
RedirectName,
};
},
});
@@ -347,7 +359,7 @@
justify-content: space-between;
align-items: center;
padding: 0;
height: @header-height;
height: 64px;
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
transition: all 0.2s ease-in-out;
width: 100%;

View File

@@ -1,11 +1,12 @@
<template>
<div class="logo">
<img src="~@/assets/images/logo.png" alt="" :class="{ 'mr-2': !collapsed }" />
<h2 v-show="!collapsed" class="title">NaiveUiAdmin</h2>
<img :src="websiteConfig.logo" alt="" :class="{ 'mr-2': !collapsed }" />
<h2 v-show="!collapsed" class="title">{{ websiteConfig.title }}</h2>
</div>
</template>
<script>
<script lang="ts">
import { websiteConfig } from '@/config/website.config';
export default {
name: 'Index',
props: {
@@ -13,6 +14,11 @@
type: Boolean,
},
},
data() {
return {
websiteConfig,
};
},
};
</script>
@@ -32,7 +38,7 @@
}
.title {
margin-bottom: 0;
margin: 0;
}
}
</style>

View File

@@ -1,12 +1,20 @@
<template>
<RouterView>
<template #default="{ Component, route }">
<transition :name="getTransitionName" mode="out-in" appear>
<keep-alive v-if="keepAliveComponents" :include="keepAliveComponents">
<template v-if="mode === 'production'">
<transition :name="getTransitionName" mode="out-in" appear>
<keep-alive v-if="keepAliveComponents.length" :include="keepAliveComponents">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
<component v-else :is="Component" :key="route.fullPath" />
</transition>
</template>
<template v-else>
<keep-alive v-if="keepAliveComponents.length" :include="keepAliveComponents">
<component :is="Component" :key="route.fullPath" />
</keep-alive>
<component v-else :is="Component" :key="route.fullPath" />
</transition>
</template>
</template>
</RouterView>
</template>
@@ -30,18 +38,20 @@
},
},
setup() {
const { getIsPageAnimate, getPageAnimateType } = useProjectSetting();
const { isPageAnimate, pageAnimateType } = useProjectSetting();
const asyncRouteStore = useAsyncRouteStore();
// 需要缓存的路由组件
const keepAliveComponents = computed(() => asyncRouteStore.keepAliveComponents);
const getTransitionName = computed(() => {
return unref(getIsPageAnimate) ? unref(getPageAnimateType) : '';
return unref(isPageAnimate) ? unref(pageAnimateType) : '';
});
const mode = import.meta.env.MODE;
return {
keepAliveComponents,
getTransitionName,
mode,
};
},
});

View File

@@ -23,7 +23,7 @@
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
export default defineComponent({
name: 'Menu',
name: 'AppMenu',
components: {},
props: {
mode: {
@@ -52,9 +52,7 @@
const selectedKeys = ref<string>(currentRoute.name as string);
const headerMenuSelectKey = ref<string>('');
const { getNavMode } = useProjectSetting();
const navMode = getNavMode;
const { navMode } = useProjectSetting();
// 获取当前打开的子菜单
const matched = currentRoute.matched;
@@ -99,13 +97,16 @@
() => currentRoute.fullPath,
() => {
updateMenu();
const matched = currentRoute.matched;
state.openKeys = matched.map((item) => item.name);
const activeMenu: string = (currentRoute.meta?.activeMenu as string) || '';
selectedKeys.value = activeMenu ? (activeMenu as string) : (currentRoute.name as string);
}
);
function updateSelectedKeys() {
const matched = currentRoute.matched;
state.openKeys = matched.map((item) => item.name);
const activeMenu: string = (currentRoute.meta?.activeMenu as string) || '';
selectedKeys.value = activeMenu ? (activeMenu as string) : (currentRoute.name as string);
}
function updateMenu() {
if (!settingStore.menuSetting.mixMenu) {
menus.value = generatorMenu(asyncRouteStore.getMenus);
@@ -116,6 +117,7 @@
const activeMenu: string = currentRoute?.matched[0].meta?.activeMenu as string;
headerMenuSelectKey.value = (activeMenu ? activeMenu : firstRouteName) || '';
}
updateSelectedKeys();
}
// 点击菜单

View File

@@ -1,24 +0,0 @@
import {
DownOutlined,
ReloadOutlined,
CloseOutlined,
VerticalRightOutlined,
VerticalLeftOutlined,
ColumnWidthOutlined,
MinusOutlined,
} from '@ant-design/icons-vue';
import { Dropdown, Tabs, Card } from 'ant-design-vue';
export default {
[Tabs.name]: Tabs,
[Tabs.TabPane.name]: Tabs.TabPane,
[Dropdown.name]: Dropdown,
[Card.name]: Card,
MinusOutlined,
DownOutlined,
ReloadOutlined,
CloseOutlined,
VerticalRightOutlined,
VerticalLeftOutlined,
ColumnWidthOutlined,
};

View File

@@ -1,6 +1,6 @@
<template>
<div
class="tabs-view"
class="box-border tabs-view"
:class="{
'tabs-view-fix': multiTabsSetting.fixed,
'tabs-view-fixed-header': isMultiHeaderFixed,
@@ -35,7 +35,7 @@
<div
:id="`tag${element.fullPath.split('/').join('\/')}`"
class="tabs-card-scroll-item"
:class="{ 'active-item': activeKey === element.path }"
:class="{ 'active-item': activeKey === element.fullPath }"
@click.stop="goPage(element)"
@contextmenu="handleContextMenu($event, element)"
>
@@ -82,7 +82,6 @@
computed,
ref,
toRefs,
unref,
provide,
watch,
onMounted,
@@ -130,7 +129,7 @@
},
setup(props) {
const { getDarkTheme, getAppTheme } = useDesignSetting();
const { getNavMode, getHeaderSetting, getMenuSetting, getMultiTabsSetting, getIsMobile } =
const { navMode, headerSetting, menuSetting, multiTabsSetting, isMobile } =
useProjectSetting();
const settingStore = useProjectSettingStore();
@@ -161,7 +160,7 @@
dropdownY: 0,
showDropdown: false,
isMultiHeaderFixed: false,
multiTabsSetting: getMultiTabsSetting,
multiTabsSetting: multiTabsSetting,
});
// 获取简易的路由对象
@@ -173,25 +172,23 @@
const isMixMenuNoneSub = computed(() => {
const mixMenu = settingStore.menuSetting.mixMenu;
const currentRoute = useRoute();
const navMode = unref(getNavMode);
if (unref(navMode) != 'horizontal-mix') return true;
return !(unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot);
if (navMode.value != 'horizontal-mix') return true;
return !(navMode.value === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot);
});
//动态组装样式 菜单缩进
const getChangeStyle = computed(() => {
const { collapsed } = props;
const navMode = unref(getNavMode);
const { minMenuWidth, menuWidth }: any = unref(getMenuSetting);
const { fixed }: any = unref(getMultiTabsSetting);
const { minMenuWidth, menuWidth }: any = menuSetting.value;
const { fixed }: any = multiTabsSetting.value;
let lenNum =
navMode === 'horizontal' || !isMixMenuNoneSub.value
navMode.value === 'horizontal' || !isMixMenuNoneSub.value
? '0px'
: collapsed
? `${minMenuWidth}px`
: `${menuWidth}px`;
if (getIsMobile.value) {
if (isMobile.value) {
return {
left: '0px',
width: '100%',
@@ -205,7 +202,7 @@
//tags 右侧下拉菜单
const TabsMenuOptions = computed(() => {
const isDisabled = unref(tabsList).length <= 1;
const isDisabled = tabsList.value.length <= 1;
return [
{
label: '刷新当前',
@@ -215,7 +212,7 @@
{
label: `关闭当前`,
key: '2',
disabled: unref(isCurrent) || isDisabled,
disabled: isCurrent.value || isDisabled,
icon: renderIcon(CloseOutlined),
},
{
@@ -263,8 +260,8 @@
window.pageYOffset ||
document.body.scrollTop; // 滚动条偏移量
state.isMultiHeaderFixed = !!(
!getHeaderSetting.value.fixed &&
getMultiTabsSetting.value.fixed &&
!headerSetting.value.fixed &&
multiTabsSetting.value.fixed &&
scrollTop >= 64
);
}
@@ -297,7 +294,7 @@
(to) => {
if (whiteList.includes(route.name as string)) return;
state.activeKey = to;
tabsViewStore.addTabs(getSimpleRoute(route));
tabsViewStore.addTab(getSimpleRoute(route));
updateNavScroll(true);
},
{ immediate: true }
@@ -328,7 +325,7 @@
const reloadPage = () => {
delKeepAliveCompName();
router.push({
path: '/redirect' + unref(route).fullPath,
path: '/redirect' + route.fullPath,
});
};
@@ -642,7 +639,6 @@
background: var(--color);
border-radius: 2px;
cursor: pointer;
//margin-right: 10px;
&-btn {
color: var(--color);
@@ -665,7 +661,7 @@
.tabs-view-fix {
position: fixed;
z-index: 5;
padding: 6px 19px 6px 10px;
padding: 6px 10px 6px 10px;
left: 200px;
}

View File

@@ -21,13 +21,22 @@
</n-layout-sider>
<n-drawer
v-model:show="showSideDrawder"
v-model:show="showSideDrawer"
:width="menuWidth"
:placement="'left'"
class="layout-side-drawer"
>
<Logo :collapsed="collapsed" />
<AsideMenu @clickMenuItem="collapsed = false" />
<n-layout-sider
:position="fixedMenu"
:collapsed="false"
:width="menuWidth"
:native-scrollbar="false"
:inverted="inverted"
class="layout-sider"
>
<Logo :collapsed="collapsed" />
<AsideMenu v-model:location="getMenuLocation" />
</n-layout-sider>
</n-drawer>
<n-layout :inverted="inverted">
@@ -77,27 +86,24 @@
import { PageHeader } from './components/Header';
import { useProjectSetting } from '@/hooks/setting/useProjectSetting';
import { useDesignSetting } from '@/hooks/setting/useDesignSetting';
import { useLoadingBar } from 'naive-ui';
import { useRoute } from 'vue-router';
import { useProjectSettingStore } from '@/store/modules/projectSetting';
const { getDarkTheme } = useDesignSetting();
const {
// getShowFooter,
getNavMode,
getNavTheme,
getHeaderSetting,
getMenuSetting,
getMultiTabsSetting,
// showFooter,
navMode,
navTheme,
headerSetting,
menuSetting,
multiTabsSetting,
} = useProjectSetting();
const settingStore = useProjectSettingStore();
const navMode = getNavMode;
const collapsed = ref<boolean>(false);
const { mobileWidth, menuWidth } = unref(getMenuSetting);
const { mobileWidth, menuWidth } = unref(menuSetting);
const isMobile = computed<boolean>({
get: () => settingStore.getIsMobile,
@@ -105,12 +111,12 @@
});
const fixedHeader = computed(() => {
const { fixed } = unref(getHeaderSetting);
const { fixed } = unref(headerSetting);
return fixed ? 'absolute' : 'static';
});
const isMixMenuNoneSub = computed(() => {
const mixMenu = settingStore.menuSetting.mixMenu;
const mixMenu = unref(menuSetting).mixMenu;
const currentRoute = useRoute();
if (unref(navMode) != 'horizontal-mix') return true;
if (unref(navMode) === 'horizontal-mix' && mixMenu && currentRoute.meta.isRoot) {
@@ -120,45 +126,37 @@
});
const fixedMenu = computed(() => {
const { fixed } = unref(getHeaderSetting);
const { fixed } = unref(headerSetting);
return fixed ? 'absolute' : 'static';
});
const isMultiTabs = computed(() => {
return unref(getMultiTabsSetting).show;
return unref(multiTabsSetting).show;
});
const fixedMulti = computed(() => {
return unref(getMultiTabsSetting).fixed;
return unref(multiTabsSetting).fixed;
});
const inverted = computed(() => {
return ['dark', 'header-dark'].includes(unref(getNavTheme));
return ['dark', 'header-dark'].includes(unref(navTheme));
});
const getHeaderInverted = computed(() => {
const navTheme = unref(getNavTheme);
return ['light', 'header-dark'].includes(navTheme) ? unref(inverted) : !unref(inverted);
return ['light', 'header-dark'].includes(unref(navTheme)) ? unref(inverted) : !unref(inverted);
});
const leftMenuWidth = computed(() => {
const { minMenuWidth, menuWidth } = unref(getMenuSetting);
const { minMenuWidth, menuWidth } = unref(menuSetting);
return collapsed.value ? minMenuWidth : menuWidth;
});
// const getChangeStyle = computed(() => {
// const { minMenuWidth, menuWidth } = unref(getMenuSetting);
// return {
// 'padding-left': collapsed.value ? `${minMenuWidth}px` : `${menuWidth}px`,
// };
// });
const getMenuLocation = computed(() => {
return 'left';
});
// 控制显示或隐藏移动端侧边栏
const showSideDrawder = computed({
const showSideDrawer = computed({
get: () => isMobile.value && collapsed.value,
set: (val) => (collapsed.value = val),
});
@@ -185,9 +183,6 @@
onMounted(() => {
checkMobileMode();
window.addEventListener('resize', watchWidth);
//挂载在 window 方便与在js中使用
window['$loading'] = useLoadingBar();
window['$loading'].finish();
});
</script>

View File

@@ -1,19 +1,23 @@
import './styles/tailwind.css';
import './styles/index.less';
import { createApp } from 'vue';
import { setupNaiveDiscreteApi, setupNaive, setupDirectives } from '@/plugins';
import App from './App.vue';
import router, { setupRouter } from './router';
import { setupStore } from '@/store';
import { setupNaive, setupDirectives } from '@/plugins';
import { AppProvider } from '@/components/Application';
async function bootstrap() {
const appProvider = createApp(AppProvider);
const app = createApp(App);
// 挂载状态管理
setupStore(app);
// 注册全局常用的 naive-ui 组件
setupNaive(app);
// 挂载 naive-ui 脱离上下文的 Api
setupNaiveDiscreteApi();
// 注册全局自定义组件
//setupCustomComponents();
@@ -23,18 +27,18 @@ async function bootstrap() {
// 注册全局方法app.config.globalProperties.$message = message
//setupGlobalMethods(app);
// 挂载状态管理
setupStore(app);
//优先挂载一下 Provider 解决路由守卫Axios中可使用DialogMessage 等之类组件
appProvider.mount('#appProvider', true);
// 挂载路由
await setupRouter(app);
setupRouter(app);
// 路由准备就绪后挂载APP实例
// 路由准备就绪后挂载 APP 实例
// https://router.vuejs.org/api/interfaces/router.html#isready
await router.isReady();
// https://www.naiveui.com/en-US/os-theme/docs/style-conflict#About-Tailwind's-Preflight-Style-Override
const meta = document.createElement('meta');
meta.name = 'naive-ui-style';
document.head.appendChild(meta);
app.mount('#app', true);
}

View File

@@ -1,6 +1,10 @@
import { App } from 'vue';
import { permission } from '@/directives/permission';
import copy from '@/directives/copy';
import debounce from '@/directives/debounce';
import throttle from '@/directives/throttle';
import draggable from '@/directives/draggable';
/**
* 注册全局自定义指令
@@ -9,4 +13,12 @@ import { permission } from '@/directives/permission';
export function setupDirectives(app: App) {
// 权限控制指令(演示)
app.directive('permission', permission);
// 复制指令
app.directive('copy', copy);
// 防抖指令
app.directive('debounce', debounce);
// 节流指令
app.directive('throttle', throttle);
// 拖拽指令
app.directive('draggable', draggable);
}

View File

@@ -1,4 +1,5 @@
export { setupNaive } from '@/plugins/naive';
export { setupNaiveDiscreteApi } from '@/plugins/naiveDiscreteApi';
export { setupDirectives } from '@/plugins/directives';
export { setupCustomComponents } from '@/plugins/customComponents';
export { setupGlobalMethods } from '@/plugins/globalMethods';

View File

@@ -1,9 +1,9 @@
import type { App } from 'vue';
import {
create,
NConfigProvider,
NMessageProvider,
NDialogProvider,
NConfigProvider,
NInput,
NButton,
NForm,
@@ -66,8 +66,10 @@ import {
NTimePicker,
NBackTop,
NSkeleton,
NCascader,
} from 'naive-ui';
// https://www.naiveui.com/en-US/os-theme/docs/import-on-demand
const naive = create({
components: [
NMessageProvider,
@@ -135,6 +137,7 @@ const naive = create({
NTimePicker,
NBackTop,
NSkeleton,
NCascader,
],
});

View File

@@ -0,0 +1,39 @@
import * as NaiveUI from 'naive-ui';
import { computed } from 'vue';
import { useDesignSetting } from '@/store/modules/designSetting';
import { lighten } from '@/utils/index';
/**
* 挂载 Naive-ui 脱离上下文的 API
* 如果你想在 setup 外使用 useDialog、useMessage、useNotification、useLoadingBar可以通过 createDiscreteApi 来构建对应的 API。
* https://www.naiveui.com/zh-CN/dark/components/discrete
*/
export function setupNaiveDiscreteApi() {
const designStore = useDesignSetting();
const configProviderPropsRef = computed(() => ({
theme: designStore.darkTheme ? NaiveUI.darkTheme : undefined,
themeOverrides: {
common: {
primaryColor: designStore.appTheme,
primaryColorHover: lighten(designStore.appTheme, 6),
primaryColorPressed: lighten(designStore.appTheme, 6),
},
LoadingBar: {
colorLoading: designStore.appTheme,
},
},
}));
const { message, dialog, notification, loadingBar } = NaiveUI.createDiscreteApi(
['message', 'dialog', 'notification', 'loadingBar'],
{
configProviderProps: configProviderPropsRef,
}
);
window['$message'] = message;
window['$dialog'] = dialog;
window['$notification'] = notification;
window['$loading'] = loadingBar;
}

View File

@@ -1,8 +1,8 @@
import type { AppRouteRecordRaw } from '@/router/types';
import { ErrorPage, RedirectName, Layout } from '@/router/constant';
import { RouteRecordRaw } from 'vue-router';
// 404 on a page
export const ErrorPageRoute: AppRouteRecordRaw = {
export const ErrorPageRoute: RouteRecordRaw = {
path: '/:path(.*)*',
name: 'ErrorPage',
component: Layout,
@@ -23,7 +23,7 @@ export const ErrorPageRoute: AppRouteRecordRaw = {
],
};
export const RedirectRoute: AppRouteRecordRaw = {
export const RedirectRoute: RouteRecordRaw = {
path: '/redirect',
name: RedirectName,
component: Layout,
@@ -34,7 +34,7 @@ export const RedirectRoute: AppRouteRecordRaw = {
children: [
{
path: '/redirect/:path(.*)',
name: RedirectName,
name: `${RedirectName}Son`,
component: () => import('@/views/redirect/index.vue'),
meta: {
title: RedirectName,

View File

@@ -1,5 +1,5 @@
import { adminMenus } from '@/api/system/menu';
import { constantRouterIcon } from './router-icons';
import { constantRouterIcon } from './icons';
import { RouteRecordRaw } from 'vue-router';
import { Layout, ParentLayout } from '@/router/constant';
import type { AppRouteRecordRaw } from '@/router/types';
@@ -16,13 +16,13 @@ LayoutMap.set('IFRAME', Iframe);
* @param parent
* @returns {*}
*/
export const routerGenerator = (routerMap, parent?): any[] => {
export const generateRoutes = (routerMap, parent?): any[] => {
return routerMap.map((item) => {
const currentRouter: any = {
const currentRoute: any = {
// 路由地址 动态拼接生成如 /dashboard/workplace
path: `${(parent && parent.path) || ''}/${item.path}`,
path: `${(parent && parent.path) ?? ''}/${item.path}`,
// 路由名称,建议唯一
name: item.name || '',
name: item.name ?? '',
// 该路由对应页面的 组件
component: item.component,
// meta: 页面标题, 菜单图标, 页面权限(供指令权限用,可去掉)
@@ -35,17 +35,17 @@ export const routerGenerator = (routerMap, parent?): any[] => {
};
// 为了防止出现后端返回结果不规范,处理有可能出现拼接出两个 反斜杠
currentRouter.path = currentRouter.path.replace('//', '/');
currentRoute.path = currentRoute.path.replace('//', '/');
// 重定向
item.redirect && (currentRouter.redirect = item.redirect);
item.redirect && (currentRoute.redirect = item.redirect);
// 是否有子菜单,并递归处理
if (item.children && item.children.length > 0) {
//如果未定义 redirect 默认第一个子路由为 redirect
!item.redirect && (currentRouter.redirect = `${item.path}/${item.children[0].path}`);
!item.redirect && (currentRoute.redirect = `${item.path}/${item.children[0].path}`);
// Recursion
currentRouter.children = routerGenerator(item.children, currentRouter);
currentRoute.children = generateRoutes(item.children, currentRoute);
}
return currentRouter;
return currentRoute;
});
};
@@ -53,19 +53,11 @@ export const routerGenerator = (routerMap, parent?): any[] => {
*
* @returns {Promise<Router>}
*/
export const generatorDynamicRouter = (): Promise<RouteRecordRaw[]> => {
return new Promise((resolve, reject) => {
adminMenus()
.then((result) => {
const routeList = routerGenerator(result);
asyncImportRoute(routeList);
resolve(routeList);
})
.catch((err) => {
reject(err);
});
});
export const generateDynamicRoutes = async (): Promise<RouteRecordRaw[]> => {
const result = await adminMenus();
const router = generateRoutes(result);
asyncImportRoute(router);
return router;
};
/**

View File

@@ -1,19 +1,20 @@
import type { RouteRecordRaw } from 'vue-router';
import { isNavigationFailure, Router } from 'vue-router';
import { useUserStoreWidthOut } from '@/store/modules/user';
import { useAsyncRouteStoreWidthOut } from '@/store/modules/asyncRoute';
import { ACCESS_TOKEN } from '@/store/mutation-types';
import { storage } from '@/utils/Storage';
import { PageEnum } from '@/enums/pageEnum';
import { ErrorPageRoute } from '@/router/base';
import { useAsyncRoute } from '@/store/modules/asyncRoute';
import { useUser } from '@/store/modules/user';
import { ACCESS_TOKEN } from '@/store/mutation-types';
import { storage } from '@/utils/Storage';
import type { RouteRecordRaw } from 'vue-router';
import { isNavigationFailure, Router } from 'vue-router';
import { RedirectName } from './constant';
const LOGIN_PATH = PageEnum.BASE_LOGIN;
const whitePathList = [LOGIN_PATH]; // no redirect whitelist
export function createRouterGuards(router: Router) {
const userStore = useUserStoreWidthOut();
const asyncRouteStore = useAsyncRouteStoreWidthOut();
const userStore = useUser();
const asyncRouteStore = useAsyncRoute();
router.beforeEach(async (to, from, next) => {
const Loading = window['$loading'] || null;
Loading && Loading.start();
@@ -51,12 +52,12 @@ export function createRouterGuards(router: Router) {
return;
}
if (asyncRouteStore.getIsDynamicAddedRoute) {
if (asyncRouteStore.getIsDynamicRouteAdded) {
next();
return;
}
const userInfo = await userStore.GetInfo();
const userInfo = await userStore.getInfo();
const routes = await asyncRouteStore.generateRoutes(userInfo);
@@ -74,7 +75,7 @@ export function createRouterGuards(router: Router) {
const redirectPath = (from.query.redirect || to.path) as string;
const redirect = decodeURIComponent(redirectPath);
const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect };
asyncRouteStore.setDynamicAddedRoute(true);
asyncRouteStore.setDynamicRouteAdded(true);
next(nextData);
Loading && Loading.finish();
});
@@ -84,14 +85,14 @@ export function createRouterGuards(router: Router) {
if (isNavigationFailure(failure)) {
//console.log('failed navigation', failure)
}
const asyncRouteStore = useAsyncRouteStoreWidthOut();
const asyncRouteStore = useAsyncRoute();
// 在这里设置需要缓存的组件名称
const keepAliveComponents = asyncRouteStore.keepAliveComponents;
const currentComName: any = to.matched.find((item) => item.name == to.name)?.name;
if (currentComName && !keepAliveComponents.includes(currentComName) && to.meta?.keepAlive) {
// 需要缓存的组件
keepAliveComponents.push(currentComName);
} else if (!to.meta?.keepAlive || to.name == 'Redirect') {
} else if (!to.meta?.keepAlive || to.name == RedirectName) {
// 不需要缓存的组件
const index = asyncRouteStore.keepAliveComponents.findIndex((name) => name == currentComName);
if (index != -1) {

View File

@@ -1,21 +1,20 @@
import { App } from 'vue';
import { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router';
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
import { RedirectRoute } from '@/router/base';
import { PageEnum } from '@/enums/pageEnum';
import { createRouterGuards } from './router-guards';
import { createRouterGuards } from './guards';
import type { IModuleType } from './types';
const modules = import.meta.globEager('./modules/**/*.ts');
const modules = import.meta.glob<IModuleType>('./modules/**/*.ts', { eager: true });
const routeModuleList: RouteRecordRaw[] = [];
Object.keys(modules).forEach((key) => {
const mod = modules[key].default || {};
const routeModuleList: RouteRecordRaw[] = Object.keys(modules).reduce((list, key) => {
const mod = modules[key].default ?? {};
const modList = Array.isArray(mod) ? [...mod] : [mod];
routeModuleList.push(...modList);
});
return [...list, ...modList];
}, []);
function sortRoute(a, b) {
return (a.meta?.sort || 0) - (b.meta?.sort || 0);
return (a.meta?.sort ?? 0) - (b.meta?.sort ?? 0);
}
routeModuleList.sort(sortRoute);
@@ -42,10 +41,10 @@ export const LoginRoute: RouteRecordRaw = {
export const asyncRoutes = [...routeModuleList];
//普通路由 无需验证权限
export const constantRouter: any[] = [LoginRoute, RootRoute, RedirectRoute];
export const constantRouter: RouteRecordRaw[] = [LoginRoute, RootRoute, RedirectRoute];
const router = createRouter({
history: createWebHashHistory(''),
history: createWebHistory(),
routes: constantRouter,
strict: true,
scrollBehavior: () => ({ left: 0, top: 0 }),

View File

@@ -1,7 +1,7 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { ProjectOutlined } from '@vicons/antd';
import { renderIcon, renderNew } from '@/utils/index';
import { renderIcon } from '@/utils/index';
const routes: Array<RouteRecordRaw> = [
{
@@ -19,8 +19,7 @@ const routes: Array<RouteRecordRaw> = [
path: 'index',
name: `about_index`,
meta: {
title: '关于',
extra: renderNew(),
title: '关于项目',
activeMenu: 'about_index',
},
component: () => import('@/views/about/index.vue'),

View File

@@ -5,17 +5,6 @@ import { renderIcon, renderNew } from '@/utils';
const routeName = 'comp';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/comp',

View File

@@ -5,16 +5,6 @@ import { renderIcon } from '@/utils/index';
const routeName = 'dashboard';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/dashboard',

View File

@@ -0,0 +1,31 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { BorderOuterOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
const routes: Array<RouteRecordRaw> = [
{
path: '/directive',
name: 'directive',
component: Layout,
meta: {
sort: 9,
isRoot: true,
activeMenu: 'directive_index',
icon: renderIcon(BorderOuterOutlined),
},
children: [
{
path: 'index',
name: `directive_index`,
meta: {
title: '指令示例',
activeMenu: 'directive_index',
},
component: () => import('@/views/directive/index.vue'),
},
],
},
];
export default routes;

View File

@@ -6,12 +6,12 @@ import { renderIcon } from '@/utils/index';
const routes: Array<RouteRecordRaw> = [
{
path: '/external',
name: 'https://naive-ui-admin-docs.vercel.app',
name: 'https://docs.naiveadmin.com',
component: Layout,
meta: {
title: '项目文档',
icon: renderIcon(DocumentTextOutline),
sort: 9,
sort: 11,
},
},
];

View File

@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
import { ExclamationCircleOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/exception',

View File

@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
import { ProfileOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/form',

View File

@@ -31,7 +31,7 @@ const routes: Array<RouteRecordRaw> = [
name: 'frame-docs',
meta: {
title: '项目文档(内嵌)',
frameSrc: 'https://naive-ui-admin-docs.vercel.app',
frameSrc: 'https://jekip.github.io/docs',
},
component: IFrame,
},

View File

@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
import { TableOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/list',

View File

@@ -0,0 +1,20 @@
import { RouteRecordRaw } from 'vue-router';
import { Layout } from '@/router/constant';
import { SketchOutlined } from '@vicons/antd';
import { renderIcon, renderNew } from '@/utils/index';
const routes: Array<RouteRecordRaw> = [
{
path: '/newversion',
name: 'https://www.naiveadmin.com',
component: Layout,
meta: {
title: 'Plus 版本',
extra: renderNew(),
icon: renderIcon(SketchOutlined),
sort: 12,
},
},
];
export default routes;

View File

@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
import { CheckCircleOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/result',

View File

@@ -3,17 +3,6 @@ import { Layout } from '@/router/constant';
import { SettingOutlined } from '@vicons/antd';
import { renderIcon } from '@/utils/index';
/**
* @param name 路由名称, 必须设置,且不能重名
* @param meta 路由元信息(路由附带扩展信息)
* @param redirect 重定向地址, 访问这个路由时,自定进行重定向
* @param meta.disabled 禁用整个菜单
* @param meta.title 菜单名称
* @param meta.icon 菜单图标
* @param meta.keepAlive 缓存该路由
* @param meta.sort 排序越小越排前
*
* */
const routes: Array<RouteRecordRaw> = [
{
path: '/setting',

Some files were not shown because too many files have changed in this diff Show More