1
0
mirror of https://github.com/zclzone/vue-naive-admin.git synced 2025-12-28 04:00:22 +08:00

48 Commits

Author SHA1 Message Date
leip247
a977ec1f3a fix: #113 (#116)
* fix(slot): 优化部分slot写法,减少手动判断

* fix(slot): 优化部分slot写法,减少手动判断

* feat(table): 添加vxe-table,适配naiveui主题,并替换掉之前table,自带导出

* feat(table): 添加vxe-table,适配naiveui主题,并替换掉之前table,自带导出

* fix: 完善table总条数不显示问题
2025-03-19 09:15:41 +08:00
leip247
6d1b3a4159 feat(table): 添加vxe-table,适配naiveui主题,并替换掉之前table,自带导出 (#105)
* fix(slot): 优化部分slot写法,减少手动判断

* fix(slot): 优化部分slot写法,减少手动判断

* feat(table): 添加vxe-table,适配naiveui主题,并替换掉之前table,自带导出

* feat(table): 添加vxe-table,适配naiveui主题,并替换掉之前table,自带导出
2024-11-22 15:47:29 +08:00
leip247
6af6eb3c99 fix(slot): 优化部分slot写法,减少手动判断 (#101)
* fix(slot): 优化部分slot写法,减少手动判断

* fix(slot): 优化部分slot写法,减少手动判断
2024-11-13 11:05:03 +08:00
zclzone
69e6e9a14a fix: 替换失效图片链接 2024-10-29 15:14:04 +08:00
zclzone
d066c999cc chore: update deps 2024-10-22 15:27:46 +08:00
zclzone
97fb638ac2 chore: update deps 2024-10-08 15:44:41 +08:00
zclzone
db800f60a9 docs: update README 2024-09-26 17:25:52 +08:00
zclzone
304b74bb49 chore: 弃用sass,使用原生css替换 2024-09-25 18:19:48 +08:00
zclzone
359f683955 chore: update deps, close #97 2024-09-25 11:24:23 +08:00
zclzone
d7a4034c69 chore: 移除无用文件 2024-09-14 10:12:08 +08:00
zclzone
520db7ec6e style: lint 2024-09-06 14:46:07 +08:00
zclzone
2264512a8b chore: update deps 2024-09-06 14:27:47 +08:00
zclzone
3359b0f2b5 fix: 修复keepAlive页面transition不生效问题, close #93 2024-09-03 09:02:17 +08:00
zclzone
615c3b63f7 chore: 集成plugin-vue-jsx插件,支持jsx写法 2024-08-27 10:50:14 +08:00
zclzone
51f2d5d22c fix: CRUD组件前端分页问题 2024-08-21 11:35:48 +08:00
zclzone
02e614e903 chore: 兼容较低版本node 2024-08-21 09:51:41 +08:00
zclzone
fdb6ad5f1d chore: 修改 vscode settings.json 2024-08-15 09:04:16 +08:00
zclzone
723d141705 style: 优化样式 2024-08-11 13:45:59 +08:00
zclzone
7b1b990d77 chore: update deps 2024-08-11 13:39:17 +08:00
zclzone
36ce5c34d0 fix: 自定义图标不显示问题 2024-07-31 21:26:45 +08:00
zclzone
1e0e1f2c48 chore: update deps 2024-07-28 20:53:58 +08:00
zclzone
491237919a docs: 添加图标使用文档外链 2024-07-24 09:51:30 +08:00
zclzone
0c0e7a465b docs: update readme 2024-07-23 08:53:39 +08:00
zclzone
0a0c970f5c Merge branch '2.x' of https://gitee.com/isme-admin/vue-naive-admin into 2.x 2024-07-22 21:47:42 +08:00
zclzone
c9e0feea47 docs: 变更文档地址 2024-07-22 21:47:32 +08:00
zclzone
f28e3a946e fix: lint fix 2024-07-19 14:55:02 +08:00
zclzone
7dde0bbfc6 Merge branch '2.x' of https://github.com/zclzone/vue-naive-admin into 2.x 2024-07-16 22:04:09 +08:00
zclzone
cf76f2177b chore: update deps 2024-07-16 21:59:35 +08:00
大脸怪
d0a99dcf12 Merge pull request #88 from lxc455735702/feat_crudExpand
 feat(crud): 支持展开收起
2024-07-10 17:14:55 +08:00
DeveloperLXC
6ab97f511f feat(crud): 支持展开收起 2024-07-10 15:05:36 +08:00
zclzone
2384113120 chore: update 2024-07-10 09:38:03 +08:00
zclzone
8cb3d2cf19 fix: 优化接口异常弹窗及消息提醒方式 2024-07-05 17:41:58 +08:00
zclzone
c5d93628f0 chore: 更新插件推荐列表,修改settings.json 2024-07-05 17:38:42 +08:00
zclzone
4d494801e3 chore: update deps 2024-06-30 22:16:38 +08:00
zclzone
e14bd2084a style: 删除无用注释代码 2024-06-30 16:58:57 +08:00
zclzone
3c2e120e34 style: 优化主题切换过渡动画 2024-06-25 18:17:17 +08:00
zclzone
c230e86767 Merge branch '2.x' of https://gitee.com/isme-admin/vue-naive-admin into 2.x 2024-06-25 16:46:47 +08:00
zclzone
b464c70ae3 refactor: 封装不同layout下的公用组件 2024-06-25 16:46:31 +08:00
zclzone
c87f56814e feat: 添加切换主题的动画效果 2024-06-25 16:44:27 +08:00
zclzone
fd4582fc0c style: 图标示例布局改成grid 2024-06-23 00:15:10 +08:00
zclzone
c2ff434b9d feat: crud组件handleSearch增加参数控制是否留在当前页 2024-06-20 14:02:45 +08:00
大脸怪
c98e489063 Merge pull request #82 from mengdebiao/2.x
fix: 修复 crud 组件不显示总条数
2024-06-18 21:36:44 +08:00
mdb
0ab029a124 fix: 修复 crud 组件不显示总条数 2024-06-18 21:09:03 +08:00
zclzone
e30e1fa27f chore: update deps 2024-06-17 18:06:04 +08:00
zclzone
3c64aca739 chore: 移除pnpm-lock.yaml 2024-06-17 18:05:51 +08:00
zclzone
995175a8a8 feat: 增加 layoutSettingVisible 控制 LayoutSetting 显示隐藏 2024-06-11 20:39:31 +08:00
zclzone
eca0859f66 docs: update README.md 2024-06-11 18:11:45 +08:00
zclzone
38fbbfd376 chore: 移除无用引用 2024-06-11 16:27:11 +08:00
68 changed files with 4843 additions and 2986 deletions

View File

@@ -4,6 +4,6 @@ root = true
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
end_of_line = unset
insert_final_newline = true
trim_trailing_whitespace = true

View File

@@ -1,63 +0,0 @@
{
"globals": {
"$loadingBar": true,
"$message": true,
"$dialog": true,
"$notification": true,
"$modal": true,
"defineOptions": true,
"EffectScope": true,
"computed": true,
"createApp": true,
"customRef": true,
"defineAsyncComponent": true,
"defineComponent": true,
"effectScope": true,
"getCurrentInstance": true,
"getCurrentScope": true,
"h": true,
"inject": true,
"isProxy": true,
"isReactive": true,
"isReadonly": true,
"isRef": true,
"markRaw": true,
"nextTick": true,
"onActivated": true,
"onBeforeMount": true,
"onBeforeUnmount": true,
"onBeforeUpdate": true,
"onDeactivated": true,
"onErrorCaptured": true,
"onMounted": true,
"onRenderTracked": true,
"onRenderTriggered": true,
"onScopeDispose": true,
"onServerPrefetch": true,
"onUnmounted": true,
"onUpdated": true,
"provide": true,
"reactive": true,
"readonly": true,
"ref": true,
"resolveComponent": true,
"shallowReactive": true,
"shallowReadonly": true,
"shallowRef": true,
"toRaw": true,
"toRef": true,
"toRefs": true,
"triggerRef": true,
"unref": true,
"useAttrs": true,
"useCssModule": true,
"useCssVars": true,
"useRoute": true,
"useRouter": true,
"useSlots": true,
"watch": true,
"watchEffect": true,
"watchPostEffect": true,
"watchSyncEffect": true
}
}

3
.npmrc
View File

@@ -1,2 +1,3 @@
registry=https://registry.npmmirror.com
shamefully-hoist=true
shamefully-hoist=true
strict-peer-dependencies=false

View File

@@ -6,6 +6,7 @@
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"sdras.vue-vscode-snippets",
"mikestead.dotenv"
"mikestead.dotenv",
"simonhe.common-intellisense"
]
}

14
.vscode/settings.json vendored
View File

@@ -1,8 +1,4 @@
{
// Enable the ESlint flat config support
// (remove this if your ESLint extension above v3.0.5)
"eslint.experimental.useFlatConfig": true,
"prettier.enable": false,
"editor.formatOnSave": false,
@@ -23,5 +19,13 @@
"markdown",
"json",
"jsonc"
]
],
"common-intellisense.ui": [
"naiveUi2"
],
"workbench.colorCustomizations": {
"activityBar.background": "#233212",
"titleBar.activeBackground": "#314619",
"titleBar.activeForeground": "#F9FCF6"
}
}

View File

@@ -54,14 +54,20 @@ Vue Naive Admin 提供一套后端代码,技术栈使用 Nestjs + TypeOrm + My
## 文档
- 项目文档: [docs | vue-naive-admin](https://docs.isme.top/web/#/624306705/188522224)
- 项目文档: [docs | vue-naive-admin](https://isme.top)
- 接口文档: [apidoc | isme-nest-serve](https://apifox.com/apidoc/shared-ff4a4d32-c0d1-4caf-b0ee-6abc130f734a)
> 注:有个比较常见的问题,就是如何添加菜单和修改菜单,由于项目是由后端控制菜单资源的,所以需要对接后端后在资源管理功能对菜单进行增删改,然后在角色管理功能给对应角色进行授权。具体如何对接后端,请参考 [项目文档](https://docs.isme.top/web/#/624306705/188522224)。当然,可能有些菜单你不想通过权限控制,那么你可以在 `/src/settings.js` 文件添加 basePermissions只需对齐菜单资源的结构即可结构可以参照 [接口文档](https://apifox.com/apidoc/shared-ff4a4d32-c0d1-4caf-b0ee-6abc130f734a/api-134536978)。
> 注:有个比较常见的问题,就是如何添加菜单和修改菜单,由于项目是由后端控制菜单资源的,所以需要对接后端后在资源管理功能对菜单进行增删改,然后在角色管理功能给对应角色进行授权。具体如何对接后端,请参考 [项目文档](https://isme.top)。当然,可能有些菜单你不想通过权限控制,那么你可以在 `/src/settings.js` 文件添加 basePermissions只需对齐菜单资源的结构即可结构可以参照 [接口文档](https://apifox.com/apidoc/shared-ff4a4d32-c0d1-4caf-b0ee-6abc130f734a/api-134536978)。
## 使用这个模板创建 Github 仓库
## 使用这个模板开始你的项目
[使用这个模板创建仓库](https://github.com/zclzone/vue-naive-admin/generate).
[使用这个模板创建Github仓库](https://github.com/zclzone/vue-naive-admin/generate).
或者使用 `degit` 克隆此仓库,这样将没有任何历史提交记录:
```cmd
npx degit zclzone/vue-naive-admin
```
## 版权说明
@@ -75,9 +81,8 @@ Vue Naive Admin 提供一套后端代码,技术栈使用 Nestjs + TypeOrm + My
- [isme-java-serve](https://github.com/DHBin/isme-java-serve): 一个轻量级的Java后端服务基于SpringBoot、MybatisPlus、SaToken、MapStruct等实现已对接 Vue Naive Admin 2.0。
- [naive-admin-go](https://github.com/ituserxxx/naive-admin-go): 一个 Go 后端服务,基于 gin、gorm、mysql、jwt和session已对接 Vue Naive Admin 2.0。
- [isme-java](https://github.com/AllenDengMs/isme-java): 一个轻量且完成度高的Java后端服务基于Springboot 3 + JDK21层次结构严谨注释齐全避免过度封装代码可读性度高依赖最简化上手成本低已集成 账号管理、权限管理、API鉴权、消息国际化等功能。
## 入群交流
## 联系作者 or 进交流
添加微信,拉你进群
![](https://static.isme.top/image/coder_wx.jpg)
[https://www.isme.top/contact.html](https://www.isme.top/contact.html)

View File

@@ -8,7 +8,7 @@
import path from 'node:path'
import { globSync } from 'glob'
import dynamicIcons from '../src/assets/icons/dynamic-icons'
import dynamicIcons from '../src/assets/icons/dynamic-icons.js'
/**
* @usage 生成icons, 用于 unocss safelist以支持页面动态渲染自定义图标

View File

@@ -6,5 +6,5 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
export { pluginPagePathes } from './page-pathes'
export { pluginIcons } from './icons'
export { pluginPagePathes } from './page-pathes'

View File

@@ -4,7 +4,6 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" href="/favicon.png" />
<link rel="stylesheet" href="/resource/loading.css" />
<title>%VITE_TITLE%</title>
<style>
.loading-container {

View File

@@ -13,43 +13,46 @@
},
"dependencies": {
"@arco-design/color": "^0.4.0",
"@vueuse/core": "^10.10.0",
"axios": "^1.7.2",
"dayjs": "^1.11.11",
"echarts": "^5.5.0",
"@vueuse/core": "^11.1.0",
"axios": "^1.7.7",
"dayjs": "^1.11.13",
"echarts": "^5.5.1",
"exceljs": "^4.4.0",
"lodash-es": "^4.17.21",
"naive-ui": "^2.38.2",
"pinia": "^2.1.7",
"pinia-plugin-persistedstate": "^3.2.1",
"vue": "^3.4.27",
"vue-echarts": "^6.7.3",
"vue-router": "^4.3.2",
"naive-ui": "^2.40.1",
"pinia": "^2.2.4",
"pinia-plugin-persistedstate": "^4.1.1",
"vue": "^3.5.12",
"vue-echarts": "^7.0.3",
"vue-router": "^4.4.5",
"vxe-pc-ui": "4.2.5",
"vxe-table": "4.8.13",
"vxe-table-plugin-export-xlsx": "^4.0.4",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@antfu/eslint-config": "^2.20.0",
"@iconify/json": "^2.2.216",
"@iconify/utils": "^2.1.24",
"@unocss/eslint-config": "^0.60.4",
"@unocss/eslint-plugin": "^0.60.4",
"@unocss/preset-rem-to-px": "^0.60.4",
"@vitejs/plugin-vue": "^5.0.5",
"eslint": "^9.4.0",
"eslint-plugin-format": "^0.1.1",
"esno": "^4.7.0",
"@antfu/eslint-config": "^3.8.0",
"@iconify/json": "^2.2.262",
"@unocss/eslint-config": "^0.63.6",
"@unocss/eslint-plugin": "^0.63.6",
"@unocss/preset-rem-to-px": "^0.63.6",
"@vitejs/plugin-vue": "^5.1.4",
"@vitejs/plugin-vue-jsx": "^4.0.1",
"eslint": "^9.13.0",
"eslint-plugin-format": "^0.1.2",
"esno": "^4.8.0",
"fs-extra": "^11.2.0",
"glob": "^10.4.1",
"lint-staged": "^15.2.5",
"glob": "^11.0.0",
"lint-staged": "^15.2.10",
"rollup-plugin-visualizer": "^5.12.0",
"sass": "^1.77.4",
"simple-git-hooks": "^2.11.1",
"taze": "^0.13.8",
"unocss": "^0.60.4",
"unplugin-auto-import": "^0.17.6",
"unplugin-vue-components": "^0.27.0",
"vite": "^5.2.12",
"taze": "^0.17.2",
"unocss": "^0.63.6",
"unplugin-auto-import": "^0.18.3",
"unplugin-vue-components": "^0.27.4",
"vite": "^5.4.9",
"vite-plugin-router-warn": "^1.0.0",
"vite-plugin-vue-devtools": "^7.2.1"
"vite-plugin-vue-devtools": "^7.5.2"
},
"simple-git-hooks": {
"pre-commit": "pnpm lint-staged"

6252
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -16,20 +16,23 @@
>
<router-view v-if="Layout" v-slot="{ Component, route: curRoute }">
<component :is="Layout">
<KeepAlive :include="keepAliveNames">
<component :is="Component" v-if="!tabStore.reloading" :key="curRoute.fullPath" />
</KeepAlive>
<transition name="fade-slide" mode="out-in" appear>
<KeepAlive :include="keepAliveNames">
<component :is="Component" v-if="!tabStore.reloading" :key="curRoute.fullPath" />
</KeepAlive>
</transition>
</component>
<LayoutSetting class="fixed right-12 top-1/2 z-999" />
<LayoutSetting v-if="layoutSettingVisible" class="fixed right-12 top-1/2 z-999" />
</router-view>
</n-config-provider>
</template>
<script setup>
import { darkTheme, dateZhCN, zhCN } from 'naive-ui'
import { LayoutSetting } from '@/components'
import { useAppStore, useTabStore } from '@/store'
import { darkTheme, dateZhCN, zhCN } from 'naive-ui'
import { layoutSettingVisible } from './settings'
const layouts = new Map()
function getLayout(name) {

View File

@@ -14,7 +14,7 @@ export default {
// 刷新token
refreshToken: () => request.get('/auth/refresh/token'),
// 登出
logout: () => request.post('/auth/logout'),
logout: () => request.post('/auth/logout', {}, { needTip: false }),
// 切换当前角色
switchCurrentRole: role => request.post(`/auth/current-role/switch/${role}`),
// 获取角色权限

View File

@@ -8,13 +8,12 @@
<template>
<main class="cus-scroll h-full flex-col flex-1 bg-#f5f6fb dark:bg-#121212">
<transition name="fade-slide" mode="out-in" appear>
<main :class="{ 'flex-1': full }" class="m-12">
<slot />
</main>
</transition>
<slot v-if="$slots.footer" name="footer" />
<TheFooter v-else-if="showFooter" class="mb-12 mt-auto" />
<main :class="{ 'flex-1': full }" class="m-12">
<slot />
</main>
<slot name="footer">
<TheFooter v-if="showFooter" class="mb-12 mt-auto" />
</slot>
<n-back-top :bottom="20" />
</main>
</template>

View File

@@ -7,18 +7,17 @@
--------------------------------->
<template>
<main class="h-full flex-1 overflow-hidden bg-#f5f6fb dark:bg-#121212">
<div class="h-full flex-col">
<AppCard
v-if="showHeader"
class="sticky top-0 z-1 min-h-60 flex items-center justify-between px-24"
border-b="1px solid light_border dark:dark_border"
>
<slot v-if="$slots.header" name="header" />
<template v-else>
<div class="flex items-center">
<slot v-if="$slots['title-prefix']" name="title-prefix" />
<template v-else-if="back">
<main class="h-full flex-col flex-1 overflow-hidden bg-#f5f6fb dark:bg-#121212">
<AppCard
v-if="showHeader"
class="sticky top-0 z-1 min-h-60 flex items-center justify-between px-24"
border-b="1px solid light_border dark:dark_border"
>
<slot v-if="$slots.header" name="header" />
<template v-else>
<div class="flex items-center">
<slot name="title-prefix">
<template v-if="back">
<div
class="mr-16 flex cursor-pointer items-center text-16 opacity-60 transition-all-300 hover:opacity-40"
@click="router.back()"
@@ -27,29 +26,27 @@
<span class="ml-4">返回</span>
</div>
</template>
<div class="mr-12 h-16 w-4 rounded-l-2 bg-primary" />
<h2 class="font-normal">
{{ title ?? route.meta?.title }}
</h2>
<slot name="title-suffix" />
</div>
<slot name="action" />
</template>
</AppCard>
<transition name="fade-slide" mode="out-in" appear>
<AppCard class="cus-scroll m-12 h-0 flex-1 rounded-8 p-24" bordered>
<slot />
</AppCard>
</transition>
</div>
</main>
</slot>
<footer class="bg-#f5f6fb dark:bg-#121212">
<slot v-if="$slots.footer" name="footer" />
<AppCard v-else-if="showFooter" class="py-12">
<TheFooter />
<div class="mr-12 h-16 w-4 rounded-l-2 bg-primary" />
<h2 class="font-normal">
{{ title ?? route.meta?.title }}
</h2>
<slot name="title-suffix" />
</div>
<slot name="action" />
</template>
</AppCard>
</footer>
<AppCard class="cus-scroll m-12 h-0 flex-1 rounded-8 p-24" bordered>
<slot />
</AppCard>
<slot name="footer">
<AppCard v-if="showFooter" class="flex-shrink-0 py-12">
<TheFooter />
</AppCard>
</slot>
</main>
</template>
<script setup>

View File

@@ -94,8 +94,8 @@
<script setup>
import { MeModal } from '@/components'
import { useAppStore } from '@/store'
import { useModal } from '@/composables'
import { useAppStore } from '@/store'
const appStore = useAppStore()
const [modalRef] = useModal()

View File

@@ -0,0 +1,240 @@
<template>
<div
:style="isComputedHeight ? `height: calc(100% - ${autoHeight}px);` : `height: ${autoHeight}px`"
>
<vxe-grid
ref="gridRef"
:columns="vxeColumns"
:loading="loading"
:data="data"
height="auto"
:stripe="true"
:border="true"
size="mini"
:auto-resize="true"
:pager-config="pagerConfig"
:export-config="exportConfig"
:toolbar-config="vxeToolbarConfig"
:proxy-config="proxyConfig"
v-on="gridEvent"
>
<template #loading>
<n-spin :show="loading" />
</template>
<template #total>
<span class="font-size-14 line-height-28px"> {{ pagerConfig.total }} 条数据</span>
</template>
<template v-for="(c, k) in slots" :key="k" #[c.key]="{ row }">
<template v-if="Array.isArray(c.render(row))">
<component :is="r" v-for="(r, k1) in c.render(row)" :key="k1" :row="row" :col="c" />
</template>
<template v-else>
<component :is="c.render(row)" :row="row" :col="c" />
</template>
</template>
</vxe-grid>
</div>
</template>
<script setup>
import { computed } from 'vue'
defineOptions({ name: 'MeVxeGrid' })
const props = defineProps({
/**
* @remote 当isComputedHeight为true时为除table外的高度 false时为table高度
*/
autoHeight: {
type: Number,
default: 300,
},
/**
* @isPagination 是否为自动计算高度
*/
isComputedHeight: {
type: Boolean,
default: false,
},
loading: {
type: Boolean,
default: false,
},
/**
* @remote table的列
*/
columns: {
type: Array,
default: () => [],
},
data: {
type: Array,
default: () => [],
},
pagination: {
type: [Boolean, Object],
default: true,
},
exportConfig: {
type: Object,
default: () => ({}),
},
toolbarConfig: {
type: Object,
default: () => ({ }),
},
/**
* 是否显示复选框
*/
showCheck: {
type: Boolean,
default: false,
},
rowKey: {
type: String, // rowKey
default: 'id',
},
exportMethod: {
type: Function,
default: () => null,
},
remoteMethod: {
type: Function,
default: () => null,
},
})
const emits = defineEmits(['update:checked-row-keys', 'pageChanged'])
const isFirst = shallowRef(true)
const gridRef = ref(null)
watch(
() => props.data,
(n) => {
isFirst.value = false
gridRef.value.reloadData(n)
},
)
const vxeToolbarConfig = computed(() => {
return {
size: 'mini',
className: 'c-toolbar',
perfect: true,
refresh: true,
export: true,
zoom: true,
custom: true,
...props.toolbarConfig,
}
})
const pagerConfig = computed(() => {
if (props.pagination === false) {
return {
enabled: false,
}
}
return {
currentPage: props.pagination.pageNo,
pageSize: props.pagination.pageSize,
total: props.pagination.itemCount || 0,
background: true,
size: 'small',
layouts: [
'Home',
'PrevPage',
'JumpNumber',
'NextPage',
'End',
'Sizes',
'FullJump',
// 'Total',
],
slots: {
right: 'total',
},
}
})
/**
* 将naive 转为vxe-table可用的列
*/
const vxeColumns = computed(() => {
if (props.showCheck) {
return [{
type: 'checkbox',
resizable: false,
width: 45,
align: 'center',
fixed: 'left',
}, ...props.columns.map((item) => {
item.field = item.key
if (item.render) {
item.slots = { default: item.key }
}
return item
})]
}
return props.columns.map((item) => {
item.field = item.key
if (item.render) {
item.slots = { default: item.key }
}
return item
})
})
/**
* 将naive render函数转换为vxeTable的slots
*/
const slots = computed(() => {
return props.columns.filter(item => item.render)
})
const gridEvent = {
checkboxAll() {
const $grid = gridRef.value
if ($grid) {
emits(
'update:checked-row-keys',
$grid.getCheckboxRecords().map(item => item[props.rowKey]),
)
}
},
checkboxChange() {
const $grid = gridRef.value
if ($grid) {
emits(
'update:checked-row-keys',
$grid.getCheckboxRecords().map(item => item[props.rowKey]),
)
}
},
pageChange(e) {
emits('pageChanged', e)
},
}
const proxyConfig = {
props: {
result: 'data.pageData',
total: 'data.total',
},
ajax: {
query: async ({ page }) => {
if (isFirst.value === true) {
return Promise.reject('')
}
props.remoteMethod(page.currentPage, page.pageSize)
},
queryAll: async () => {
const res = await props.exportMethod()
return res
},
},
}
</script>
<style lang="scss" scoped>
</style>

View File

@@ -6,7 +6,7 @@
- Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
--------------------------------->
<template>
<div class="h-40 w-40 rounded-4 bg-primary p-1/100">
<div class="h-32 w-32 rounded-4 bg-primary">
<img src="@/assets/images/isme.png" alt="Logo">
</div>
</template>

View File

@@ -14,8 +14,8 @@
</template>
<script setup>
import { getPresetColors } from '@arco-design/color'
import { useAppStore } from '@/store'
import { getPresetColors } from '@arco-design/color'
const appStore = useAppStore()

View File

@@ -0,0 +1,44 @@
<template>
<i
class="mr-16 cursor-pointer"
:class="isDark ? 'i-fe:moon' : 'i-fe:sun'"
@click="toggleDark"
/>
</template>
<script setup>
import { useAppStore } from '@/store'
import { useDark, useToggle } from '@vueuse/core'
const appStore = useAppStore()
const isDark = useDark()
async function toggleDark({ clientX, clientY }) {
function handler() {
appStore.toggleDark()
useToggle(isDark)()
}
if (!document.startViewTransition) {
return handler()
}
const clipPath = [
`circle(0px at ${clientX}px ${clientY}px)`,
`circle(${Math.hypot(
Math.max(clientX, window.innerWidth - clientX),
Math.max(clientY, window.innerHeight - clientY),
)}px at ${clientX}px ${clientY}px)`,
]
await document.startViewTransition(handler).ready
document.documentElement.animate(
{ clipPath: isDark.value ? clipPath.reverse() : clipPath },
{
duration: 500,
easing: 'ease-in',
pseudoElement: `::view-transition-${isDark.value ? 'old' : 'new'}(root)`,
},
)
}
</script>

View File

@@ -1,5 +1,7 @@
export { default as AppCard } from './AppCard.vue'
export { default as TheFooter } from './TheFooter.vue'
export { default as AppPage } from './AppPage.vue'
export { default as CommonPage } from './CommonPage.vue'
export { default as LayoutSetting } from './LayoutSetting.vue'
export { default as MeVxeGrid } from './MeVxeGrid.vue'
export { default as TheFooter } from './TheFooter.vue'
export { default as ToggleTheme } from './ToggleTheme.vue'

View File

@@ -7,25 +7,59 @@
--------------------------------->
<template>
<AppCard v-if="$slots.default" bordered bg="#fafafc dark:black" class="mb-30 min-h-60 rounded-4">
<form class="flex justify-between p-16" @submit.prevent="handleSearch()">
<n-space wrap :size="[32, 16]">
<slot />
</n-space>
<div class="flex-shrink-0">
<n-button ghost type="primary" @click="handleReset">
<i class="i-fe:rotate-ccw mr-4" />
重置
</n-button>
<n-button attr-type="submit" class="ml-20" type="primary">
<i class="i-fe:search mr-4" />
搜索
</n-button>
</div>
</form>
</AppCard>
<div :class="computedHeight === true ? 'h-100%' : ''">
<div id="search_main">
<AppCard v-if="$slots.default" bordered bg="#fafafc dark:black" class="mb-30 min-h-60 rounded-4">
<form class="flex justify-between p-16" @submit.prevent="handleSearch()">
<n-scrollbar x-scrollable>
<n-space :wrap="!expand || isExpanded" :size="[32, 16]" class="p-10">
<slot />
</n-space>
</n-scrollbar>
<div class="flex-shrink-0 p-10">
<n-button ghost type="primary" @click="handleReset">
<i class="i-fe:rotate-ccw mr-4" />
重置
</n-button>
<n-button attr-type="submit" class="ml-20" type="primary">
<i class="i-fe:search mr-4" />
搜索
</n-button>
<NDataTable
<template v-if="expand">
<n-button v-if="!isExpanded" type="primary" text @click="toggleExpand">
<i class="i-fe:chevrons-down ml-4" />
展开
</n-button>
<n-button v-else text type="primary" @click="toggleExpand">
<i class="i-fe:chevrons-up ml-4" />
收起
</n-button>
</template>
</div>
</form>
</AppCard>
</div>
<MeVxeGrid
:remote="remote"
:loading="loading"
:scroll-x="scrollX"
:columns="columns"
:export-config="{ filename: exportName, modes: ['all', 'current'] }"
:toolbar-config="toolbarConfig"
:data="tableData"
:auto-height="computedHeight === true ? autoHeight : computedHeight"
:row-key="rowKey"
:is-computed-height="computedHeight === true"
:show-check="showCheck"
:pagination="isPagination ? pagination : false"
:remote-method="remoteMethod"
:export-method="exportMethod"
@update:checked-row-keys="onChecked"
@update:page="onPageChange"
/>
<!-- <NDataTable
:remote="remote"
:loading="loading"
:scroll-x="scrollX"
@@ -35,14 +69,39 @@
:pagination="isPagination ? pagination : false"
@update:checked-row-keys="onChecked"
@update:page="onPageChange"
/>
/> -->
</div>
</template>
<script setup>
import { NDataTable } from 'naive-ui'
import { utils, writeFile } from 'xlsx'
const props = defineProps({
/**
* 是否自动计算高度适用只有search和table
*/
computedHeight: {
type: [Boolean, Number],
default: () => true,
},
showCheck: {
type: Boolean,
default: () => false,
},
toolbarConfig: {
type: Object,
default: () => ({
export: true,
zoom: true,
}),
},
/**
* @remote 导出数据表格名称
*/
exportName: {
type: String,
default: '导出数据',
},
/**
* @remote true: 后端分页 false 前端分页
*/
@@ -89,13 +148,54 @@ const props = defineProps({
type: Function,
required: true,
},
/** 是否支持展开 */
expand: Boolean,
})
const emit = defineEmits(['update:queryItems', 'onChecked', 'onDataChange'])
const autoHeight = ref(0)
window.onresize = () => {
autoHeight.value = document.getElementById('search_main').clientHeight + 24
}
onMounted(() => {
autoHeight.value = document.getElementById('search_main').clientHeight + 24
})
const emit = defineEmits(['update:queryItems', 'onChecked', 'onDataChange'])
const loading = ref(false)
const initQuery = { ...props.queryItems }
const tableData = ref([])
const pagination = reactive({ page: 1, pageSize: 10 })
const pagination = reactive({
page: 1,
pageSize: 10,
prefix({ itemCount }) {
return `${itemCount} 条数据`
},
})
// 是否展开
const isExpanded = ref(false)
function toggleExpand() {
isExpanded.value = !isExpanded.value
}
async function exportMethod() {
const { data } = await props.getData({
pageNo: 1,
pageSize: 2000,
...props.queryItems,
})
return data
}
function remoteMethod(page, pageSize) {
pagination.page = page
pagination.pageSize = pageSize
handleQuery()
}
async function handleQuery() {
try {
@@ -111,8 +211,14 @@ async function handleQuery() {
})
tableData.value = data?.pageData || data
pagination.itemCount = data.total ?? data.length
if (pagination.itemCount && !tableData.value.length && pagination.page > 1) {
// 如果当前页数据为空且总条数不为0则返回上一页数据
onPageChange(pagination.page - 1)
}
return data
}
catch (error) {
console.error(error)
tableData.value = []
pagination.itemCount = 0
}
@@ -121,9 +227,14 @@ async function handleQuery() {
loading.value = false
}
}
function handleSearch() {
pagination.page = 1
handleQuery()
function handleSearch(keepCurrentPage = false) {
if (keepCurrentPage || !props.remote) {
handleQuery()
}
else {
onPageChange(1)
}
}
async function handleReset() {
const queryItems = { ...props.queryItems }
@@ -142,9 +253,7 @@ function onPageChange(currentPage) {
}
}
function onChecked(rowKeys) {
if (props.columns.some(item => item.type === 'selection')) {
emit('onChecked', rowKeys)
}
emit('onChecked', rowKeys)
}
function handleExport(columns = props.columns, data = tableData.value) {
if (!data?.length)

View File

@@ -1,3 +1,3 @@
export { default as MeModal } from './modal/index.vue'
export { default as MeCrud } from './crud/index.vue'
export { default as MeQueryItem } from './crud/QueryItem.vue'
export { default as MeModal } from './modal/index.vue'

View File

@@ -26,21 +26,22 @@
<!-- 底部按钮 -->
<template #footer>
<slot v-if="$slots.footer" name="footer" />
<footer v-else-if="modalOptions.showFooter" class="flex justify-end">
<n-button v-if="modalOptions.showCancel" @click="handleCancel()">
{{ modalOptions.cancelText }}
</n-button>
<n-button
v-if="modalOptions.showOk"
type="primary"
:loading="modalOptions.okLoading"
class="ml-20"
@click="handleOk()"
>
{{ modalOptions.okText }}
</n-button>
</footer>
<slot name="footer">
<footer v-if="modalOptions.showFooter" class="flex justify-end">
<n-button v-if="modalOptions.showCancel" @click="handleCancel()">
{{ modalOptions.cancelText }}
</n-button>
<n-button
v-if="modalOptions.showOk"
type="primary"
:loading="modalOptions.okLoading"
class="ml-20"
@click="handleOk()"
>
{{ modalOptions.okText }}
</n-button>
</footer>
</slot>
</template>
</n-card>
</n-modal>
@@ -144,11 +145,12 @@ async function handleOk(data) {
// 调用onOk函数传入data参数
const res = await modalOptions.value.onOk(data)
// 如果onOk函数的返回值不为false则关闭模态框
res !== false && close()
if (res !== false)
close()
}
catch (error) {
okLoading.value = false
console.error(error)
okLoading.value = false
}
}
@@ -163,11 +165,12 @@ async function handleCancel(data) {
const res = await modalOptions.value.onCancel(data)
// 如果onCancel函数的返回值不为false则关闭模态框
res !== false && close()
if (res !== false)
close()
}
catch (error) {
okLoading.value = false
console.error(error)
okLoading.value = false
}
}

View File

@@ -1,4 +1,4 @@
export * from './useAliveData'
export * from './useCrud'
export * from './useForm'
export * from './useModal'
export * from './useAliveData'

View File

@@ -80,6 +80,7 @@ export function useCrud({ name, initForm = {}, doCreate, doDelete, doUpdate, ref
data && refresh(data)
}
catch (error) {
console.error(error)
okLoading.value = false
return false
}
@@ -100,9 +101,10 @@ export function useCrud({ name, initForm = {}, doCreate, doDelete, doUpdate, ref
const data = await doDelete(id)
$message.success('删除成功')
d.loading = false
refresh(data)
refresh(data, true)
}
catch (error) {
console.error(error)
d.loading = false
}
},

View File

@@ -0,0 +1,13 @@
<template>
<i
class="mr-16 cursor-pointer"
:class="isFullscreen ? 'i-fe:minimize' : 'i-fe:maximize'"
@click="toggle"
/>
</template>
<script setup>
import { useFullscreen } from '@vueuse/core'
const { isFullscreen, toggle } = useFullscreen()
</script>

View File

@@ -59,7 +59,7 @@ function handleMenuSelect(key, item) {
}
</script>
<style lang="scss">
<style>
.side-menu:not(.n-menu--collapsed) {
.n-menu-item-content {
&::before {

View File

@@ -21,9 +21,9 @@
</template>
<script setup>
import { useAuthStore, usePermissionStore, useUserStore } from '@/store'
import { RoleSelect } from '@/layouts/components'
import api from '@/api'
import { RoleSelect } from '@/layouts/components'
import { useAuthStore, usePermissionStore, useUserStore } from '@/store'
const router = useRouter()
const userStore = useUserStore()

View File

@@ -1,7 +1,8 @@
export { default as RoleSelect } from './RoleSelect.vue'
export { default as UserAvatar } from './UserAvatar.vue'
export { default as MenuCollapse } from './MenuCollapse.vue'
export { default as BreadCrumb } from './BreadCrumb.vue'
export { default as AppTab } from './tab/index.vue'
export { default as Fullscreen } from './Fullscreen.vue'
export { default as MenuCollapse } from './MenuCollapse.vue'
export { default as RoleSelect } from './RoleSelect.vue'
export { default as SideLogo } from './SideLogo.vue'
export { default as SideMenu } from './SideMenu.vue'
export { default as AppTab } from './tab/index.vue'
export { default as UserAvatar } from './UserAvatar.vue'

View File

@@ -119,7 +119,8 @@ function handleHideDropdown() {
function handleSelect(key) {
const actionFn = actionMap.get(key)
actionFn && actionFn()
if (typeof actionFn === 'function')
actionFn()
handleHideDropdown()
}
</script>

View File

@@ -36,8 +36,8 @@
</template>
<script setup>
import ContextMenu from './ContextMenu.vue'
import { useTabStore } from '@/store'
import ContextMenu from './ContextMenu.vue'
const router = useRouter()
const tabStore = useTabStore()
@@ -74,7 +74,7 @@ async function handleContextMenu(e, tagItem) {
}
</script>
<style scoped lang="scss">
<style scoped>
:deep(.n-tabs) {
.n-tabs-tab {
padding-left: 16px;

View File

@@ -13,16 +13,9 @@
<BreadCrumb />
<div class="ml-auto flex flex-shrink-0 items-center px-12 text-18">
<i
class="mr-16 cursor-pointer"
:class="isDark ? 'i-fe:moon' : 'i-fe:sun'"
@click="toggleDark"
/>
<i
class="mr-16 cursor-pointer"
:class="isFullscreen ? 'i-fe:minimize' : 'i-fe:maximize'"
@click="toggle"
/>
<ToggleTheme />
<Fullscreen />
<i
class="i-fe:github mr-16 cursor-pointer"
@@ -41,18 +34,8 @@
</template>
<script setup>
import { useDark, useFullscreen, useToggle } from '@vueuse/core'
import { BreadCrumb, MenuCollapse, UserAvatar } from '@/layouts/components'
import { useAppStore } from '@/store'
const appStore = useAppStore()
const isDark = useDark()
function toggleDark() {
appStore.toggleDark()
useToggle(isDark)()
}
const { isFullscreen, toggle } = useFullscreen()
import { ToggleTheme } from '@/components'
import { BreadCrumb, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
function handleLinkClick(link) {
window.open(link)

View File

@@ -27,10 +27,10 @@
</template>
<script setup>
import SideBar from './sidebar/index.vue'
import AppHeader from './header/index.vue'
import { useAppStore } from '@/store'
import { AppTab } from '@/layouts/components'
import { useAppStore } from '@/store'
import AppHeader from './header/index.vue'
import SideBar from './sidebar/index.vue'
const appStore = useAppStore()
</script>

View File

@@ -15,16 +15,9 @@
<span class="mx-6 opacity-20">|</span>
<div class="flex flex-shrink-0 items-center px-12 text-18">
<i
class="mr-16 cursor-pointer"
:class="isDark ? 'i-fe:moon' : 'i-fe:sun'"
@click="toggleDark"
/>
<i
class="mr-16 cursor-pointer"
:class="isFullscreen ? 'i-fe:minimize' : 'i-fe:maximize'"
@click="toggle"
/>
<ToggleTheme />
<Fullscreen />
<i
class="i-fe:github mr-16 cursor-pointer"
@@ -43,24 +36,10 @@
</template>
<script setup>
import { useDark, useFullscreen, useToggle } from '@vueuse/core'
import { AppTab, MenuCollapse, UserAvatar } from '@/layouts/components'
import { useAppStore } from '@/store'
const appStore = useAppStore()
const isDark = useDark()
function toggleDark() {
appStore.toggleDark()
useToggle(isDark)()
}
const { isFullscreen, toggle } = useFullscreen()
import { ToggleTheme } from '@/components'
import { AppTab, Fullscreen, MenuCollapse, UserAvatar } from '@/layouts/components'
function handleLinkClick(link) {
window.open(link)
}
watchEffect(() => {
appStore.setThemeColor(appStore.primaryColor, appStore.isDark)
})
</script>

View File

@@ -24,9 +24,9 @@
</template>
<script setup>
import SideBar from './sidebar/index.vue'
import AppHeader from './header/index.vue'
import { useAppStore } from '@/store'
import AppHeader from './header/index.vue'
import SideBar from './sidebar/index.vue'
const appStore = useAppStore()
</script>

View File

@@ -23,8 +23,8 @@
</template>
<script setup>
import SideBar from './sidebar/index.vue'
import { useAppStore } from '@/store'
import SideBar from './sidebar/index.vue'
const appStore = useAppStore()
</script>

View File

@@ -8,19 +8,35 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import '@/styles/reset.css'
import '@/styles/global.scss'
import 'uno.css'
import ExcelJS from 'exceljs'
import { createApp } from 'vue'
import App from './App.vue'
import { setupDirectives } from './directives'
/**
* 引入vxe-table 和 vxe-pc-ui excel导出
*/
import VxeUI from 'vxe-pc-ui'
import VXETable from 'vxe-table'
import VXETablePluginExportXLSX from 'vxe-table-plugin-export-xlsx'
import { setupRouter } from './router'
import { setupStore } from './store'
import { setupNaiveDiscreteApi } from './utils'
import { setupDirectives } from './directives'
import 'vxe-pc-ui/lib/style.css'
import 'vxe-table/lib/style.css'
import '@/styles/reset.css'
import '@/styles/global.css'
import 'uno.css'
async function bootstrap() {
const app = createApp(App)
VXETable.use(VXETablePluginExportXLSX, {
ExcelJS,
})
app.use(VxeUI).use(VXETable)
setupStore(app)
setupDirectives(app)
await setupRouter(app)

View File

@@ -6,8 +6,8 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { useAuthStore, usePermissionStore, useUserStore } from '@/store'
import api from '@/api'
import { useAuthStore, usePermissionStore, useUserStore } from '@/store'
import { getPermissions, getUserInfo } from '@/store/helper'
const WHITE_LIST = ['/login', '/404']

View File

@@ -7,8 +7,8 @@
**********************************/
import { createRouter, createWebHashHistory, createWebHistory } from 'vue-router'
import { setupRouterGuards } from './guards'
import { basicRoutes } from './basic-routes'
import { setupRouterGuards } from './guards'
export const router = createRouter({
history:

View File

@@ -10,6 +10,9 @@ export const defaultLayout = 'normal'
export const defaultPrimaryColor = '#316C72'
// 控制 LayoutSetting 组件是否可见
export const layoutSettingVisible = true
export const naiveThemeOverrides = {
common: {
primaryColor: '#316C72FF',
@@ -33,7 +36,7 @@ export const basePermissions = [
code: 'ShowDocs',
name: '项目文档',
type: 'MENU',
path: 'https://docs.isme.top/web/#/624306705/188522224',
path: 'https://isme.top',
icon: 'i-me:docs',
order: 1,
enable: true,

View File

@@ -1,5 +1,5 @@
import { basePermissions } from '@/settings'
import api from '@/api'
import { basePermissions } from '@/settings'
export async function getUserInfo() {
const res = await api.getUser()

View File

@@ -6,10 +6,11 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { defineStore } from 'pinia'
import { useDark } from '@vueuse/core'
import { generate, getRgbStr } from '@arco-design/color'
import { defaultLayout, defaultPrimaryColor, naiveThemeOverrides } from '@/settings'
import { generate, getRgbStr } from '@arco-design/color'
import { useDark } from '@vueuse/core'
import { defineStore } from 'pinia'
import { VxeUI } from 'vxe-pc-ui'
export const useAppStore = defineStore('app', {
state: () => ({
@@ -40,6 +41,18 @@ export const useAppStore = defineStore('app', {
list: true,
dark: isDark,
})
VxeUI.setTheme(isDark ? 'dark' : 'light')
if (isDark) {
document.body.style.setProperty('--vxe-ui-font-color', '#fff')
}
else {
document.body.style.setProperty('--vxe-ui-font-color', '#000')
}
document.body.style.setProperty('--vxe-ui-font-primary-color', color)
document.body.style.setProperty('--vxe-ui-font-primary-lighten-color', color)
document.body.style.setProperty('--vxe-ui-font-primary-darken-color', color)
document.body.style.setProperty('--primary-color', getRgbStr(colors[5]))
this.naiveThemeOverrides.common = Object.assign(this.naiveThemeOverrides.common || {}, {
primaryColor: colors[5],

View File

@@ -6,8 +6,8 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { defineStore } from 'pinia'
import { usePermissionStore, useRouterStore, useTabStore, useUserStore } from '@/store'
import { defineStore } from 'pinia'
export const useAuthStore = defineStore('auth', {
state: () => ({

View File

@@ -1,6 +1,6 @@
export * from './app'
export * from './auth'
export * from './permission'
export * from './router'
export * from './tab'
export * from './user'
export * from './router'

View File

@@ -6,9 +6,9 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { isExternal } from '@/utils'
import { hyphenate } from '@vueuse/core'
import { defineStore } from 'pinia'
import { isExternal } from '@/utils'
export const usePermissionStore = defineStore('permission', {
state: () => ({

View File

@@ -26,12 +26,12 @@ body {
.fade-slide-enter-from {
opacity: 0;
transform: translateX(-30px);
transform: translateX(-2%);
}
.fade-slide-leave-to {
opacity: 0;
transform: translateX(30px);
transform: translateX(2%);
}
/* 自定义滚动条样式 */
@@ -72,3 +72,20 @@ body {
}
}
}
/* 切换主题的动画效果 */
::view-transition-old(root),
::view-transition-new(root) {
animation: none;
mix-blend-mode: normal;
}
::view-transition-old(root),
.dark::view-transition-new(root) {
z-index: 1;
}
::view-transition-new(root),
.dark::view-transition-old(root) {
z-index: 9999;
}

View File

@@ -1,49 +1,388 @@
/**********************************
* @Author: Ronnie Zhang
* @LastEditor: Ronnie Zhang
* @LastEditTime: 2023/12/05 21:26:38
* @Email: zclzone@outlook.com
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
html {
box-sizing: border-box;
}
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
2. [UnoCSS]: allow to override the default border color with css var `--un-default-border-color`
*/
*,
::before,
::after {
margin: 0;
padding: 0;
box-sizing: inherit;
box-sizing: border-box; /* 1 */
border-width: 0; /* 2 */
border-style: solid; /* 2 */
border-color: var(--un-default-border-color, #e5e7eb); /* 2 */
}
::before,
::after {
--un-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS.
*/
html,
:host {
line-height: 1.5; /* 1 */
-webkit-text-size-adjust: 100%; /* 2 */
-moz-tab-size: 4; /* 3 */
tab-size: 4; /* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol',
'Noto Color Emoji'; /* 4 */
font-feature-settings: normal; /* 5 */
font-variation-settings: normal; /* 6 */
-webkit-tap-highlight-color: transparent; /* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0; /* 1 */
line-height: inherit; /* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0; /* 1 */
color: inherit; /* 2 */
border-top-width: 1px; /* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
text-decoration: none;
color: inherit;
text-decoration: inherit;
}
a:hover,
a:link,
a:visited,
a:active {
text-decoration: none;
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; /* 1 */
font-feature-settings: normal; /* 2 */
font-variation-settings: normal; /* 3 */
font-size: 1em; /* 4 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0; /* 1 */
border-color: inherit; /* 2 */
border-collapse: collapse; /* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit; /* 1 */
font-feature-settings: inherit; /* 1 */
font-variation-settings: inherit; /* 1 */
font-size: 100%; /* 1 */
font-weight: inherit; /* 1 */
line-height: inherit; /* 1 */
color: inherit; /* 1 */
margin: 0; /* 2 */
padding: 0; /* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button; /* 1 */
/* background-color: transparent; */
background-image: none; /* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield; /* 1 */
outline-offset: -2px; /* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button; /* 1 */
font: inherit; /* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul {
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
input,
textarea {
outline: none;
border: none;
resize: none;
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::placeholder,
textarea::placeholder {
opacity: 1; /* 1 */
color: #9ca3af; /* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role='button'] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block; /* 1 */
vertical-align: middle; /* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/*
Make elements with the HTML hidden attribute stay hidden by default.
*/
[hidden] {
display: none;
}

View File

@@ -10,10 +10,10 @@
import { useAuthStore } from '@/store'
let isConfirming = false
export function resolveResError(code, message) {
export function resolveResError(code, message, needTip = true) {
switch (code) {
case 401:
if (isConfirming)
if (isConfirming || !needTip)
return
isConfirming = true
$dialog.confirm({
@@ -32,7 +32,7 @@ export function resolveResError(code, message) {
return false
case 11007:
case 11008:
if (isConfirming)
if (isConfirming || !needTip)
return
isConfirming = true
$dialog.confirm({
@@ -62,5 +62,6 @@ export function resolveResError(code, message) {
message = message ?? `${code}】: 未知异常!`
break
}
needTip && window.$message?.error(message)
return message
}

View File

@@ -7,29 +7,10 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { resolveResError } from './helpers'
import { useAuthStore } from '@/store'
import { resolveResError } from './helpers'
export function setupInterceptors(axiosInstance) {
function reqResolve(config) {
// 处理不需要token的请求
if (config.noNeedToken) {
return config
}
const { accessToken } = useAuthStore()
if (accessToken) {
// token: Bearer + xxx
config.headers.Authorization = `Bearer ${accessToken}`
}
return config
}
function reqReject(error) {
return Promise.reject(error)
}
const SUCCESS_CODES = [0, 200]
function resResolve(response) {
const { data, status, config, statusText, headers } = response
@@ -39,34 +20,51 @@ export function setupInterceptors(axiosInstance) {
}
const code = data?.code ?? status
// 根据code处理对应的操作并返回处理后的message
const message = resolveResError(code, data?.message ?? statusText)
const needTip = config?.needTip !== false
// 根据code处理对应的操作并返回处理后的message
const message = resolveResError(code, data?.message ?? statusText, needTip)
// 需要错误提醒
!config?.noNeedTip && message && window.$message?.error(message)
return Promise.reject({ code, message, error: data ?? response })
}
return Promise.resolve(data ?? response)
}
async function resReject(error) {
if (!error || !error.response) {
const code = error?.code
/** 根据code处理对应的操作并返回处理后的message */
const message = resolveResError(code, error.message)
window.$message?.error(message)
return Promise.reject({ code, message, error })
}
const { data, status, config } = error.response
const code = data?.code ?? status
const message = resolveResError(code, data?.message ?? error.message)
/** 需要错误提醒 */
!config?.noNeedTip && message && window.$message?.error(message)
return Promise.reject({ code, message, error: error.response?.data || error.response })
}
axiosInstance.interceptors.request.use(reqResolve, reqReject)
axiosInstance.interceptors.response.use(resResolve, resReject)
}
function reqResolve(config) {
// 处理不需要token的请求
if (config.needToken === false) {
return config
}
const { accessToken } = useAuthStore()
if (accessToken) {
// token: Bearer + xxx
config.headers.Authorization = `Bearer ${accessToken}`
}
return config
}
function reqReject(error) {
return Promise.reject(error)
}
async function resReject(error) {
if (!error || !error.response) {
const code = error?.code
/** 根据code处理对应的操作并返回处理后的message */
const message = resolveResError(code, error.message)
return Promise.reject({ code, message, error })
}
const { data, status, config } = error.response
const code = data?.code ?? status
const needTip = config?.needTip !== false
const message = resolveResError(code, data?.message ?? error.message, needTip)
return Promise.reject({ code, message, error: error.response?.data || error.response })
}

View File

@@ -7,9 +7,9 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import * as NaiveUI from 'naive-ui'
import { isNullOrUndef } from '@/utils'
import { useAppStore } from '@/store'
import { isNullOrUndef } from '@/utils'
import * as NaiveUI from 'naive-ui'
export function setupMessage(NMessage) {
class Message {

View File

@@ -47,6 +47,7 @@ class Storage {
return def
}
catch (error) {
console.error(error)
this.remove(key)
return def
}

View File

@@ -22,8 +22,8 @@
<script setup>
import { MeModal } from '@/components'
import { sleep } from '@/utils'
import { useModal } from '@/composables'
import { sleep } from '@/utils'
const text = ref('')
const [$modal1, okLoading1] = useModal()

View File

@@ -9,15 +9,24 @@
<template>
<CommonPage show-footer>
<template #title-suffix>
<n-tag class="ml-12" type="primary">
feather图标集 + isme自定义图标
</n-tag>
<n-tooltip
placement="bottom"
trigger="hover"
>
<template #trigger>
<a href="https://juejin.cn/post/7394789388154241033" target="_blank" class="ml-12 flex cursor-pointer items-center hover:underline">
<i class="i-simple-icons:juejin text-#1E80FF" />
<span class="ml-4">Unocss 图标</span>
</a>
</template>
点击查看如何使用 Unocss 图标
</n-tooltip>
</template>
<ul class="flex flex-wrap justify-between">
<ul class="grid grid-cols-[repeat(auto-fill,minmax(160px,1fr))] justify-items-center gap-16">
<li
v-for="item in icons"
:key="item"
class="m-16 w-160 f-c-c flex-col cursor-pointer rounded-12 px-12 py-24 card-border auto-bg"
class="w-160 f-c-c flex-col cursor-pointer rounded-12 px-12 py-24 card-border auto-bg"
@click="copy(`&lt;i class=&quot;${item}&quot; /&gt;`)"
>
<i :class="`${item}?mask`" class="text-28 text-gray-600 hover:bg-primary" />
@@ -28,13 +37,6 @@
{{ item }}
</span>
</li>
<li class="mx-16 h-0 w-160" />
<li class="mx-16 h-0 w-160" />
<li class="mx-16 h-0 w-160" />
<li class="mx-16 h-0 w-160" />
<li class="mx-16 h-0 w-160" />
<li class="mx-16 h-0 w-160" />
<li class="mx-16 h-0 w-160" />
</ul>
</CommonPage>
</template>
@@ -46,6 +48,7 @@ import icons from 'isme:icons'
const { copy, copied } = useClipboard()
watch(copied, (val) => {
val && $message.success('已复制到剪切板')
if (val)
$message.success('已复制到剪切板')
})
</script>

View File

@@ -63,14 +63,14 @@ defineOptions({ name: 'ImgUpload' })
const { copy, copied } = useClipboard()
const imgList = reactive([
{ url: 'https://cdn.isme.top/images/5c23d52f880511ebb6edd017c2d2eca2.jpg' },
{ url: 'https://cdn.isme.top/images/5c23d52f880511ebb6edd017c2d2eca2.jpg' },
{ url: 'https://cdn.isme.top/images/5c23d52f880511ebb6edd017c2d2eca2.jpg' },
{ url: 'https://cdn.isme.top/images/5c23d52f880511ebb6edd017c2d2eca2.jpg' },
{ url: 'https://img.isme.top/isme/67208863145ef.jpg' },
{ url: 'https://img.isme.top/isme/67208ab2a9de0.jpg' },
{ url: 'https://img.isme.top/isme/67208ab4c6596.jpg' },
])
watch(copied, (val) => {
val && $message.success('已复制到剪切板')
if (val)
$message.success('已复制到剪切板')
})
function onBeforeUpload({ file }) {

View File

@@ -49,7 +49,7 @@
type="primary"
ghost
tag="a"
href="https://docs.isme.top/web/#/624306705/188522224"
href="https://isme.top"
target="__blank"
>
开发文档
@@ -160,13 +160,13 @@
</template>
<script setup>
import * as echarts from 'echarts/core'
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import { useUserStore } from '@/store'
import { BarChart, LineChart, PieChart } from 'echarts/charts'
import { GridComponent, LegendComponent, TooltipComponent } from 'echarts/components'
import * as echarts from 'echarts/core'
import { UniversalTransition } from 'echarts/features'
import { CanvasRenderer } from 'echarts/renderers'
import VChart from 'vue-echarts'
import { useUserStore } from '@/store'
const userStore = useUserStore()

View File

@@ -10,6 +10,6 @@ import { request } from '@/utils'
export default {
toggleRole: data => request.post('/auth/role/toggle', data),
login: data => request.post('/auth/login', data, { noNeedToken: true }),
login: data => request.post('/auth/login', data, { needToken: false }),
getUser: () => request.get('/user/detail'),
}

View File

@@ -101,10 +101,10 @@
</template>
<script setup>
import { useAuthStore } from '@/store'
import { lStorage, throttle } from '@/utils'
import { useStorage } from '@vueuse/core'
import api from './api'
import { lStorage, throttle } from '@/utils'
import { useAuthStore } from '@/store'
const authStore = useAuthStore()
const router = useRouter()

View File

@@ -6,8 +6,8 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import axios from 'axios'
import { request } from '@/utils'
import axios from 'axios'
export default {
getMenuTree: () => request.get('/permission/menu/tree'),

View File

@@ -28,8 +28,8 @@
:on-update:selected-keys="onSelect"
key-field="code"
label-field="name"
default-expand-all
block-line
block-line default-expand-all
/>
</n-space>
@@ -38,8 +38,8 @@
</template>
<script setup>
import { withModifiers } from 'vue'
import { NButton } from 'naive-ui'
import { withModifiers } from 'vue'
import api from '../api'
import ResAddOrEdit from './ResAddOrEdit.vue'
@@ -115,6 +115,7 @@ function handleDelete(item) {
emit('update:currentMenu', null)
}
catch (error) {
console.error(error)
$message.destroy('deleteMenu')
}
},

View File

@@ -159,12 +159,12 @@
</template>
<script setup>
import pagePathes from 'isme:page-pathes'
import icons from 'isme:icons'
import api from '../api'
import QuestionLabel from './QuestionLabel.vue'
import { MeModal } from '@/components'
import { useForm, useModal } from '@/composables'
import icons from 'isme:icons'
import pagePathes from 'isme:page-pathes'
import api from '../api'
import QuestionLabel from './QuestionLabel.vue'
const props = defineProps({
menus: {

View File

@@ -11,7 +11,7 @@
<div class="flex">
<n-spin size="small" :show="treeLoading">
<MenuTree
v-model:currentMenu="currentMenu"
v-model:current-menu="currentMenu"
class="w-320 shrink-0"
:tree-data="treeData"
@refresh="initData"
@@ -81,6 +81,7 @@
:columns="btnsColumns"
:scroll-x="-1"
:get-data="api.getButtons"
:computed-height="300"
:query-items="{ parentId: currentMenu.id }"
/>
</template>
@@ -92,11 +93,11 @@
</template>
<script setup>
import { MeCrud } from '@/components'
import { NButton, NSwitch } from 'naive-ui'
import api from './api'
import MenuTree from './components/MenuTree.vue'
import ResAddOrEdit from './components/ResAddOrEdit.vue'
import api from './api'
import { MeCrud } from '@/components'
const treeData = ref([])
const treeLoading = ref(false)
@@ -231,6 +232,7 @@ function handleDeleteBtn(id) {
d.loading = false
}
catch (error) {
console.error(error)
d.loading = false
}
},
@@ -248,6 +250,7 @@ async function handleEnable(item) {
item.enableLoading = false
}
catch (error) {
console.error(error)
item.enableLoading = false
}
}

View File

@@ -74,9 +74,8 @@
:data="permissionTree"
:checked-keys="modalForm.permissionIds"
:on-update:checked-keys="(keys) => (modalForm.permissionIds = keys)"
checkable
check-on-click
default-expand-all
default-expand-all checkable check-on-click
class="cus-scroll max-h-200 w-full"
/>
</n-form-item>
@@ -96,10 +95,10 @@
</template>
<script setup>
import { NButton, NSwitch } from 'naive-ui'
import api from './api'
import { MeCrud, MeModal, MeQueryItem } from '@/components'
import { useCrud } from '@/composables'
import { NButton, NSwitch } from 'naive-ui'
import api from './api'
defineOptions({ name: 'RoleMgt' })
@@ -120,7 +119,7 @@ const { modalRef, modalFormRef, modalAction, modalForm, handleAdd, handleDelete,
doDelete: api.delete,
doUpdate: api.update,
initForm: { enable: true },
refresh: () => $table.value?.handleSearch(),
refresh: (_, keepCurrentPage) => $table.value?.handleSearch(keepCurrentPage),
})
const columns = [
@@ -211,6 +210,7 @@ async function handleEnable(row) {
$table.value?.handleSearch()
}
catch (error) {
console.error(error)
row.enableLoading = false
}
}

View File

@@ -37,6 +37,7 @@
:scroll-x="1200"
:columns="columns"
:get-data="api.getAllUsers"
:show-check="true"
@on-checked="onChecked"
>
<MeQueryItem label="用户名" :label-width="50">
@@ -67,11 +68,11 @@
</template>
<script setup>
import { MeCrud, MeQueryItem } from '@/components'
import { formatDateTime } from '@/utils'
import { NAvatar, NButton, NSwitch, NTag } from 'naive-ui'
import { h } from 'vue'
import api from './api'
import { MeCrud, MeQueryItem } from '@/components'
import { formatDateTime } from '@/utils'
defineOptions({ name: 'RoleUser' })
const route = useRoute()
@@ -90,7 +91,7 @@ const genders = [
]
const columns = [
{ type: 'selection', fixed: 'left' },
// { type: 'selection', fixed: 'left' },
{
title: '头像',
key: 'avatar',
@@ -101,11 +102,10 @@ const columns = [
src: avatar,
}),
},
{ title: '用户名', key: 'username', width: 150, ellipsis: { tooltip: true } },
{ title: '用户名', key: 'username', ellipsis: { tooltip: true } },
{
title: '角色',
key: 'roles',
width: 200,
ellipsis: { tooltip: true },
render: ({ roles }) => {
if (roles?.length) {
@@ -156,7 +156,7 @@ const columns = [
{
title: '操作',
key: 'actions',
width: 100,
width: 120,
align: 'right',
fixed: 'right',
hideInExcel: true,

View File

@@ -110,11 +110,11 @@
</template>
<script setup>
import { NAvatar, NButton, NSwitch, NTag } from 'naive-ui'
import api from './api'
import { formatDateTime } from '@/utils'
import { MeCrud, MeModal, MeQueryItem } from '@/components'
import { useCrud } from '@/composables'
import { formatDateTime } from '@/utils'
import { NAvatar, NButton, NSwitch, NTag } from 'naive-ui'
import api from './api'
defineOptions({ name: 'UserMgt' })
@@ -162,11 +162,10 @@ const columns = [
src: avatar,
}),
},
{ title: '用户名', key: 'username', width: 150, ellipsis: { tooltip: true } },
{ title: '用户名', key: 'username', ellipsis: { tooltip: true } },
{
title: '角色',
key: 'roles',
width: 200,
ellipsis: { tooltip: true },
render: ({ roles }) => {
if (roles?.length) {
@@ -279,6 +278,7 @@ async function handleEnable(row) {
$table.value?.handleSearch()
}
catch (error) {
console.error(error)
row.enableLoading = false
}
}

View File

@@ -105,11 +105,11 @@
</template>
<script setup>
import api from './api'
import { MeModal } from '@/components'
import { useForm, useModal } from '@/composables'
import { useUserStore } from '@/store'
import { getUserInfo } from '@/store/helper'
import api from './api'
const userStore = useUserStore()
const required = {

View File

@@ -6,10 +6,10 @@
* Copyright © 2023 Ronnie Zhang(大脸怪) | https://isme.top
**********************************/
import { defineConfig, presetAttributify, presetIcons, presetUno } from 'unocss'
import presetRemToPx from '@unocss/preset-rem-to-px'
import { FileSystemIconLoader } from '@iconify/utils/lib/loader/node-loaders'
import { getIcons } from './build'
import presetRemToPx from '@unocss/preset-rem-to-px'
import { defineConfig, presetAttributify, presetIcons, presetUno } from 'unocss'
import { getIcons } from './build/index.js'
const icons = getIcons()
export default defineConfig({
@@ -21,6 +21,8 @@ export default defineConfig({
prefix: ['i-'],
extraProperties: {
display: 'inline-block',
width: '1em',
height: '1em',
},
collections: {
me: FileSystemIconLoader('./src/assets/icons/isme'),

View File

@@ -7,14 +7,15 @@
**********************************/
import path from 'node:path'
import { defineConfig, loadEnv } from 'vite'
import Vue from '@vitejs/plugin-vue'
import VueDevTools from 'vite-plugin-vue-devtools'
import VueJsx from '@vitejs/plugin-vue-jsx'
import Unocss from 'unocss/vite'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { NaiveUiResolver } from 'unplugin-vue-components/resolvers'
import Components from 'unplugin-vue-components/vite'
import { defineConfig, loadEnv } from 'vite'
import removeNoMatch from 'vite-plugin-router-warn'
import VueDevTools from 'vite-plugin-vue-devtools'
import { pluginIcons, pluginPagePathes } from './build/plugin-isme'
export default defineConfig(({ mode }) => {
@@ -25,6 +26,7 @@ export default defineConfig(({ mode }) => {
base: VITE_PUBLIC_PATH || '/',
plugins: [
Vue(),
VueJsx(),
VueDevTools(),
Unocss(),
AutoImport({