Merge branch 'master' into release
This commit is contained in:
commit
fa1735fbdb
10
README.md
10
README.md
|
@ -31,11 +31,7 @@ If you need a basic template, please switch to the `mini` branch. `mini` simply
|
|||
- [vue-element-plus-admin](https://element-plus-admin.cn/) - Full version of the github site
|
||||
- [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - Full version of the gitee site
|
||||
|
||||
account: **admin/admin test/test**
|
||||
|
||||
`admin` account is used to simulate the control permission of the server, and render whatever the server returns
|
||||
|
||||
`test` account is used to simulate the front-end control authority. The server only returns the menu key to be displayed, and the front-end performs matching rendering
|
||||
account: **admin/admin**
|
||||
|
||||
Online examples do not apply to menu filtering by default, but directly use Static routing
|
||||
|
||||
|
@ -133,11 +129,13 @@ Support modern browsers, not IE
|
|||
|
||||
If you find this project helpful, welcome sponsorship to show your support~
|
||||
|
||||
[Paypal Me](https://www.paypal.com/paypalme/ckl94)
|
||||
|
||||
<img src="https://github.com/kailong321200875/my-image/raw/master/pay.jpg" />
|
||||
|
||||
## Group
|
||||
|
||||
<img src="https://github.com/kailong321200875/my-image/raw/master/chat-0820.jpg" />
|
||||
<img src="https://github.com/kailong321200875/my-image/raw/master/chat-0903.jpg" />
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
@ -31,11 +31,7 @@ vue-element-plus-admin 的定位是后台集成方案,不太适合当基础模
|
|||
- [vue-element-plus-admin](https://element-plus-admin.cn/) - 完整版 github 站点
|
||||
- [vue-element-plus-admin](https://kailong110120130.gitee.io/vue-element-plus-admin) - 完整版 gitee 站点
|
||||
|
||||
帐号:**admin/admin test/test**
|
||||
|
||||
`admin` 帐号用于模拟服务端控制权限,服务端返回什么就渲染什么
|
||||
|
||||
`test` 帐号用于模拟前端控制权限,服务端只返回需要显示的菜单 key,前端进行匹配渲染
|
||||
帐号:**admin/admin**
|
||||
|
||||
在线例子默认不适用菜单过滤,而是直接使用静态路由表
|
||||
|
||||
|
@ -133,11 +129,13 @@ pnpm run build:pro
|
|||
|
||||
如果你觉得这个项目有帮助,欢迎赞助以示支持~
|
||||
|
||||
[Paypal Me](https://www.paypal.com/paypalme/ckl94)
|
||||
|
||||
<img src="https://gitee.com/kailong110120130/my-image/raw/master/pay.jpg" />
|
||||
|
||||
## 交流群
|
||||
|
||||
<img src="https://gitee.com/kailong110120130/my-image/raw/master/chat-0820.jpg" />
|
||||
<img src="https://gitee.com/kailong110120130/my-image/raw/master/chat-0903.jpg" />
|
||||
|
||||
## 许可证
|
||||
|
||||
|
|
|
@ -168,8 +168,10 @@ export default [
|
|||
const ids = body.ids
|
||||
if (!ids) {
|
||||
return {
|
||||
code: '500',
|
||||
message: '请选择需要删除的数据'
|
||||
data: {
|
||||
code: 500,
|
||||
message: '请选择需要删除的数据'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
|
@ -203,8 +205,10 @@ export default [
|
|||
const ids = body.ids
|
||||
if (!ids) {
|
||||
return {
|
||||
code: '500',
|
||||
message: '请选择需要删除的数据'
|
||||
data: {
|
||||
code: 500,
|
||||
message: '请选择需要删除的数据'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
|
|
|
@ -179,6 +179,14 @@ const adminList = [
|
|||
meta: {
|
||||
title: 'router.richText'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'json-editor',
|
||||
component: 'views/Components/Editor/JsonEditor',
|
||||
name: 'JsonEditor',
|
||||
meta: {
|
||||
title: 'router.jsonEditor'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -323,21 +331,29 @@ const adminList = [
|
|||
}
|
||||
},
|
||||
{
|
||||
path: 'useOpenTab',
|
||||
component: 'views/hooks/useOpenTab',
|
||||
name: 'UseOpenTab',
|
||||
path: 'useTagsView',
|
||||
component: 'views/hooks/useTagsView',
|
||||
name: 'UseTagsView',
|
||||
meta: {
|
||||
title: 'useOpenTab'
|
||||
title: 'useTagsView'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'useValidator',
|
||||
component: 'views/hooks/useValidator',
|
||||
name: 'UseValidator',
|
||||
meta: {
|
||||
title: 'useValidator'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'useCrudSchemas',
|
||||
component: 'views/hooks/useCrudSchemas',
|
||||
name: 'UseCrudSchemas',
|
||||
meta: {
|
||||
title: 'useCrudSchemas'
|
||||
}
|
||||
}
|
||||
// {
|
||||
// path: 'useCrudSchemas',
|
||||
// component: 'views/hooks/useCrudSchemas',
|
||||
// name: 'UseCrudSchemas',
|
||||
// meta: {
|
||||
// title: 'useCrudSchemas'
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
@ -580,6 +596,7 @@ const testList: string[] = [
|
|||
'/components/table/ref-table',
|
||||
'/components/editor-demo',
|
||||
'/components/editor-demo/editor',
|
||||
'/components/editor-demo/json-editor',
|
||||
'/components/search',
|
||||
'/components/descriptions',
|
||||
'/components/image-viewer',
|
||||
|
@ -597,8 +614,9 @@ const testList: string[] = [
|
|||
'/function/multiple-tabs-demo/:id',
|
||||
'/hooks',
|
||||
'/hooks/useWatermark',
|
||||
'/hooks/useOpenTab',
|
||||
// '/hooks/useCrudSchemas',
|
||||
'/hooks/useTagsView',
|
||||
'/hooks/useValidator',
|
||||
'/hooks/useCrudSchemas',
|
||||
'/level',
|
||||
'/level/menu1',
|
||||
'/level/menu1/menu1-1',
|
||||
|
@ -1052,12 +1070,41 @@ export default [
|
|||
url: '/role/list',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: ({ query }) => {
|
||||
const { roleName } = query
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: roleName === 'admin' ? adminList : testList
|
||||
data: adminList
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
url: '/role/table',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: {
|
||||
list: List,
|
||||
total: 4
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 列表接口
|
||||
{
|
||||
url: '/role/list2',
|
||||
method: 'get',
|
||||
timeout,
|
||||
response: () => {
|
||||
return {
|
||||
data: {
|
||||
code: code,
|
||||
data: testList
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -246,8 +246,10 @@ export default [
|
|||
const ids = body.ids
|
||||
if (!ids) {
|
||||
return {
|
||||
code: '500',
|
||||
message: '请选择需要删除的数据'
|
||||
data: {
|
||||
code: 500,
|
||||
message: '请选择需要删除的数据'
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let i = List.length
|
||||
|
|
|
@ -76,8 +76,10 @@ export default [
|
|||
}
|
||||
if (!hasUser) {
|
||||
return {
|
||||
code: 500,
|
||||
message: '账号或密码错误'
|
||||
data: {
|
||||
code: 500,
|
||||
message: '账号或密码错误'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
56
package.json
56
package.json
|
@ -28,22 +28,22 @@
|
|||
"dependencies": {
|
||||
"@iconify/iconify": "^3.1.1",
|
||||
"@iconify/vue": "^4.1.1",
|
||||
"@vueuse/core": "^10.2.1",
|
||||
"@vueuse/core": "^10.3.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.10",
|
||||
"@zxcvbn-ts/core": "^3.0.3",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.4.0",
|
||||
"dayjs": "^1.11.9",
|
||||
"driver.js": "^1.2.1",
|
||||
"echarts": "^5.4.3",
|
||||
"echarts-wordcloud": "^2.1.0",
|
||||
"element-plus": "^2.3.8",
|
||||
"intro.js": "^7.0.1",
|
||||
"element-plus": "^2.3.9",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"mockjs": "^1.1.0",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.4",
|
||||
"pinia": "^2.1.6",
|
||||
"pinia-plugin-persist": "^1.0.0",
|
||||
"qrcode": "^1.5.3",
|
||||
"qs": "^6.11.2",
|
||||
|
@ -51,56 +51,54 @@
|
|||
"url": "^0.11.1",
|
||||
"vue": "3.3.4",
|
||||
"vue-i18n": "9.2.2",
|
||||
"vue-json-pretty": "^2.2.4",
|
||||
"vue-router": "^4.2.4",
|
||||
"vue-types": "^5.1.0"
|
||||
"vue-types": "^5.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^17.6.7",
|
||||
"@commitlint/config-conventional": "^17.6.7",
|
||||
"@iconify/json": "^2.2.92",
|
||||
"@commitlint/cli": "^17.7.1",
|
||||
"@commitlint/config-conventional": "^17.7.0",
|
||||
"@iconify/json": "^2.2.101",
|
||||
"@intlify/unplugin-vue-i18n": "^0.12.2",
|
||||
"@purge-icons/generated": "^0.9.0",
|
||||
"@types/intro.js": "^5.1.1",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/node": "^20.4.2",
|
||||
"@types/node": "^20.4.10",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/qrcode": "^1.5.1",
|
||||
"@types/qs": "^6.9.7",
|
||||
"@types/sortablejs": "^1.15.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
||||
"@typescript-eslint/parser": "^6.1.0",
|
||||
"@unocss/transformer-variant-group": "^0.53.5",
|
||||
"@vitejs/plugin-legacy": "^4.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.3.0",
|
||||
"@typescript-eslint/parser": "^6.3.0",
|
||||
"@unocss/transformer-variant-group": "^0.55.0",
|
||||
"@vitejs/plugin-legacy": "^4.1.1",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"@vue-macros/volar": "^0.12.2",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"consola": "^3.2.3",
|
||||
"eslint": "^8.45.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-define-config": "^1.21.0",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-define-config": "^1.23.0",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-vue": "^9.15.1",
|
||||
"eslint-plugin-vue": "^9.17.0",
|
||||
"husky": "^8.0.3",
|
||||
"less": "^4.1.3",
|
||||
"less": "^4.2.0",
|
||||
"lint-staged": "^13.2.3",
|
||||
"plop": "^3.1.2",
|
||||
"postcss": "^8.4.26",
|
||||
"postcss": "^8.4.27",
|
||||
"postcss-html": "^1.5.0",
|
||||
"postcss-less": "^6.0.0",
|
||||
"prettier": "^3.0.0",
|
||||
"prettier": "^3.0.1",
|
||||
"rimraf": "^5.0.1",
|
||||
"rollup": "^3.26.3",
|
||||
"stylelint": "^15.10.1",
|
||||
"rollup": "^3.28.0",
|
||||
"stylelint": "^15.10.2",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recommended": "^13.0.0",
|
||||
"stylelint-config-standard": "^34.0.0",
|
||||
"stylelint-order": "^6.0.3",
|
||||
"terser": "^5.19.1",
|
||||
"terser": "^5.19.2",
|
||||
"typescript": "5.1.6",
|
||||
"unocss": "^0.53.5",
|
||||
"unplugin-vue-define-options": "^1.3.11",
|
||||
"vite": "4.4.4",
|
||||
"unocss": "^0.55.0",
|
||||
"vite": "4.4.9",
|
||||
"vite-plugin-ejs": "^1.6.4",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-mock": "2.9.6",
|
||||
|
@ -108,7 +106,7 @@
|
|||
"vite-plugin-purge-icons": "^0.9.2",
|
||||
"vite-plugin-style-import": "2.0.0",
|
||||
"vite-plugin-svg-icons": "^2.0.1",
|
||||
"vue-tsc": "^1.8.5"
|
||||
"vue-tsc": "^1.8.8"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
|
|
|
@ -30,5 +30,5 @@ export const getAdminRoleApi = (
|
|||
}
|
||||
|
||||
export const getTestRoleApi = (params: RoleParams): Promise<IResponse<string[]>> => {
|
||||
return request.get({ url: '/role/list', params })
|
||||
return request.get({ url: '/role/list2', params })
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import { useRouter } from 'vue-router'
|
|||
import { usePermissionStore } from '@/store/modules/permission'
|
||||
import { filterBreadcrumb } from './helper'
|
||||
import { filter, treeToList } from '@/utils/tree'
|
||||
import type { RouteLocationNormalizedLoaded, RouteMeta } from 'vue-router'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { Icon } from '@/components/Icon'
|
||||
import { useAppStore } from '@/store/modules/app'
|
||||
|
@ -47,15 +47,15 @@ export default defineComponent({
|
|||
const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))
|
||||
return breadcrumbList.map((v) => {
|
||||
const disabled = !v.redirect || v.redirect === 'noredirect'
|
||||
const meta = v.meta as RouteMeta
|
||||
const meta = v.meta
|
||||
return (
|
||||
<ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>
|
||||
{meta?.icon && breadcrumbIcon.value ? (
|
||||
<>
|
||||
<Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title)}
|
||||
<Icon icon={meta.icon} class="mr-[5px]"></Icon> {t(v?.meta?.title || '')}
|
||||
</>
|
||||
) : (
|
||||
t(v?.meta?.title)
|
||||
t(v?.meta?.title || '')
|
||||
)}
|
||||
</ElBreadcrumbItem>
|
||||
)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { pathResolve } from '@/utils/routerHelper'
|
||||
import type { RouteMeta } from 'vue-router'
|
||||
|
||||
export const filterBreadcrumb = (
|
||||
routes: AppRouteRecordRaw[],
|
||||
|
@ -8,7 +7,7 @@ export const filterBreadcrumb = (
|
|||
const res: AppRouteRecordRaw[] = []
|
||||
|
||||
for (const route of routes) {
|
||||
const meta = route?.meta as RouteMeta
|
||||
const meta = route?.meta
|
||||
if (meta.hidden && !meta.canTo) {
|
||||
continue
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ export default defineComponent({
|
|||
) : null}
|
||||
|
||||
<ElCollapseTransition>
|
||||
<div v-show={unref(show)} class={[`${prefixCls}-content`]}>
|
||||
<div v-show={unref(show)} class={[`${prefixCls}-content`, 'p-20px']}>
|
||||
<ElDescriptions {...unref(getBindValue)}>
|
||||
{{
|
||||
extra: () => (slots['extra'] ? slots['extra']() : props.extra),
|
||||
|
|
|
@ -22,6 +22,7 @@ import {
|
|||
} from 'element-plus'
|
||||
import { InputPassword } from '@/components/InputPassword'
|
||||
import { Editor } from '@/components/Editor'
|
||||
import { JsonEditor } from '@/components/JsonEditor'
|
||||
import { ComponentName } from '../types'
|
||||
|
||||
const componentMap: Recordable<Component, ComponentName> = {
|
||||
|
@ -47,7 +48,8 @@ const componentMap: Recordable<Component, ComponentName> = {
|
|||
InputPassword: InputPassword,
|
||||
Editor: Editor,
|
||||
TreeSelect: ElTreeSelect,
|
||||
Upload: ElUpload
|
||||
Upload: ElUpload,
|
||||
JsonEditor: JsonEditor
|
||||
}
|
||||
|
||||
export { componentMap }
|
||||
|
|
|
@ -21,6 +21,7 @@ import {
|
|||
UploadProps
|
||||
} from 'element-plus'
|
||||
import { IEditorConfig } from '@wangeditor/editor'
|
||||
import { JsonEditorProps } from '@/components/JsonEditor'
|
||||
import { CSSProperties } from 'vue'
|
||||
|
||||
export interface PlaceholderModel {
|
||||
|
@ -53,7 +54,8 @@ export enum ComponentNameEnum {
|
|||
INPUT_PASSWORD = 'InputPassword',
|
||||
EDITOR = 'Editor',
|
||||
TREE_SELECT = 'TreeSelect',
|
||||
UPLOAD = 'Upload'
|
||||
UPLOAD = 'Upload',
|
||||
JSON_EDITOR = 'JsonEditor'
|
||||
}
|
||||
|
||||
type CamelCaseComponentName = keyof typeof ComponentNameEnum extends infer K
|
||||
|
@ -620,6 +622,7 @@ export interface FormSchema {
|
|||
| InputPasswordComponentProps
|
||||
| TreeSelectComponentProps
|
||||
| UploadComponentProps
|
||||
| JsonEditorProps
|
||||
| any
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
import JsonEditor from './src/JsonEditor.vue'
|
||||
export type { JsonEditorProps } from './src/types'
|
||||
|
||||
export { JsonEditor }
|
|
@ -0,0 +1,98 @@
|
|||
<script setup lang="ts">
|
||||
import VueJsonPretty from 'vue-json-pretty'
|
||||
import 'vue-json-pretty/lib/styles.css'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const emits = defineEmits([
|
||||
'update:modelValue',
|
||||
'node-click',
|
||||
'brackets-click',
|
||||
'icon-click',
|
||||
'selected-value'
|
||||
])
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
deep: propTypes.number.def(5),
|
||||
showLength: propTypes.bool.def(true),
|
||||
showLineNumbers: propTypes.bool.def(true),
|
||||
showLineNumber: propTypes.bool.def(true),
|
||||
showIcon: propTypes.bool.def(true),
|
||||
showDoubleQuotes: propTypes.bool.def(true),
|
||||
virtual: propTypes.bool.def(false),
|
||||
height: propTypes.number.def(400),
|
||||
itemHeight: propTypes.number.def(20),
|
||||
rootPath: propTypes.string.def('root'),
|
||||
nodeSelectable: propTypes.func.def(),
|
||||
selectableType: propTypes.oneOf<'multiple' | 'single'>(['multiple', 'single']).def(),
|
||||
showSelectController: propTypes.bool.def(false),
|
||||
selectOnClickNode: propTypes.bool.def(true),
|
||||
highlightSelectedNode: propTypes.bool.def(true),
|
||||
collapsedOnClickBrackets: propTypes.bool.def(true),
|
||||
renderNodeKey: propTypes.func.def(),
|
||||
renderNodeValue: propTypes.func.def(),
|
||||
editable: propTypes.bool.def(true),
|
||||
editableTrigger: propTypes.oneOf<'click' | 'dblclick'>(['click', 'dblclick']).def('click')
|
||||
})
|
||||
|
||||
const data = computed(() => props.modelValue)
|
||||
|
||||
const localModelValue = computed({
|
||||
get: () => data.value,
|
||||
set: (val) => {
|
||||
console.log(val)
|
||||
emits('update:modelValue', val)
|
||||
}
|
||||
})
|
||||
|
||||
const nodeClick = (node: any) => {
|
||||
emits('node-click', node)
|
||||
}
|
||||
|
||||
const bracketsClick = (collapsed: boolean) => {
|
||||
emits('brackets-click', collapsed)
|
||||
}
|
||||
|
||||
const iconClick = (collapsed: boolean) => {
|
||||
emits('icon-click', collapsed)
|
||||
}
|
||||
|
||||
const selectedChange = (newVal: any, oldVal: any) => {
|
||||
console.log(newVal, oldVal)
|
||||
emits('selected-value', newVal, oldVal)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VueJsonPretty
|
||||
v-model:data="localModelValue"
|
||||
:deep="deep"
|
||||
:show-length="showLength"
|
||||
:show-line-numbers="showLineNumbers"
|
||||
:show-line-number="showLineNumber"
|
||||
:show-icon="showIcon"
|
||||
:show-double-quotes="showDoubleQuotes"
|
||||
:virtual="virtual"
|
||||
:height="height"
|
||||
:item-height="itemHeight"
|
||||
:root-path="rootPath"
|
||||
:node-selectable="nodeSelectable"
|
||||
:selectable-type="selectableType"
|
||||
:show-select-controller="showSelectController"
|
||||
:select-on-click-node="selectOnClickNode"
|
||||
:highlight-selected-node="highlightSelectedNode"
|
||||
:collapsed-on-click-brackets="collapsedOnClickBrackets"
|
||||
:render-node-key="renderNodeKey"
|
||||
:render-node-value="renderNodeValue"
|
||||
:editable="editable"
|
||||
:editable-trigger="editableTrigger"
|
||||
@node-click="nodeClick"
|
||||
@brackets-click="bracketsClick"
|
||||
@icon-click="iconClick"
|
||||
@selected-change="selectedChange"
|
||||
/>
|
||||
</template>
|
|
@ -0,0 +1,23 @@
|
|||
export interface JsonEditorProps {
|
||||
value: any
|
||||
deep?: number
|
||||
showLength?: boolean
|
||||
showLineNumbers?: boolean
|
||||
showLineNumber?: boolean
|
||||
showIcon?: boolean
|
||||
showDoubleQuotes?: boolean
|
||||
virtual?: boolean
|
||||
height?: number
|
||||
itemHeight?: number
|
||||
rootPath?: string
|
||||
nodeSelectable?: (...args: any[]) => boolean
|
||||
selectableType?: 'multiple' | 'single'
|
||||
showSelectController?: boolean
|
||||
selectOnClickNode?: boolean
|
||||
highlightSelectedNode?: boolean
|
||||
collapsedOnClickBrackets?: boolean
|
||||
renderNodeKey?: (...args: any[]) => any
|
||||
renderNodeValue?: (...args: any[]) => any
|
||||
editable?: boolean
|
||||
editableTrigger?: 'click' | 'dblclick'
|
||||
}
|
|
@ -1,24 +1,23 @@
|
|||
import { ElSubMenu, ElMenuItem } from 'element-plus'
|
||||
import type { RouteMeta } from 'vue-router'
|
||||
import { hasOneShowingChild } from '../helper'
|
||||
import { isUrl } from '@/utils/is'
|
||||
import { useRenderMenuTitle } from './useRenderMenuTitle'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { pathResolve } from '@/utils/routerHelper'
|
||||
|
||||
const { renderMenuTitle } = useRenderMenuTitle()
|
||||
|
||||
export const useRenderMenuItem = (
|
||||
// allRouters: AppRouteRecordRaw[] = [],
|
||||
menuMode: 'vertical' | 'horizontal'
|
||||
) => {
|
||||
const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {
|
||||
return routers.map((v) => {
|
||||
const meta = (v.meta ?? {}) as RouteMeta
|
||||
const meta = v.meta ?? {}
|
||||
if (!meta.hidden) {
|
||||
const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)
|
||||
const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')
|
||||
|
||||
const { renderMenuTitle } = useRenderMenuTitle()
|
||||
|
||||
if (
|
||||
oneShowingChild &&
|
||||
(!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import type { RouteMeta } from 'vue-router'
|
||||
import { ref, unref } from 'vue'
|
||||
import { findPath } from '@/utils/tree'
|
||||
|
||||
|
@ -21,7 +20,7 @@ export const hasOneShowingChild = (
|
|||
const onlyOneChild = ref<OnlyOneChildType>()
|
||||
|
||||
const showingChildren = children.filter((v) => {
|
||||
const meta = (v.meta ?? {}) as RouteMeta
|
||||
const meta = v.meta ?? {}
|
||||
if (meta.hidden) {
|
||||
return false
|
||||
} else {
|
||||
|
|
|
@ -115,6 +115,14 @@ const dynamicRouterChange = (show: boolean) => {
|
|||
appStore.setDynamicRouter(show)
|
||||
}
|
||||
|
||||
// 服务端动态路由
|
||||
const serverDynamicRouter = ref(appStore.getServerDynamicRouter)
|
||||
|
||||
const serverDynamicRouterChange = (show: boolean) => {
|
||||
ElMessage.info(t('setting.reExperienced'))
|
||||
appStore.setServerDynamicRouter(show)
|
||||
}
|
||||
|
||||
// 固定菜单
|
||||
const fixedMenu = ref(appStore.getFixedMenu)
|
||||
|
||||
|
@ -206,6 +214,11 @@ watch(
|
|||
<ElSwitch v-model="dynamicRouter" @change="dynamicRouterChange" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-14px">{{ t('setting.serverDynamicRouter') }}</span>
|
||||
<ElSwitch v-model="serverDynamicRouter" @change="serverDynamicRouterChange" />
|
||||
</div>
|
||||
|
||||
<div class="flex justify-between items-center">
|
||||
<span class="text-14px">{{ t('setting.fixedMenu') }}</span>
|
||||
<ElSwitch v-model="fixedMenu" @change="fixedMenuChange" />
|
||||
|
|
|
@ -105,7 +105,8 @@ export default defineComponent({
|
|||
tabActive.value = item.children ? item.path : item.path.split('/')[0]
|
||||
if (item.children) {
|
||||
if (newPath === oldPath || !unref(showMenu)) {
|
||||
showMenu.value = unref(fixedMenu) ? true : !unref(showMenu)
|
||||
// showMenu.value = unref(fixedMenu) ? true : !unref(showMenu)
|
||||
showMenu.value = !unref(showMenu)
|
||||
}
|
||||
if (unref(showMenu)) {
|
||||
permissionStore.setMenuTabRouters(
|
||||
|
@ -131,11 +132,6 @@ export default defineComponent({
|
|||
return false
|
||||
}
|
||||
|
||||
const mouseleave = () => {
|
||||
if (!unref(showMenu) || unref(fixedMenu)) return
|
||||
showMenu.value = false
|
||||
}
|
||||
|
||||
return () => (
|
||||
<div
|
||||
id={`${variables.namespace}-menu`}
|
||||
|
@ -147,7 +143,6 @@ export default defineComponent({
|
|||
'w-[var(--tab-menu-min-width)]': unref(collapse)
|
||||
}
|
||||
]}
|
||||
onMouseleave={mouseleave}
|
||||
>
|
||||
<ElScrollbar class="!h-[calc(100%-var(--tab-menu-collapse-height)-1px)]">
|
||||
<div>
|
||||
|
@ -178,7 +173,7 @@ export default defineComponent({
|
|||
<Icon icon={item?.meta?.icon}></Icon>
|
||||
</div>
|
||||
{!unref(showTitle) ? undefined : (
|
||||
<p class="break-words mt-5px px-2px">{t(item.meta?.title)}</p>
|
||||
<p class="break-words mt-5px px-2px">{t(item.meta?.title || '')}</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
@ -197,11 +192,11 @@ export default defineComponent({
|
|||
</div>
|
||||
<Menu
|
||||
class={[
|
||||
'!absolute top-0',
|
||||
'!absolute top-0 z-4000',
|
||||
{
|
||||
'!left-[var(--tab-menu-min-width)]': unref(collapse),
|
||||
'!left-[var(--tab-menu-max-width)]': !unref(collapse),
|
||||
'!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),
|
||||
'!w-[var(--left-menu-max-width)]': unref(showMenu) || unref(fixedMenu),
|
||||
'!w-0': !unref(showMenu) && !unref(fixedMenu)
|
||||
}
|
||||
]}
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { getAllParentPath } from '@/components/Menu/src/helper'
|
||||
import type { RouteMeta } from 'vue-router'
|
||||
import { isUrl } from '@/utils/is'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { reactive } from 'vue'
|
||||
|
@ -12,7 +11,7 @@ export const tabPathMap = reactive<TabMapTypes>({})
|
|||
|
||||
export const initTabMap = (routes: AppRouteRecordRaw[]) => {
|
||||
for (const v of routes) {
|
||||
const meta = (v.meta ?? {}) as RouteMeta
|
||||
const meta = v.meta ?? {}
|
||||
if (!meta?.hidden) {
|
||||
tabPathMap[v.path] = []
|
||||
}
|
||||
|
@ -26,7 +25,7 @@ export const filterMenusPath = (
|
|||
const res: AppRouteRecordRaw[] = []
|
||||
for (const v of routes) {
|
||||
let data: Nullable<AppRouteRecordRaw> = null
|
||||
const meta = (v.meta ?? {}) as RouteMeta
|
||||
const meta = v.meta ?? {}
|
||||
if (!meta.hidden || meta.canTo) {
|
||||
const allParentPath = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path)
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ import { useDesign } from '@/hooks/web/useDesign'
|
|||
import { useTemplateRefsList } from '@vueuse/core'
|
||||
import { ElScrollbar } from 'element-plus'
|
||||
import { useScrollTo } from '@/hooks/event/useScrollTo'
|
||||
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
const { getPrefixCls } = useDesign()
|
||||
|
||||
|
@ -19,7 +21,9 @@ const prefixCls = getPrefixCls('tags-view')
|
|||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { currentRoute, push, replace } = useRouter()
|
||||
const { currentRoute, push } = useRouter()
|
||||
|
||||
const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage } = useTagsView()
|
||||
|
||||
const permissionStore = usePermissionStore()
|
||||
|
||||
|
@ -31,6 +35,10 @@ const visitedViews = computed(() => tagsViewStore.getVisitedViews)
|
|||
|
||||
const affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])
|
||||
|
||||
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
|
||||
|
||||
const setSelectTag = tagsViewStore.setSelectedTag
|
||||
|
||||
const appStore = useAppStore()
|
||||
|
||||
const tagsViewIcon = computed(() => appStore.getTagsViewIcon)
|
||||
|
@ -43,66 +51,30 @@ const initTags = () => {
|
|||
for (const tag of unref(affixTagArr)) {
|
||||
// Must have tag name
|
||||
if (tag.name) {
|
||||
tagsViewStore.addVisitedView(tag)
|
||||
tagsViewStore.addVisitedView(cloneDeep(tag))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const selectedTag = ref<RouteLocationNormalizedLoaded>()
|
||||
|
||||
// 新增tag
|
||||
const addTags = () => {
|
||||
const { name } = unref(currentRoute)
|
||||
if (name) {
|
||||
selectedTag.value = unref(currentRoute)
|
||||
setSelectTag(unref(currentRoute))
|
||||
tagsViewStore.addView(unref(currentRoute))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// 关闭选中的tag
|
||||
const closeSelectedTag = (view: RouteLocationNormalizedLoaded) => {
|
||||
if (view?.meta?.affix) return
|
||||
tagsViewStore.delView(view)
|
||||
if (isActive(view)) {
|
||||
toLastView()
|
||||
}
|
||||
}
|
||||
|
||||
// 关闭全部
|
||||
const closeAllTags = () => {
|
||||
tagsViewStore.delAllViews()
|
||||
toLastView()
|
||||
}
|
||||
|
||||
// 关闭其它
|
||||
const closeOthersTags = () => {
|
||||
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
|
||||
// 重新加载
|
||||
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
|
||||
if (!view) return
|
||||
tagsViewStore.delCachedView()
|
||||
const { path, query } = view
|
||||
await nextTick()
|
||||
replace({
|
||||
path: '/redirect' + path,
|
||||
query: query
|
||||
closeCurrent(view, () => {
|
||||
if (isActive(view)) {
|
||||
toLastView()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭左侧
|
||||
const closeLeftTags = () => {
|
||||
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
|
||||
// 关闭右侧
|
||||
const closeRightTags = () => {
|
||||
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
}
|
||||
|
||||
// 跳转到最后一个
|
||||
// 去最后一个
|
||||
const toLastView = () => {
|
||||
const visitedViews = tagsViewStore.getVisitedViews
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
|
@ -121,6 +93,33 @@ const toLastView = () => {
|
|||
}
|
||||
}
|
||||
|
||||
// 关闭全部
|
||||
const closeAllTags = () => {
|
||||
closeAll(() => {
|
||||
toLastView()
|
||||
})
|
||||
}
|
||||
|
||||
// 关闭其它
|
||||
const closeOthersTags = () => {
|
||||
closeOther()
|
||||
}
|
||||
|
||||
// 重新加载
|
||||
const refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {
|
||||
refreshPage(view)
|
||||
}
|
||||
|
||||
// 关闭左侧
|
||||
const closeLeftTags = () => {
|
||||
closeLeft()
|
||||
}
|
||||
|
||||
// 关闭右侧
|
||||
const closeRightTags = () => {
|
||||
closeRight()
|
||||
}
|
||||
|
||||
// 滚动到选中的tag
|
||||
const moveToCurrentTag = async () => {
|
||||
await nextTick()
|
||||
|
@ -583,3 +582,4 @@ watch(
|
|||
}
|
||||
}
|
||||
</style>
|
||||
@/hooks/web/useTagsView
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import type { RouteMeta, RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import type { RouteLocationNormalizedLoaded } from 'vue-router'
|
||||
import { pathResolve } from '@/utils/routerHelper'
|
||||
|
||||
export const filterAffixTags = (routes: AppRouteRecordRaw[], parentPath = '') => {
|
||||
let tags: RouteLocationNormalizedLoaded[] = []
|
||||
routes.forEach((route) => {
|
||||
const meta = route.meta as RouteMeta
|
||||
const meta = route.meta ?? {}
|
||||
const tagPath = pathResolve(parentPath, route.path)
|
||||
if (meta?.affix) {
|
||||
tags.push({ ...route, path: tagPath, fullPath: tagPath } as RouteLocationNormalizedLoaded)
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
import { Config, driver } from 'driver.js'
|
||||
import 'driver.js/dist/driver.css'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { variables } = useDesign()
|
||||
|
||||
export const useGuide = (options?: Config) => {
|
||||
const driverObj = driver(
|
||||
options || {
|
||||
showProgress: true,
|
||||
nextBtnText: t('common.nextLabel'),
|
||||
prevBtnText: t('common.prevLabel'),
|
||||
doneBtnText: t('common.doneLabel'),
|
||||
steps: [
|
||||
{
|
||||
element: `#${variables.namespace}-menu`,
|
||||
popover: {
|
||||
title: t('common.menu'),
|
||||
description: t('common.menuDes'),
|
||||
side: 'right'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: `#${variables.namespace}-tool-header`,
|
||||
popover: {
|
||||
title: t('common.tool'),
|
||||
description: t('common.toolDes'),
|
||||
side: 'left'
|
||||
}
|
||||
},
|
||||
{
|
||||
element: `#${variables.namespace}-tags-view`,
|
||||
popover: {
|
||||
title: t('common.tagsView'),
|
||||
description: t('common.tagsViewDes'),
|
||||
side: 'bottom'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
return {
|
||||
...driverObj
|
||||
}
|
||||
}
|
|
@ -1,47 +0,0 @@
|
|||
import introJs from 'intro.js'
|
||||
import { IntroJs, Step, Options } from 'intro.js'
|
||||
import 'intro.js/introjs.css'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useDesign } from '@/hooks/web/useDesign'
|
||||
|
||||
export const useIntro = (setps?: Step[], options?: Options) => {
|
||||
const { t } = useI18n()
|
||||
|
||||
const { variables } = useDesign()
|
||||
|
||||
const defaultSetps: Step[] = setps || [
|
||||
{
|
||||
element: `#${variables.namespace}-menu`,
|
||||
title: t('common.menu'),
|
||||
intro: t('common.menuDes'),
|
||||
position: 'right'
|
||||
},
|
||||
{
|
||||
element: `#${variables.namespace}-tool-header`,
|
||||
title: t('common.tool'),
|
||||
intro: t('common.toolDes'),
|
||||
position: 'left'
|
||||
},
|
||||
{
|
||||
element: `#${variables.namespace}-tags-view`,
|
||||
title: t('common.tagsView'),
|
||||
intro: t('common.tagsViewDes'),
|
||||
position: 'bottom'
|
||||
}
|
||||
]
|
||||
|
||||
const defaultOptions: Options = options || {
|
||||
prevLabel: t('common.prevLabel'),
|
||||
nextLabel: t('common.nextLabel'),
|
||||
skipLabel: t('common.skipLabel'),
|
||||
doneLabel: t('common.doneLabel')
|
||||
}
|
||||
|
||||
const introRef: IntroJs = introJs()
|
||||
|
||||
introRef.addSteps(defaultSetps).setOptions(defaultOptions)
|
||||
|
||||
return {
|
||||
introRef
|
||||
}
|
||||
}
|
|
@ -1,15 +1,21 @@
|
|||
import { isArray, isObject } from '@/utils/is'
|
||||
// 获取传入的值的类型
|
||||
const getValueType = (value: any) => {
|
||||
const type = Object.prototype.toString.call(value)
|
||||
return type.slice(8, -1)
|
||||
}
|
||||
|
||||
export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'sessionStorage') => {
|
||||
const setStorage = (key: string, value: any) => {
|
||||
window[type].setItem(key, isArray(value) || isObject(value) ? JSON.stringify(value) : value)
|
||||
const valueType = getValueType(value)
|
||||
window[type].setItem(key, JSON.stringify({ type: valueType, value }))
|
||||
}
|
||||
|
||||
const getStorage = (key: string) => {
|
||||
const value = window[type].getItem(key)
|
||||
try {
|
||||
return JSON.parse(value || '')
|
||||
} catch (error) {
|
||||
if (value) {
|
||||
const { value: val } = JSON.parse(value)
|
||||
return val
|
||||
} else {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
@ -18,8 +24,17 @@ export const useStorage = (type: 'sessionStorage' | 'localStorage' = 'sessionSto
|
|||
window[type].removeItem(key)
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
window[type].clear()
|
||||
const clear = (excludes?: string[]) => {
|
||||
// 获取排除项
|
||||
const keys = Object.keys(window[type])
|
||||
const defaultExcludes = ['dynamicRouter', 'serverDynamicRouter']
|
||||
const excludesArr = excludes ? [...excludes, ...defaultExcludes] : defaultExcludes
|
||||
const excludesKeys = excludesArr ? keys.filter((key) => !excludesArr.includes(key)) : keys
|
||||
// 排除项不清除
|
||||
excludesKeys.forEach((key) => {
|
||||
window[type].removeItem(key)
|
||||
})
|
||||
// window[type].clear()
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
import { useTagsViewStoreWithOut } from '@/store/modules/tagsView'
|
||||
import { RouteLocationNormalizedLoaded, useRouter } from 'vue-router'
|
||||
import { computed, nextTick, unref } from 'vue'
|
||||
|
||||
export const useTagsView = () => {
|
||||
const tagsViewStore = useTagsViewStoreWithOut()
|
||||
|
||||
const { replace, currentRoute } = useRouter()
|
||||
|
||||
const selectedTag = computed(() => tagsViewStore.getSelectedTag)
|
||||
|
||||
const closeAll = (callback?: Fn) => {
|
||||
tagsViewStore.delAllViews()
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeLeft = (callback?: Fn) => {
|
||||
tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeRight = (callback?: Fn) => {
|
||||
tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeOther = (callback?: Fn) => {
|
||||
tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const closeCurrent = (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
|
||||
if (view?.meta?.affix) return
|
||||
tagsViewStore.delView(view || unref(currentRoute))
|
||||
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const refreshPage = async (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {
|
||||
tagsViewStore.delCachedView()
|
||||
const { path, query } = view || unref(currentRoute)
|
||||
await nextTick()
|
||||
replace({
|
||||
path: '/redirect' + path,
|
||||
query: query
|
||||
})
|
||||
callback?.()
|
||||
}
|
||||
|
||||
const setTitle = (title: string, path?: string) => {
|
||||
tagsViewStore.setTitle(title, path)
|
||||
}
|
||||
|
||||
return {
|
||||
closeAll,
|
||||
closeLeft,
|
||||
closeRight,
|
||||
closeOther,
|
||||
closeCurrent,
|
||||
refreshPage,
|
||||
setTitle
|
||||
}
|
||||
}
|
|
@ -1,56 +1,53 @@
|
|||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { FormItemRule } from 'element-plus'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
type Callback = (error?: string | Error | undefined) => void
|
||||
|
||||
interface LengthRange {
|
||||
min: number
|
||||
max: number
|
||||
message: string
|
||||
message?: string
|
||||
}
|
||||
|
||||
export const useValidator = () => {
|
||||
const required = (message?: string) => {
|
||||
const required = (message?: string): FormItemRule => {
|
||||
return {
|
||||
required: true,
|
||||
message: message || t('common.required')
|
||||
}
|
||||
}
|
||||
|
||||
const lengthRange = (val: any, callback: Callback, options: LengthRange) => {
|
||||
const lengthRange = (options: LengthRange): FormItemRule => {
|
||||
const { min, max, message } = options
|
||||
if (val.length < min || val.length > max) {
|
||||
callback(new Error(message))
|
||||
} else {
|
||||
callback()
|
||||
|
||||
return {
|
||||
min,
|
||||
max,
|
||||
message: message || t('common.lengthRange', { min, max })
|
||||
}
|
||||
}
|
||||
|
||||
const notSpace = (val: any, callback: Callback, message: string) => {
|
||||
// 用户名不能有空格
|
||||
if (val.indexOf(' ') !== -1) {
|
||||
callback(new Error(message))
|
||||
} else {
|
||||
callback()
|
||||
const notSpace = (message?: string): FormItemRule => {
|
||||
return {
|
||||
validator: (_, val, callback) => {
|
||||
if (val?.indexOf(' ') !== -1) {
|
||||
callback(new Error(message || t('common.notSpace')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const notSpecialCharacters = (val: any, callback: Callback, message: string) => {
|
||||
// 密码不能是特殊字符
|
||||
if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
|
||||
callback(new Error(message))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
// 两个字符串是否想等
|
||||
const isEqual = (val1: string, val2: string, callback: Callback, message: string) => {
|
||||
if (val1 === val2) {
|
||||
callback()
|
||||
} else {
|
||||
callback(new Error(message))
|
||||
const notSpecialCharacters = (message?: string): FormItemRule => {
|
||||
return {
|
||||
validator: (_, val, callback) => {
|
||||
if (/[`~!@#$%^&*()_+<>?:"{},.\/;'[\]]/gi.test(val)) {
|
||||
callback(new Error(message || t('common.notSpecialCharacters')))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -58,7 +55,6 @@ export const useValidator = () => {
|
|||
required,
|
||||
lengthRange,
|
||||
notSpace,
|
||||
notSpecialCharacters,
|
||||
isEqual
|
||||
notSpecialCharacters
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,7 +44,11 @@ export default {
|
|||
refresh: 'Refresh',
|
||||
fullscreen: 'Fullscreen',
|
||||
size: 'Size',
|
||||
columnSetting: 'Column setting'
|
||||
columnSetting: 'Column setting',
|
||||
lengthRange: 'The length should be between {min} and {max}',
|
||||
notSpace: 'Spaces are not allowed',
|
||||
notSpecialCharacters: 'Special characters are not allowed',
|
||||
isEqual: 'The two are not equal'
|
||||
},
|
||||
lock: {
|
||||
lockScreen: 'Lock screen',
|
||||
|
@ -89,7 +93,9 @@ export default {
|
|||
footer: 'Footer',
|
||||
uniqueOpened: 'Unique opened',
|
||||
tagsViewIcon: 'Tags view icon',
|
||||
dynamicRouter: 'Dynamic router',
|
||||
// 开启动态路由
|
||||
dynamicRouter: 'Enable dynamic router',
|
||||
serverDynamicRouter: 'Server dynamic router',
|
||||
reExperienced: 'Please exit the login experience again',
|
||||
fixedMenu: 'Fixed menu'
|
||||
},
|
||||
|
@ -143,6 +149,7 @@ export default {
|
|||
defaultTable: 'Basic example',
|
||||
editor: 'Editor',
|
||||
richText: 'Rich text',
|
||||
jsonEditor: 'JSON Editor',
|
||||
dialog: 'Dialog',
|
||||
imageViewer: 'Image viewer',
|
||||
descriptions: 'Descriptions',
|
||||
|
@ -296,6 +303,7 @@ export default {
|
|||
verifyReset: 'Verify reset',
|
||||
// 富文本编辑器
|
||||
richText: 'Rich text',
|
||||
jsonEditor: 'JSON Editor',
|
||||
form: 'Form',
|
||||
// 远程加载
|
||||
remoteLoading: 'Remote loading',
|
||||
|
@ -318,7 +326,7 @@ export default {
|
|||
guide: 'Guide',
|
||||
start: 'Start',
|
||||
message:
|
||||
'The guide page is very useful for some people who enter the project for the first time. You can briefly introduce the functions of the project. The boot page is based on intro js'
|
||||
'The guide page is very useful for some people who enter the project for the first time. You can briefly introduce the functions of the project. The boot page is based on driver.js'
|
||||
},
|
||||
iconDemo: {
|
||||
icon: 'Icon',
|
||||
|
@ -444,7 +452,9 @@ export default {
|
|||
},
|
||||
richText: {
|
||||
richText: 'Rich text',
|
||||
richTextDes: 'Secondary packaging based on wangeditor'
|
||||
richTextDes: 'Secondary packaging based on wangeditor',
|
||||
jsonEditor: 'JSON Editor',
|
||||
jsonEditorDes: 'Secondary packaging based on vue-json-pretty'
|
||||
},
|
||||
dialogDemo: {
|
||||
dialog: 'Dialog',
|
||||
|
|
|
@ -44,7 +44,11 @@ export default {
|
|||
refresh: '刷新',
|
||||
fullscreen: '全屏',
|
||||
size: '尺寸',
|
||||
columnSetting: '列设置'
|
||||
columnSetting: '列设置',
|
||||
lengthRange: '长度在 {min} 到 {max} 个字符',
|
||||
notSpace: '不能包含空格',
|
||||
notSpecialCharacters: '不能包含特殊字符',
|
||||
isEqual: '两次输入不一致'
|
||||
},
|
||||
lock: {
|
||||
lockScreen: '锁定屏幕',
|
||||
|
@ -89,7 +93,8 @@ export default {
|
|||
footer: '页脚',
|
||||
uniqueOpened: '菜单手风琴',
|
||||
tagsViewIcon: '标签页图标',
|
||||
dynamicRouter: '动态路由',
|
||||
dynamicRouter: '开启动态路由',
|
||||
serverDynamicRouter: '服务端动态路由',
|
||||
reExperienced: '请重新退出登录体验',
|
||||
fixedMenu: '固定菜单'
|
||||
},
|
||||
|
@ -143,6 +148,7 @@ export default {
|
|||
defaultTable: '基础示例',
|
||||
editor: '编辑器',
|
||||
richText: '富文本',
|
||||
jsonEditor: 'JSON编辑器',
|
||||
dialog: '弹窗',
|
||||
imageViewer: '图片预览',
|
||||
descriptions: '描述',
|
||||
|
@ -294,6 +300,8 @@ export default {
|
|||
verifyReset: '验证重置',
|
||||
// 富文本编辑器
|
||||
richText: '富文本编辑器',
|
||||
// JSON编辑器
|
||||
jsonEditor: 'JSON编辑器',
|
||||
form: '表单',
|
||||
// 远程加载
|
||||
remoteLoading: '远程加载',
|
||||
|
@ -313,7 +321,7 @@ export default {
|
|||
guide: '引导页',
|
||||
start: '开始',
|
||||
message:
|
||||
'引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。引导页基于 intro.js'
|
||||
'引导页对于一些第一次进入项目的人很有用,你可以简单介绍下项目的功能。引导页基于 driver.js'
|
||||
},
|
||||
iconDemo: {
|
||||
icon: '图标',
|
||||
|
@ -437,7 +445,9 @@ export default {
|
|||
},
|
||||
richText: {
|
||||
richText: '富文本',
|
||||
richTextDes: '基于 wangeditor 二次封装'
|
||||
richTextDes: '基于 wangeditor 二次封装',
|
||||
jsonEditor: 'JSON编辑器',
|
||||
jsonEditorDes: '基于 vue-json-pretty 二次封装'
|
||||
},
|
||||
dialogDemo: {
|
||||
dialog: '弹窗',
|
||||
|
|
|
@ -5,16 +5,12 @@ import type { RouteRecordRaw } from 'vue-router'
|
|||
import { useTitle } from '@/hooks/web/useTitle'
|
||||
import { useNProgress } from '@/hooks/web/useNProgress'
|
||||
import { usePermissionStoreWithOut } from '@/store/modules/permission'
|
||||
// import { useDictStoreWithOut } from '@/store/modules/dict'
|
||||
import { usePageLoading } from '@/hooks/web/usePageLoading'
|
||||
// import { getDictApi } from '@/api/common'
|
||||
|
||||
const permissionStore = usePermissionStoreWithOut()
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
// const dictStore = useDictStoreWithOut()
|
||||
|
||||
const { getStorage } = useStorage()
|
||||
|
||||
const { start, done } = useNProgress()
|
||||
|
@ -30,14 +26,6 @@ router.beforeEach(async (to, from, next) => {
|
|||
if (to.path === '/login') {
|
||||
next({ path: '/' })
|
||||
} else {
|
||||
// if (!dictStore.getIsSetDict) {
|
||||
// // 获取所有字典
|
||||
// const res = await getDictApi()
|
||||
// if (res) {
|
||||
// dictStore.setDictObj(res.data)
|
||||
// dictStore.setIsSetDict(true)
|
||||
// }
|
||||
// }
|
||||
if (permissionStore.getIsAddRouters) {
|
||||
next()
|
||||
return
|
||||
|
@ -45,15 +33,14 @@ router.beforeEach(async (to, from, next) => {
|
|||
|
||||
// 开发者可根据实际情况进行修改
|
||||
const roleRouters = getStorage('roleRouters') || []
|
||||
const userInfo = getStorage(appStore.getUserInfo)
|
||||
|
||||
// 是否使用动态路由
|
||||
if (appStore.getDynamicRouter) {
|
||||
userInfo.role === 'admin'
|
||||
? await permissionStore.generateRoutes('admin', roleRouters as AppCustomRouteRecordRaw[])
|
||||
: await permissionStore.generateRoutes('test', roleRouters as string[])
|
||||
appStore.serverDynamicRouter
|
||||
? await permissionStore.generateRoutes('server', roleRouters as AppCustomRouteRecordRaw[])
|
||||
: await permissionStore.generateRoutes('frontEnd', roleRouters as string[])
|
||||
} else {
|
||||
await permissionStore.generateRoutes('none')
|
||||
await permissionStore.generateRoutes('static')
|
||||
}
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
|
|
|
@ -220,6 +220,14 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
|||
meta: {
|
||||
title: t('router.richText')
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'json-editor',
|
||||
component: () => import('@/views/Components/Editor/JsonEditor.vue'),
|
||||
name: 'JsonEditor',
|
||||
meta: {
|
||||
title: t('router.jsonEditor')
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -339,7 +347,8 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
|||
meta: {
|
||||
hidden: true,
|
||||
title: t('router.details'),
|
||||
canTo: true
|
||||
canTo: true,
|
||||
activeMenu: '/function/multiple-tabs'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
@ -362,15 +371,31 @@ export const asyncRouterMap: AppRouteRecordRaw[] = [
|
|||
meta: {
|
||||
title: 'useWatermark'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'useTagsView',
|
||||
component: () => import('@/views/hooks/useTagsView.vue'),
|
||||
name: 'UseTagsView',
|
||||
meta: {
|
||||
title: 'useTagsView'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'useValidator',
|
||||
component: () => import('@/views/hooks/useValidator.vue'),
|
||||
name: 'UseValidator',
|
||||
meta: {
|
||||
title: 'useValidator'
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'useCrudSchemas',
|
||||
component: () => import('@/views/hooks/useCrudSchemas.vue'),
|
||||
name: 'UseCrudSchemas',
|
||||
meta: {
|
||||
title: 'useCrudSchemas'
|
||||
}
|
||||
}
|
||||
// {
|
||||
// path: 'useCrudSchemas',
|
||||
// component: () => import('@/views/hooks/useCrudSchemas.vue'),
|
||||
// name: 'UseCrudSchemas',
|
||||
// meta: {
|
||||
// title: 'useCrudSchemas'
|
||||
// }
|
||||
// }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
@ -21,6 +21,7 @@ interface AppState {
|
|||
fixedHeader: boolean
|
||||
greyMode: boolean
|
||||
dynamicRouter: boolean
|
||||
serverDynamicRouter: boolean
|
||||
pageLoading: boolean
|
||||
layout: LayoutType
|
||||
title: string
|
||||
|
@ -42,7 +43,6 @@ export const useAppStore = defineStore('app', {
|
|||
mobile: false, // 是否是移动端
|
||||
title: import.meta.env.VITE_APP_TITLE, // 标题
|
||||
pageLoading: false, // 路由跳转loading
|
||||
|
||||
breadcrumb: true, // 面包屑
|
||||
breadcrumbIcon: true, // 面包屑图标
|
||||
collapse: false, // 折叠菜单
|
||||
|
@ -57,11 +57,12 @@ export const useAppStore = defineStore('app', {
|
|||
fixedHeader: true, // 固定toolheader
|
||||
footer: true, // 显示页脚
|
||||
greyMode: false, // 是否开始灰色模式,用于特殊悼念日
|
||||
dynamicRouter: getStorage('dynamicRouter') || false, // 是否动态路由
|
||||
fixedMenu: getStorage('fixedMenu') || false, // 是否固定菜单
|
||||
dynamicRouter: getStorage('dynamicRouter'), // 是否动态路由
|
||||
serverDynamicRouter: getStorage('serverDynamicRouter'), // 是否服务端渲染动态路由
|
||||
fixedMenu: getStorage('fixedMenu'), // 是否固定菜单
|
||||
|
||||
layout: getStorage('layout') || 'classic', // layout布局
|
||||
isDark: getStorage('isDark') || false, // 是否是暗黑模式
|
||||
isDark: getStorage('isDark'), // 是否是暗黑模式
|
||||
currentSize: getStorage('default') || 'default', // 组件尺寸
|
||||
theme: getStorage('theme') || {
|
||||
// 主题色
|
||||
|
@ -138,6 +139,9 @@ export const useAppStore = defineStore('app', {
|
|||
getDynamicRouter(): boolean {
|
||||
return this.dynamicRouter
|
||||
},
|
||||
getServerDynamicRouter(): boolean {
|
||||
return this.serverDynamicRouter
|
||||
},
|
||||
getFixedMenu(): boolean {
|
||||
return this.fixedMenu
|
||||
},
|
||||
|
@ -216,6 +220,10 @@ export const useAppStore = defineStore('app', {
|
|||
setStorage('dynamicRouter', dynamicRouter)
|
||||
this.dynamicRouter = dynamicRouter
|
||||
},
|
||||
setServerDynamicRouter(serverDynamicRouter: boolean) {
|
||||
setStorage('serverDynamicRouter', serverDynamicRouter)
|
||||
this.serverDynamicRouter = serverDynamicRouter
|
||||
},
|
||||
setFixedMenu(fixedMenu: boolean) {
|
||||
setStorage('fixedMenu', fixedMenu)
|
||||
this.fixedMenu = fixedMenu
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
|
||||
export interface DictState {
|
||||
isSetDict: boolean
|
||||
dictObj: Recordable
|
||||
}
|
||||
|
||||
export const useDictStore = defineStore('dict', {
|
||||
state: (): DictState => ({
|
||||
isSetDict: false,
|
||||
dictObj: {}
|
||||
}),
|
||||
getters: {
|
||||
getDictObj(): Recordable {
|
||||
return this.dictObj
|
||||
},
|
||||
getIsSetDict(): boolean {
|
||||
return this.isSetDict
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
setDictObj(dictObj: Recordable) {
|
||||
this.dictObj = dictObj
|
||||
},
|
||||
setIsSetDict(isSetDict: boolean) {
|
||||
this.isSetDict = isSetDict
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export const useDictStoreWithOut = () => {
|
||||
return useDictStore(store)
|
||||
}
|
|
@ -1,6 +1,10 @@
|
|||
import { defineStore } from 'pinia'
|
||||
import { asyncRouterMap, constantRouterMap } from '@/router'
|
||||
import { generateRoutesFn1, generateRoutesFn2, flatMultiLevelRoutes } from '@/utils/routerHelper'
|
||||
import {
|
||||
generateRoutesByFrontEnd,
|
||||
generateRoutesByServer,
|
||||
flatMultiLevelRoutes
|
||||
} from '@/utils/routerHelper'
|
||||
import { store } from '../index'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
|
||||
|
@ -34,17 +38,17 @@ export const usePermissionStore = defineStore('permission', {
|
|||
},
|
||||
actions: {
|
||||
generateRoutes(
|
||||
type: 'admin' | 'test' | 'none',
|
||||
type: 'server' | 'frontEnd' | 'static',
|
||||
routers?: AppCustomRouteRecordRaw[] | string[]
|
||||
): Promise<unknown> {
|
||||
return new Promise<void>((resolve) => {
|
||||
let routerMap: AppRouteRecordRaw[] = []
|
||||
if (type === 'admin') {
|
||||
if (type === 'server') {
|
||||
// 模拟后端过滤菜单
|
||||
routerMap = generateRoutesFn2(routers as AppCustomRouteRecordRaw[])
|
||||
} else if (type === 'test') {
|
||||
routerMap = generateRoutesByServer(routers as AppCustomRouteRecordRaw[])
|
||||
} else if (type === 'frontEnd') {
|
||||
// 模拟前端过滤菜单
|
||||
routerMap = generateRoutesFn1(cloneDeep(asyncRouterMap), routers as string[])
|
||||
routerMap = generateRoutesByFrontEnd(cloneDeep(asyncRouterMap), routers as string[])
|
||||
} else {
|
||||
// 直接读取静态路由表
|
||||
routerMap = cloneDeep(asyncRouterMap)
|
||||
|
|
|
@ -4,16 +4,24 @@ import { getRawRoute } from '@/utils/routerHelper'
|
|||
import { defineStore } from 'pinia'
|
||||
import { store } from '../index'
|
||||
import { findIndex } from '@/utils'
|
||||
import { useStorage } from '@/hooks/web/useStorage'
|
||||
import { useAppStoreWithOut } from './app'
|
||||
|
||||
const appStore = useAppStoreWithOut()
|
||||
|
||||
const { getStorage } = useStorage()
|
||||
|
||||
export interface TagsViewState {
|
||||
visitedViews: RouteLocationNormalizedLoaded[]
|
||||
cachedViews: Set<string>
|
||||
selectedTag?: RouteLocationNormalizedLoaded
|
||||
}
|
||||
|
||||
export const useTagsViewStore = defineStore('tagsView', {
|
||||
state: (): TagsViewState => ({
|
||||
visitedViews: [],
|
||||
cachedViews: new Set()
|
||||
cachedViews: new Set(),
|
||||
selectedTag: undefined
|
||||
}),
|
||||
getters: {
|
||||
getVisitedViews(): RouteLocationNormalizedLoaded[] {
|
||||
|
@ -21,6 +29,9 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||
},
|
||||
getCachedViews(): string[] {
|
||||
return Array.from(this.cachedViews)
|
||||
},
|
||||
getSelectedTag(): RouteLocationNormalizedLoaded | undefined {
|
||||
return this.selectedTag
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
|
@ -44,7 +55,7 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||
const cacheMap: Set<string> = new Set()
|
||||
for (const v of this.visitedViews) {
|
||||
const item = getRawRoute(v)
|
||||
const needCache = !item.meta?.noCache
|
||||
const needCache = !item?.meta?.noCache
|
||||
if (!needCache) {
|
||||
continue
|
||||
}
|
||||
|
@ -85,7 +96,9 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||
// 删除所有tag
|
||||
delAllVisitedViews() {
|
||||
// const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)
|
||||
this.visitedViews = []
|
||||
this.visitedViews = getStorage(appStore.getUserInfo)
|
||||
? this.visitedViews.filter((tag) => tag?.meta?.affix)
|
||||
: []
|
||||
},
|
||||
// 删除其它
|
||||
delOthersViews(view: RouteLocationNormalizedLoaded) {
|
||||
|
@ -131,6 +144,18 @@ export const useTagsViewStore = defineStore('tagsView', {
|
|||
break
|
||||
}
|
||||
}
|
||||
},
|
||||
// 设置当前选中的tag
|
||||
setSelectedTag(tag: RouteLocationNormalizedLoaded) {
|
||||
this.selectedTag = tag
|
||||
},
|
||||
setTitle(title: string, path?: string) {
|
||||
for (const v of this.visitedViews) {
|
||||
if (v.path === (path ?? this.selectedTag?.path)) {
|
||||
v.meta.title = title
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
|
@ -3,7 +3,6 @@ import type {
|
|||
Router,
|
||||
RouteLocationNormalized,
|
||||
RouteRecordNormalized,
|
||||
RouteMeta,
|
||||
RouteRecordRaw
|
||||
} from 'vue-router'
|
||||
import { isUrl } from '@/utils/is'
|
||||
|
@ -39,7 +38,7 @@ export const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormal
|
|||
}
|
||||
|
||||
// 前端控制路由生成
|
||||
export const generateRoutesFn1 = (
|
||||
export const generateRoutesByFrontEnd = (
|
||||
routes: AppRouteRecordRaw[],
|
||||
keys: string[],
|
||||
basePath = '/'
|
||||
|
@ -47,7 +46,7 @@ export const generateRoutesFn1 = (
|
|||
const res: AppRouteRecordRaw[] = []
|
||||
|
||||
for (const route of routes) {
|
||||
const meta = route.meta as RouteMeta
|
||||
const meta = route.meta ?? {}
|
||||
// skip some route
|
||||
if (meta.hidden && !meta.canTo) {
|
||||
continue
|
||||
|
@ -70,7 +69,7 @@ export const generateRoutesFn1 = (
|
|||
if (isUrl(item) && (onlyOneChild === item || route.path === item)) {
|
||||
data = Object.assign({}, route)
|
||||
} else {
|
||||
const routePath = onlyOneChild ?? pathResolve(basePath, route.path)
|
||||
const routePath = (onlyOneChild ?? pathResolve(basePath, route.path)).trim()
|
||||
if (routePath === item || meta.followRoute === item) {
|
||||
data = Object.assign({}, route)
|
||||
}
|
||||
|
@ -79,7 +78,11 @@ export const generateRoutesFn1 = (
|
|||
|
||||
// recursive child routes
|
||||
if (route.children && data) {
|
||||
data.children = generateRoutesFn1(route.children, keys, pathResolve(basePath, data.path))
|
||||
data.children = generateRoutesByFrontEnd(
|
||||
route.children,
|
||||
keys,
|
||||
pathResolve(basePath, data.path)
|
||||
)
|
||||
}
|
||||
if (data) {
|
||||
res.push(data as AppRouteRecordRaw)
|
||||
|
@ -89,7 +92,7 @@ export const generateRoutesFn1 = (
|
|||
}
|
||||
|
||||
// 后端控制路由生成
|
||||
export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
|
||||
export const generateRoutesByServer = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {
|
||||
const res: AppRouteRecordRaw[] = []
|
||||
|
||||
for (const route of routes) {
|
||||
|
@ -112,7 +115,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
|
|||
}
|
||||
// recursive child routes
|
||||
if (route.children) {
|
||||
data.children = generateRoutesFn2(route.children)
|
||||
data.children = generateRoutesByServer(route.children)
|
||||
}
|
||||
res.push(data as AppRouteRecordRaw)
|
||||
}
|
||||
|
@ -122,7 +125,7 @@ export const generateRoutesFn2 = (routes: AppCustomRouteRecordRaw[]): AppRouteRe
|
|||
export const pathResolve = (parentPath: string, path: string) => {
|
||||
if (isUrl(path)) return path
|
||||
const childPath = path.startsWith('/') || !path ? path : `/${path}`
|
||||
return `${parentPath}${childPath}`.replace(/\/\//g, '/')
|
||||
return `${parentPath}${childPath}`.replace(/\/\//g, '/').trim()
|
||||
}
|
||||
|
||||
// 路由降级
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { JsonEditor } from '@/components/JsonEditor'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref, watch } from 'vue'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const defaultData = ref({
|
||||
title: '标题',
|
||||
content: '内容'
|
||||
})
|
||||
|
||||
watch(
|
||||
() => defaultData.value,
|
||||
(val) => {
|
||||
console.log(val)
|
||||
},
|
||||
{
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
setTimeout(() => {
|
||||
defaultData.value = {
|
||||
title: '异步标题',
|
||||
content: '异步内容'
|
||||
}
|
||||
}, 4000)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap :title="t('richText.jsonEditor')" :message="t('richText.jsonEditorDes')">
|
||||
<JsonEditor v-model="defaultData" />
|
||||
</ContentWrap>
|
||||
</template>
|
|
@ -442,6 +442,15 @@ const treeSelectData = [
|
|||
}
|
||||
]
|
||||
|
||||
// 模拟远程加载
|
||||
const getTreeSelectData = () => {
|
||||
return new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
resolve(treeSelectData)
|
||||
}, 3000)
|
||||
})
|
||||
}
|
||||
|
||||
let id = 0
|
||||
|
||||
const imageUrl = ref('')
|
||||
|
@ -1533,8 +1542,9 @@ const schema = reactive<FormSchema[]>([
|
|||
label: `${t('formDemo.treeSelect')}`,
|
||||
component: 'TreeSelect',
|
||||
// 远程加载option
|
||||
optionApi: () => {
|
||||
return treeSelectData
|
||||
optionApi: async () => {
|
||||
const res = await getTreeSelectData()
|
||||
return res
|
||||
}
|
||||
},
|
||||
{
|
||||
|
@ -1750,6 +1760,20 @@ const schema = reactive<FormSchema[]>([
|
|||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'field85',
|
||||
component: 'Divider',
|
||||
label: t('formDemo.jsonEditor')
|
||||
},
|
||||
{
|
||||
field: 'field86',
|
||||
component: 'JsonEditor',
|
||||
label: t('formDemo.default'),
|
||||
value: {
|
||||
a: 1,
|
||||
b: 2
|
||||
}
|
||||
}
|
||||
])
|
||||
</script>
|
||||
|
|
|
@ -3,10 +3,15 @@ import { ContentWrap } from '@/components/ContentWrap'
|
|||
import { ElInput } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||
|
||||
const { setTitle } = useTagsView()
|
||||
|
||||
const { params } = useRoute()
|
||||
|
||||
const val = ref(params.id as string)
|
||||
|
||||
setTitle(`详情页-${val.value}`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { ElInput } from 'element-plus'
|
||||
import { ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||
|
||||
const { setTitle } = useTagsView()
|
||||
|
||||
const { query } = useRoute()
|
||||
|
||||
const val = ref(query.id as string)
|
||||
|
||||
setTitle(`详情页query-${val.value}`)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap> 获取参数: <ElInput v-model="val" /> </ContentWrap>
|
||||
</template>
|
|
@ -1,15 +1,15 @@
|
|||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { useIntro } from '@/hooks/web/useIntro'
|
||||
import { ElButton } from 'element-plus'
|
||||
import { useGuide } from '@/hooks/web/useGuide'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const { introRef } = useIntro()
|
||||
const { drive } = useGuide()
|
||||
|
||||
const guideStart = () => {
|
||||
introRef.start()
|
||||
drive()
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -205,13 +205,6 @@ watch(
|
|||
|
||||
// 登录
|
||||
const signIn = async () => {
|
||||
await permissionStore.generateRoutes('none').catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
permissionStore.setIsAddRouters(true)
|
||||
push({ path: redirect.value || permissionStore.addRouters[0].path })
|
||||
|
||||
const formRef = await getElFormExpose()
|
||||
await formRef?.validate(async (isValid) => {
|
||||
if (isValid) {
|
||||
|
@ -227,7 +220,7 @@ const signIn = async () => {
|
|||
if (appStore.getDynamicRouter) {
|
||||
getRole()
|
||||
} else {
|
||||
await permissionStore.generateRoutes('none').catch(() => {})
|
||||
await permissionStore.generateRoutes('static').catch(() => {})
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
})
|
||||
|
@ -248,17 +241,16 @@ const getRole = async () => {
|
|||
const params = {
|
||||
roleName: formData.username
|
||||
}
|
||||
// admin - 模拟后端过滤菜单
|
||||
// test - 模拟前端过滤菜单
|
||||
const res =
|
||||
formData.username === 'admin' ? await getAdminRoleApi(params) : await getTestRoleApi(params)
|
||||
appStore.getDynamicRouter && appStore.getServerDynamicRouter
|
||||
? await getAdminRoleApi(params)
|
||||
: await getTestRoleApi(params)
|
||||
if (res) {
|
||||
const routers = res.data || []
|
||||
setStorage('roleRouters', routers)
|
||||
|
||||
formData.username === 'admin'
|
||||
? await permissionStore.generateRoutes('admin', routers).catch(() => {})
|
||||
: await permissionStore.generateRoutes('test', routers).catch(() => {})
|
||||
appStore.getDynamicRouter && appStore.getServerDynamicRouter
|
||||
? await permissionStore.generateRoutes('server', routers).catch(() => {})
|
||||
: await permissionStore.generateRoutes('frontEnd', routers).catch(() => {})
|
||||
|
||||
permissionStore.getAddRouters.forEach((route) => {
|
||||
addRoute(route as RouteRecordRaw) // 动态添加可访问路由表
|
||||
|
|
|
@ -1,223 +1,186 @@
|
|||
<script setup lang="ts">
|
||||
// import { ContentWrap } from '@/components/ContentWrap'
|
||||
// import { Search } from '@/components/Search'
|
||||
// import { useI18n } from '@/hooks/web/useI18n'
|
||||
// import { ElButton, ElTag } from 'element-plus'
|
||||
// import { Table } from '@/components/Table'
|
||||
// import { getTableListApi, delTableListApi } from '@/api/table'
|
||||
// import { useTable } from '@/hooks/web/useTable'
|
||||
// import { TableData } from '@/api/table/types'
|
||||
// import { h, ref, reactive } from 'vue'
|
||||
// import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
// import { useDictStore } from '@/store/modules/dict'
|
||||
// import { getDictOneApi } from '@/api/common'
|
||||
// import { TableColumn } from '@/types/table'
|
||||
import { CrudSchema, useCrudSchemas } from '@/hooks/web/useCrudSchemas'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { reactive } from 'vue'
|
||||
import { JsonEditor } from '@/components/JsonEditor'
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { ElRow, ElCol } from 'element-plus'
|
||||
|
||||
// const dictStore = useDictStore()
|
||||
const { t } = useI18n()
|
||||
|
||||
// const { register, tableObject, methods } = useTable<TableData>({
|
||||
// getListApi: getTableListApi,
|
||||
// delListApi: delTableListApi,
|
||||
// response: {
|
||||
// list: 'list',
|
||||
// total: 'total'
|
||||
// }
|
||||
// })
|
||||
const crudSchemas = reactive<CrudSchema[]>([
|
||||
{
|
||||
field: 'selection',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
type: 'selection'
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'index',
|
||||
label: t('tableDemo.index'),
|
||||
type: 'index',
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'title',
|
||||
label: t('tableDemo.title'),
|
||||
search: {
|
||||
component: 'Input'
|
||||
},
|
||||
form: {
|
||||
component: 'Input',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'author',
|
||||
label: t('tableDemo.author'),
|
||||
search: {
|
||||
hidden: true
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'display_time',
|
||||
label: t('tableDemo.displayTime'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'DatePicker',
|
||||
componentProps: {
|
||||
type: 'datetime',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'importance',
|
||||
label: t('tableDemo.importance'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'Select',
|
||||
componentProps: {
|
||||
style: {
|
||||
width: '100%'
|
||||
},
|
||||
options: [
|
||||
{
|
||||
label: '重要',
|
||||
value: 3
|
||||
},
|
||||
{
|
||||
label: '良好',
|
||||
value: 2
|
||||
},
|
||||
{
|
||||
label: '一般',
|
||||
value: 1
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'pageviews',
|
||||
label: t('tableDemo.pageviews'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
component: 'InputNumber',
|
||||
value: 0
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'content',
|
||||
label: t('exampleDemo.content'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
table: {
|
||||
show: false
|
||||
},
|
||||
form: {
|
||||
component: 'Editor',
|
||||
colProps: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
detail: {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
{
|
||||
field: 'action',
|
||||
width: '260px',
|
||||
label: t('tableDemo.action'),
|
||||
search: {
|
||||
hidden: true
|
||||
},
|
||||
form: {
|
||||
hidden: true
|
||||
},
|
||||
detail: {
|
||||
hidden: true
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
// const { getList, setSearchParams } = methods
|
||||
|
||||
// getList()
|
||||
|
||||
// const { t } = useI18n()
|
||||
|
||||
// const crudSchemas = reactive<CrudSchema[]>([
|
||||
// {
|
||||
// field: 'index',
|
||||
// label: t('tableDemo.index'),
|
||||
// type: 'index',
|
||||
// form: {
|
||||
// show: false
|
||||
// },
|
||||
// detail: {
|
||||
// show: false
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'title',
|
||||
// label: t('tableDemo.title'),
|
||||
// search: {
|
||||
// show: true
|
||||
// },
|
||||
// form: {
|
||||
// colProps: {
|
||||
// span: 24
|
||||
// }
|
||||
// },
|
||||
// detail: {
|
||||
// span: 24
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'author',
|
||||
// label: t('tableDemo.author')
|
||||
// },
|
||||
// {
|
||||
// field: 'display_time',
|
||||
// label: t('tableDemo.displayTime'),
|
||||
// form: {
|
||||
// component: 'DatePicker',
|
||||
// componentProps: {
|
||||
// type: 'datetime',
|
||||
// valueFormat: 'YYYY-MM-DD HH:mm:ss'
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'importance',
|
||||
// label: t('tableDemo.importance'),
|
||||
// formatter: (_: Recordable, __: TableColumn, cellValue: number) => {
|
||||
// return h(
|
||||
// ElTag,
|
||||
// {
|
||||
// type: cellValue === 1 ? 'success' : cellValue === 2 ? 'warning' : 'danger'
|
||||
// },
|
||||
// () =>
|
||||
// cellValue === 1
|
||||
// ? t('tableDemo.important')
|
||||
// : cellValue === 2
|
||||
// ? t('tableDemo.good')
|
||||
// : t('tableDemo.commonly')
|
||||
// )
|
||||
// },
|
||||
// search: {
|
||||
// show: true,
|
||||
// component: 'Select',
|
||||
// componentProps: {
|
||||
// options: dictStore.getDictObj.importance
|
||||
// }
|
||||
// },
|
||||
// form: {
|
||||
// component: 'Select',
|
||||
// componentProps: {
|
||||
// options: [
|
||||
// {
|
||||
// label: '重要',
|
||||
// value: 3
|
||||
// },
|
||||
// {
|
||||
// label: '良好',
|
||||
// value: 2
|
||||
// },
|
||||
// {
|
||||
// label: '一般',
|
||||
// value: 1
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'importance2',
|
||||
// label: `${t('tableDemo.importance')}2`,
|
||||
// search: {
|
||||
// show: true,
|
||||
// component: 'Select',
|
||||
// dictName: 'importance'
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'importance3',
|
||||
// label: `${t('tableDemo.importance')}3`,
|
||||
// search: {
|
||||
// show: true,
|
||||
// component: 'Select',
|
||||
// api: async () => {
|
||||
// const res = await getDictOneApi()
|
||||
// return res.data
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'pageviews',
|
||||
// label: t('tableDemo.pageviews'),
|
||||
// form: {
|
||||
// component: 'InputNumber',
|
||||
// value: 0
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'content',
|
||||
// label: t('exampleDemo.content'),
|
||||
// table: {
|
||||
// show: false
|
||||
// },
|
||||
// form: {
|
||||
// component: 'Editor',
|
||||
// colProps: {
|
||||
// span: 24
|
||||
// }
|
||||
// },
|
||||
// detail: {
|
||||
// span: 24
|
||||
// }
|
||||
// },
|
||||
// {
|
||||
// field: 'action',
|
||||
// width: '260px',
|
||||
// label: t('tableDemo.action'),
|
||||
// form: {
|
||||
// show: false
|
||||
// },
|
||||
// detail: {
|
||||
// show: false
|
||||
// }
|
||||
// }
|
||||
// ])
|
||||
|
||||
// const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
|
||||
// const delLoading = ref(false)
|
||||
|
||||
// const delData = async (row: TableData | null, multiple: boolean) => {
|
||||
// tableObject.currentRow = row
|
||||
// const { delList, getSelections } = methods
|
||||
// const selections = await getSelections()
|
||||
// delLoading.value = true
|
||||
// await delList(
|
||||
// multiple ? selections.map((v) => v.id) : [tableObject.currentRow?.id as string],
|
||||
// multiple
|
||||
// ).finally(() => {
|
||||
// delLoading.value = false
|
||||
// })
|
||||
// }
|
||||
const { allSchemas } = useCrudSchemas(crudSchemas)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap>
|
||||
<!-- <Search :schema="allSchemas.searchSchema" @search="setSearchParams" @reset="setSearchParams" />
|
||||
|
||||
<div class="mb-10px">
|
||||
<ElButton :loading="delLoading" type="danger" @click="delData(null, true)">
|
||||
{{ t('exampleDemo.del') }}
|
||||
</ElButton>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
v-model:pageSize="tableObject.pageSize"
|
||||
v-model:currentPage="tableObject.currentPage"
|
||||
:columns="allSchemas.tableColumns"
|
||||
:data="tableObject.tableList"
|
||||
:loading="tableObject.loading"
|
||||
:pagination="{
|
||||
total: tableObject.total
|
||||
}"
|
||||
@register="register"
|
||||
>
|
||||
<template #action="{ row }">
|
||||
<ElButton type="danger" @click="delData(row, false)">
|
||||
{{ t('exampleDemo.del') }}
|
||||
</ElButton>
|
||||
</template>
|
||||
</Table> -->
|
||||
<ContentWrap title="useCrudSchemas">
|
||||
<ElRow :gutter="20">
|
||||
<ElCol :span="24">
|
||||
<ContentWrap title="原始数据数据" class="mt-20px">
|
||||
<JsonEditor v-model="crudSchemas" />
|
||||
</ContentWrap>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ContentWrap title="查询组件数据结构" class="mt-20px">
|
||||
<JsonEditor v-model="allSchemas.searchSchema" />
|
||||
</ContentWrap>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ContentWrap title="表单组件数据结构" class="mt-20px">
|
||||
<JsonEditor v-model="allSchemas.formSchema" />
|
||||
</ContentWrap>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ContentWrap title="表格组件数据结构" class="mt-20px">
|
||||
<JsonEditor v-model="allSchemas.tableColumns" />
|
||||
</ContentWrap>
|
||||
</ElCol>
|
||||
<ElCol :span="24">
|
||||
<ContentWrap title="表格组件数据结构" class="mt-20px">
|
||||
<JsonEditor v-model="allSchemas.detailSchema" />
|
||||
</ContentWrap>
|
||||
</ElCol>
|
||||
</ElRow>
|
||||
</ContentWrap>
|
||||
</template>
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { ElButton } from 'element-plus'
|
||||
import { useTagsView } from '@/hooks/web/useTagsView'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
const { push } = useRouter()
|
||||
|
||||
const { closeAll, closeLeft, closeRight, closeOther, closeCurrent, refreshPage, setTitle } =
|
||||
useTagsView()
|
||||
|
||||
const closeAllTabs = () => {
|
||||
closeAll(() => {
|
||||
push('/dashboard/analysis')
|
||||
})
|
||||
}
|
||||
|
||||
const closeLeftTabs = () => {
|
||||
closeLeft()
|
||||
}
|
||||
|
||||
const closeRightTabs = () => {
|
||||
closeRight()
|
||||
}
|
||||
|
||||
const closeOtherTabs = () => {
|
||||
closeOther()
|
||||
}
|
||||
|
||||
const refresh = () => {
|
||||
refreshPage()
|
||||
}
|
||||
|
||||
const closeCurrentTab = () => {
|
||||
closeCurrent(undefined, () => {
|
||||
push('/dashboard/analysis')
|
||||
})
|
||||
}
|
||||
|
||||
const setTabTitle = () => {
|
||||
setTitle(new Date().getTime().toString())
|
||||
}
|
||||
|
||||
const setAnalysisTitle = () => {
|
||||
setTitle(`分析页-${new Date().getTime().toString()}`, '/dashboard/analysis')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="useTagsView">
|
||||
<ElButton type="primary" @click="closeAllTabs"> 关闭所有标签页 </ElButton>
|
||||
<ElButton type="primary" @click="closeLeftTabs"> 关闭左侧标签页 </ElButton>
|
||||
<ElButton type="primary" @click="closeRightTabs"> 关闭右侧标签页 </ElButton>
|
||||
<ElButton type="primary" @click="closeOtherTabs"> 关闭其他标签页 </ElButton>
|
||||
<ElButton type="primary" @click="closeCurrentTab"> 关闭当前标签页 </ElButton>
|
||||
<ElButton type="primary" @click="refresh"> 刷新当前标签页 </ElButton>
|
||||
<ElButton type="primary" @click="setTabTitle"> 修改当前标题 </ElButton>
|
||||
<ElButton type="primary" @click="setAnalysisTitle"> 修改分析页标题 </ElButton>
|
||||
</ContentWrap>
|
||||
</template>
|
|
@ -0,0 +1,80 @@
|
|||
<script setup lang="ts">
|
||||
import { ContentWrap } from '@/components/ContentWrap'
|
||||
import { Form, FormSchema } from '@/components/Form'
|
||||
import { useValidator } from '@/hooks/web/useValidator'
|
||||
import { useForm } from '@/hooks/web/useForm'
|
||||
import { reactive } from 'vue'
|
||||
import { FormItemRule } from 'element-plus'
|
||||
|
||||
const { formRegister, formMethods } = useForm()
|
||||
|
||||
const { getFormData } = formMethods
|
||||
|
||||
const { required, lengthRange, notSpace, notSpecialCharacters } = useValidator()
|
||||
|
||||
const formSchema = reactive<FormSchema[]>([
|
||||
{
|
||||
field: 'field1',
|
||||
label: '必填',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field2',
|
||||
label: '长度范围',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field3',
|
||||
label: '不能有空格',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field4',
|
||||
label: '不能有特殊字符',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field5',
|
||||
label: '是否相等-值1',
|
||||
component: 'Input'
|
||||
},
|
||||
{
|
||||
field: 'field6',
|
||||
label: '是否相等-值2',
|
||||
component: 'Input'
|
||||
}
|
||||
])
|
||||
|
||||
const rules = reactive<{
|
||||
[key: string]: FormItemRule[]
|
||||
}>({
|
||||
field1: [required()],
|
||||
field2: [
|
||||
lengthRange({
|
||||
min: 2,
|
||||
max: 5
|
||||
})
|
||||
],
|
||||
field3: [notSpace()],
|
||||
field4: [notSpecialCharacters()],
|
||||
field5: [
|
||||
{
|
||||
asyncValidator: async (_, val, callback) => {
|
||||
const formData = await getFormData()
|
||||
const { field6 } = formData
|
||||
if (val !== field6) {
|
||||
callback(new Error('两个值不相等'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ContentWrap title="useValidator">
|
||||
<Form :schema="formSchema" :rules="rules" @register="formRegister" />
|
||||
</ContentWrap>
|
||||
</template>
|
|
@ -27,10 +27,8 @@
|
|||
"@intlify/unplugin-vue-i18n/types",
|
||||
"vite/client",
|
||||
"element-plus/global",
|
||||
"@types/intro.js",
|
||||
"@types/qrcode",
|
||||
"vite-plugin-svg-icons/client",
|
||||
"unplugin-vue-define-options/macros-global"
|
||||
"vite-plugin-svg-icons/client"
|
||||
]
|
||||
},
|
||||
"include": ["src", "types/**/*.d.ts", "mock/**/*.ts"]
|
||||
|
|
|
@ -32,21 +32,23 @@ import { defineComponent } from 'vue'
|
|||
permission: ['edit','add', 'delete'] 设置该路由的权限
|
||||
}
|
||||
**/
|
||||
|
||||
interface RouteMetaCustom extends Record<string | number | symbol, unknown> {
|
||||
hidden?: boolean
|
||||
alwaysShow?: boolean
|
||||
title?: string
|
||||
icon?: string
|
||||
noCache?: boolean
|
||||
breadcrumb?: boolean
|
||||
affix?: boolean
|
||||
activeMenu?: string
|
||||
noTagsView?: boolean
|
||||
canTo?: boolean
|
||||
permission?: string[]
|
||||
}
|
||||
|
||||
declare module 'vue-router' {
|
||||
interface RouteMeta extends Record<string | number | symbol, unknown> {
|
||||
hidden?: boolean
|
||||
alwaysShow?: boolean
|
||||
title?: string
|
||||
icon?: string
|
||||
noCache?: boolean
|
||||
breadcrumb?: boolean
|
||||
affix?: boolean
|
||||
activeMenu?: string
|
||||
noTagsView?: boolean
|
||||
followAuth?: string
|
||||
canTo?: boolean
|
||||
permission?: string[]
|
||||
}
|
||||
interface RouteMeta extends RouteMetaCustom {}
|
||||
}
|
||||
|
||||
type Component<T = any> =
|
||||
|
@ -57,7 +59,7 @@ type Component<T = any> =
|
|||
declare global {
|
||||
declare interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||
name: string
|
||||
meta: RouteMeta
|
||||
meta: RouteMetaCustom
|
||||
component?: Component | string
|
||||
children?: AppRouteRecordRaw[]
|
||||
props?: Recordable
|
||||
|
@ -66,7 +68,7 @@ declare global {
|
|||
|
||||
declare interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {
|
||||
name: string
|
||||
meta: RouteMeta
|
||||
meta: RouteMetaCustom
|
||||
component: string
|
||||
path: string
|
||||
redirect: string
|
||||
|
|
|
@ -10,7 +10,6 @@ import { viteMockServe } from 'vite-plugin-mock'
|
|||
import PurgeIcons from 'vite-plugin-purge-icons'
|
||||
import VueI18nPlugin from "@intlify/unplugin-vue-i18n/vite"
|
||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||
import DefineOptions from "unplugin-vue-define-options/vite"
|
||||
import { createStyleImportPlugin, ElementPlusResolve } from 'vite-plugin-style-import'
|
||||
import UnoCSS from 'unocss/vite'
|
||||
|
||||
|
@ -32,7 +31,12 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||
return {
|
||||
base: env.VITE_BASE_PATH,
|
||||
plugins: [
|
||||
Vue(),
|
||||
Vue({
|
||||
script: {
|
||||
// 开启defineModel
|
||||
defineModel: true
|
||||
}
|
||||
}),
|
||||
VueJsx(),
|
||||
// WindiCSS(),
|
||||
progress(),
|
||||
|
@ -75,7 +79,6 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||
setupProdMockServer()
|
||||
`
|
||||
}),
|
||||
DefineOptions(),
|
||||
ViteEjsPlugin({
|
||||
title: env.VITE_APP_TITLE
|
||||
}),
|
||||
|
@ -147,7 +150,8 @@ export default ({ command, mode }: ConfigEnv): UserConfig => {
|
|||
'intro.js',
|
||||
'qrcode',
|
||||
'@wangeditor/editor',
|
||||
'@wangeditor/editor-for-vue'
|
||||
'@wangeditor/editor-for-vue',
|
||||
'vue-json-pretty'
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue