init
commit
8ecee5ddef
|
|
@ -0,0 +1,13 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
charset = utf-8
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
|
||||
[*.md]
|
||||
insert_final_newline = false
|
||||
trim_trailing_whitespace = false
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
VITE_PROXY=/api
|
||||
VITE_FILE_URL=https://file.zcloudchina.com/YTHFile
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
VITE_BASE=/
|
||||
VITE_BASE_URL=
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
VITE_BASE=/
|
||||
VITE_BASE_URL=
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
/src/test/
|
||||
/target/
|
||||
.idea
|
||||
|
||||
/node_modules
|
||||
/dist
|
||||
*.local
|
||||
package-lock.json
|
||||
yarn.lock
|
||||
pnpm-lock.yaml
|
||||
env.d.ts
|
||||
auto-imports.d.ts
|
||||
components.d.ts
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"prettier.enable": false,
|
||||
"editor.formatOnSave": false,
|
||||
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
|
||||
"eslint.rules.customizations": [
|
||||
{ "rule": "style/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "format/*", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-indent", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-spacing", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-spaces", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-order", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-dangle", "severity": "off", "fixable": true },
|
||||
{ "rule": "*-newline", "severity": "off", "fixable": true },
|
||||
{ "rule": "*quotes", "severity": "off", "fixable": true },
|
||||
{ "rule": "*semi", "severity": "off", "fixable": true }
|
||||
],
|
||||
|
||||
"eslint.validate": [
|
||||
"javascript",
|
||||
"javascriptreact",
|
||||
"typescript",
|
||||
"typescriptreact",
|
||||
"vue",
|
||||
"html",
|
||||
"markdown",
|
||||
"json",
|
||||
"json5",
|
||||
"jsonc",
|
||||
"yaml",
|
||||
"toml",
|
||||
"xml",
|
||||
"gql",
|
||||
"graphql",
|
||||
"astro",
|
||||
"svelte",
|
||||
"css",
|
||||
"less",
|
||||
"scss",
|
||||
"pcss",
|
||||
"postcss"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
# Vue 3 + Vite
|
||||
|
||||
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
||||
|
||||
## Recommended IDE Setup
|
||||
|
||||
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur) + [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin).
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
import antfu from "@antfu/eslint-config";
|
||||
|
||||
export default antfu({
|
||||
formatters: {
|
||||
html: false,
|
||||
css: true,
|
||||
},
|
||||
test: false,
|
||||
typescript: false,
|
||||
vue: true,
|
||||
stylistic: {
|
||||
semi: true,
|
||||
quotes: "double",
|
||||
},
|
||||
overrides: {
|
||||
vue: {
|
||||
"vue/no-template-shadow": "error",
|
||||
"vue/attribute-hyphenation": "error",
|
||||
"vue/html-end-tags": "error",
|
||||
"vue/component-name-in-template-casing": ["error", "kebab-case"],
|
||||
"vue/enforce-style-attribute": [
|
||||
"error",
|
||||
{ allow: ["scoped", "module"] },
|
||||
],
|
||||
"vue/v-on-event-hyphenation": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
autofix: true,
|
||||
},
|
||||
],
|
||||
"vue/require-explicit-emits": "error",
|
||||
},
|
||||
javascript: {
|
||||
"no-console": process.env.NODE_ENV === "production" ? "error" : "warn",
|
||||
"no-debugger": process.env.NODE_ENV === "production" ? "error" : "warn",
|
||||
"no-alert": process.env.NODE_ENV === "production" ? "error" : "warn",
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "VariableDeclarator[id.name='pd']",
|
||||
message: "不允许使用 pd,请改用有语义化的变量名",
|
||||
},
|
||||
{
|
||||
selector: "ObjectExpression > Property[key.name='pd']",
|
||||
message: "不允许使用 pd,请改用有语义化的变量名",
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
rules: {
|
||||
"antfu/top-level-function": "off",
|
||||
"node/prefer-global/process": "off",
|
||||
"linebreak-style": ["off", "windows"],
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="stylesheet" href="https://g.alicdn.com/apsara-media-box/imp-web-player/2.16.3/skins/default/aliplayer-min.css" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no" />
|
||||
<title>blank-vue-template</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<noscript>
|
||||
<strong>很抱歉,如果没有启用JavaScript,网站无法正常工作,请启用JavaScript使其正常工作。</strong>
|
||||
</noscript>
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
<script charset="utf-8" type="text/javascript" src="https://g.alicdn.com/apsara-media-box/imp-web-player/2.16.3/aliplayer-min.js"></script>
|
||||
<script type="text/javascript" src="https://api.map.baidu.com/api?v=1.0&type=webgl&ak=OElqFYoKiAH8KFtph8ftLKF5NlNrbCUr"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
{
|
||||
"name": "blank-vue-template",
|
||||
"type": "module",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"web-types": "./web-types.json",
|
||||
"scripts": {
|
||||
"dev": "vite --mode development",
|
||||
"build": "vite build --mode production",
|
||||
"preview": "vite preview",
|
||||
"lint": "eslint --ext .js,.vue --fix src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@icon-park/vue-next": "^1.4.2",
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"@vueuse/integrations": "^13.9.0",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^5.1.12",
|
||||
"animate.css": "^4.1.1",
|
||||
"autofit.js": "^3.2.3",
|
||||
"axios": "^1.7.9",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.13",
|
||||
"echarts": "^5.5.1",
|
||||
"element-plus": "^2.9.1",
|
||||
"lodash-es": "^4.17.21",
|
||||
"mitt": "^3.0.1",
|
||||
"normalize.css": "^8.0.1",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
"qrcode": "^1.5.4",
|
||||
"qs": "^6.13.1",
|
||||
"throttle-debounce": "^5.0.2",
|
||||
"v-viewer": "^3.0.21",
|
||||
"viewerjs": "^1.11.7",
|
||||
"vue": "^3.5.13",
|
||||
"vue-countup-v3": "^1.4.2",
|
||||
"vue-draggable-plus": "^0.3.5",
|
||||
"vue-esign": "^1.1.4",
|
||||
"vue-router": "^4.5.0",
|
||||
"vue3-pdfjs": "^0.1.6",
|
||||
"vue3-print-nb": "^0.1.4",
|
||||
"vue3-puzzle-vcode": "^1.1.7",
|
||||
"vue3-seamless-scroll": "^2.0.1",
|
||||
"zy-vue-library": "^1.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@antfu/eslint-config": "^5.4.1",
|
||||
"@types/node": "^18.19.68",
|
||||
"@vant/auto-import-resolver": "^1.2.1",
|
||||
"@vitejs/plugin-vue": "^5.2.1",
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.36.0",
|
||||
"eslint-plugin-format": "^1.0.2",
|
||||
"sass": "^1.83.0",
|
||||
"unplugin-auto-import": "^0.12.2",
|
||||
"unplugin-element-plus": "^0.8.0",
|
||||
"unplugin-vue-components": "^0.22.12",
|
||||
"vite": "^6.0.3",
|
||||
"vite-plugin-env-parse": "^1.0.15",
|
||||
"vite-plugin-eslint": "^1.8.1",
|
||||
"vite-plugin-remove-console": "^2.2.0"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {
|
||||
overrideBrowserslist: [
|
||||
"Android 4.1",
|
||||
"iOS 7.1",
|
||||
"Chrome > 31",
|
||||
"ff > 31",
|
||||
"ie >= 8",
|
||||
"> 1%",
|
||||
],
|
||||
grid: true,
|
||||
},
|
||||
// '@our-patches/postcss-px-to-viewport': {
|
||||
// unitToConvert: 'px',
|
||||
// viewportWidth: 1920,
|
||||
// unitPrecision: 3,
|
||||
// viewportUnit: 'vw',
|
||||
// selectorBlackList: ['.ignore', '.hairlines'],
|
||||
// minPixelValue: 1,
|
||||
// mediaQuery: false,
|
||||
// exclude: [/^node_modules$/],
|
||||
// include: [/BI/],
|
||||
// landscapeUnit: 'vw',
|
||||
// landscapeWidth: 750,
|
||||
// }
|
||||
},
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -0,0 +1,25 @@
|
|||
<script setup>
|
||||
import zhCn from "element-plus/dist/locale/zh-cn.mjs";
|
||||
import { useRequestLoading } from "zy-vue-library";
|
||||
|
||||
const loading = useRequestLoading();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<suspense>
|
||||
<template #default>
|
||||
<el-config-provider :locale="zhCn">
|
||||
<router-view
|
||||
v-loading.fullscreen.lock="loading"
|
||||
element-loading-text="加载中..."
|
||||
element-loading-background="rgba(0, 0, 0, 0.5)"
|
||||
/>
|
||||
</el-config-provider>
|
||||
</template>
|
||||
<template #fallback>
|
||||
<div>加载中...</div>
|
||||
</template>
|
||||
</suspense>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1 @@
|
|||
// 将常用的值储存成常量,防止重复使用写错
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
import mitt from "mitt";
|
||||
|
||||
export default mitt();
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<script setup>
|
||||
defineOptions({
|
||||
name: "Children",
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
import { getDataDictionariesList } from "@/request/data_dictionary.js";
|
||||
|
||||
// 无法确定parentId的数据字典
|
||||
export const appFnGetDataDictionary = async (params) => {
|
||||
const { dictionariesList } = await getDataDictionariesList(params);
|
||||
return dictionariesList;
|
||||
};
|
||||
// 导航栏
|
||||
export const appFnGetMenuNavList = async () => {
|
||||
const { dictionariesList } = await getDataDictionariesList({
|
||||
parentId: "7b2cf146798280ecf8f4dfbf8c4f59d8",
|
||||
});
|
||||
return dictionariesList;
|
||||
};
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
<script setup>
|
||||
import { AppLayout } from "zy-vue-library";
|
||||
import { useMenuStore } from "@/pinia/menu.js";
|
||||
import { useNavStore } from "@/pinia/nav.js";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
|
||||
defineOptions({
|
||||
name: "Layout",
|
||||
});
|
||||
const menuStore = useMenuStore();
|
||||
const userStore = useUserStore();
|
||||
const navStore = useNavStore();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<app-layout
|
||||
:menu-store="menuStore"
|
||||
:user-store="userStore"
|
||||
:nav-store="navStore"
|
||||
:user-name="userStore.getUserInfo.username"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
import { install } from "@icon-park/vue-next/es/all";
|
||||
import ElDialog from "element-plus/es/components/dialog/index";
|
||||
import ElSelect from "element-plus/es/components/select/index";
|
||||
import VueViewer from "v-viewer";
|
||||
import { createApp } from "vue";
|
||||
import print from "vue3-print-nb";
|
||||
import {
|
||||
configureAesSecret,
|
||||
configureAxios,
|
||||
configureDynamicRouter,
|
||||
permissionDirective,
|
||||
} from "zy-vue-library";
|
||||
import { useMenuStore } from "@/pinia/menu.js";
|
||||
import { useNavStore } from "@/pinia/nav.js";
|
||||
import { useRouterStore } from "@/pinia/router.js";
|
||||
import { useUserStore } from "@/pinia/user.js";
|
||||
import App from "./App";
|
||||
import pinia from "./pinia";
|
||||
import router from "./router";
|
||||
import "dayjs/locale/zh-cn";
|
||||
import "zy-vue-library/css/index.scss";
|
||||
import "normalize.css";
|
||||
import "animate.css";
|
||||
import "viewerjs/dist/viewer.css";
|
||||
import "element-plus/es/components/loading/style/css";
|
||||
import "element-plus/es/components/message/style/css";
|
||||
import "element-plus/es/components/message-box/style/css";
|
||||
import "element-plus/es/components/notification/style/css";
|
||||
|
||||
ElDialog.props.closeOnClickModal.default = false;
|
||||
ElDialog.props.closeOnPressEscape.default = false;
|
||||
ElSelect.props.filterable = { type: Boolean, default: true };
|
||||
ElSelect.props.clearable = { type: Boolean, default: true };
|
||||
const app = createApp(App);
|
||||
install(app, "icon");
|
||||
app
|
||||
.use(pinia)
|
||||
.use(router)
|
||||
.use(VueViewer, {
|
||||
defaultOptions: {
|
||||
zIndex: 9999,
|
||||
},
|
||||
})
|
||||
.use(print)
|
||||
.use(permissionDirective)
|
||||
.mount("#app");
|
||||
configureAesSecret({ encryptKey: "daac3ae52eff4cec" });
|
||||
configureAxios({ router, store: useUserStore() });
|
||||
configureDynamicRouter({
|
||||
router,
|
||||
stores: {
|
||||
routerStore: useRouterStore(),
|
||||
menuStore: useMenuStore(),
|
||||
userStore: useUserStore(),
|
||||
navStore: useNavStore(),
|
||||
},
|
||||
modules: import.meta.glob([
|
||||
"./views/**/*.vue",
|
||||
"!./views/**/components/*.vue",
|
||||
]),
|
||||
});
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { createPinia } from "pinia";
|
||||
import piniaPersistedstate from "pinia-plugin-persistedstate";
|
||||
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPersistedstate);
|
||||
|
||||
export default pinia;
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useMenuStore = defineStore("menuStore", {
|
||||
state: () => ({
|
||||
menusAll: [],
|
||||
menus: [],
|
||||
model: "",
|
||||
}),
|
||||
getters: {
|
||||
getMenus: state => state.menus,
|
||||
getModel: state => state.model,
|
||||
},
|
||||
actions: {
|
||||
setMenus(menus) {
|
||||
this.menusAll = menus;
|
||||
},
|
||||
setModel(model) {
|
||||
this.model = model;
|
||||
this.menus = [];
|
||||
for (let i = 0; i < this.menusAll.length; i++) {
|
||||
if (this.menusAll[i].meta.model === model) {
|
||||
this.menus.push(this.menusAll[i]);
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.sessionStorage,
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useNavStore = defineStore("navStore", {
|
||||
state: () => ({
|
||||
navList: [],
|
||||
}),
|
||||
getters: {
|
||||
getNavList: state => state.navList,
|
||||
},
|
||||
actions: {
|
||||
setNavList(navList) {
|
||||
this.navList = navList;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.sessionStorage,
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useRouterStore = defineStore("routerStore", {
|
||||
state: () => ({
|
||||
routers: [],
|
||||
}),
|
||||
getters: {
|
||||
getRouters: state => state.routers,
|
||||
},
|
||||
actions: {
|
||||
setRouters(routers) {
|
||||
this.routers = routers;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.sessionStorage,
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
import { defineStore } from "pinia";
|
||||
|
||||
export const useUserStore = defineStore("userStore", {
|
||||
state: () => ({
|
||||
userInfo: {},
|
||||
token: "",
|
||||
tokenTime: "",
|
||||
permissions: [],
|
||||
}),
|
||||
getters: {
|
||||
getUserInfo: state => state.userInfo,
|
||||
getToken: state => state.token,
|
||||
getTokenTime: state => state.tokenTime,
|
||||
getPermissions: state => state.permissions,
|
||||
},
|
||||
actions: {
|
||||
setUserInfo(userInfo) {
|
||||
this.userInfo = userInfo;
|
||||
},
|
||||
async setToken(token) {
|
||||
this.token = token;
|
||||
},
|
||||
async setTokenTime(tokenTime) {
|
||||
this.tokenTime = tokenTime;
|
||||
},
|
||||
setPermissions(permissions) {
|
||||
this.permissions = permissions;
|
||||
},
|
||||
},
|
||||
persist: {
|
||||
storage: window.sessionStorage,
|
||||
},
|
||||
});
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
import { postRequest } from "zy-vue-library";
|
||||
|
||||
export const Login = params => postRequest("/sys/login", params); // 登录
|
||||
export const getUserInfo = params => postRequest("/sys-user/info", params); // 获取用户信息
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { getRequest, postRequest } from "zy-vue-library";
|
||||
|
||||
// 获取部门树形
|
||||
export const getDepartmentTree = params =>
|
||||
getRequest("/sysdepartment/listTree", params);
|
||||
// 获取部门树形
|
||||
export const getDepartmentTreeByCorpId = params =>
|
||||
getRequest("/corp-department/list-tree", params);
|
||||
// 获取数据字典
|
||||
export const getDataDictionariesList = params =>
|
||||
postRequest("/sys/dictionaries/list", params);
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
import { postRequest } from "zy-vue-library";
|
||||
|
||||
export const getRoleList = params =>
|
||||
postRequest("/sys/role/listPage", params); // 角色管理列表
|
||||
export const getRoleListAll = params =>
|
||||
postRequest("/sys/role/listAll", params); // 角色管理列表所有
|
||||
export const setRoleDelete = params =>
|
||||
postRequest("/sys/role/delete", params); // 角色管理删除
|
||||
export const setRoleAdd = params => postRequest("/sys/role/save", params); // 角色管理添加
|
||||
export const setRoleEdit = params => postRequest("/sys/role/update", params); // 角色管理修改
|
||||
export const getRoleView = params => postRequest("/sys/role/info", params); // 角色管理查看
|
||||
export const getDataDictionaryList = params =>
|
||||
postRequest("/sys/dictionaries/listPage", params); // 数据字典列表
|
||||
export const setDataDictionaryDelete = params =>
|
||||
postRequest("/sys/dictionaries/delete", params); // 数据字典删除
|
||||
export const setDataDictionaryAdd = params =>
|
||||
postRequest("/sys/dictionaries/save", params); // 数据字典添加
|
||||
export const setDataDictionaryEdit = params =>
|
||||
postRequest("/sys/dictionaries/update", params); // 数据字典修改
|
||||
export const getDataDictionaryInfo = params =>
|
||||
postRequest("/sys/dictionaries/info", params); // 数据字典查看
|
||||
export const getDataDictionaryRepeat = params =>
|
||||
postRequest("/sys/dictionaries/findByBianma", params); // 数据字典验证编码是否重复
|
||||
export const getRouteList = params => postRequest("/sys/menu/list", params); // 菜单管理列表
|
||||
export const getRouteView = params => postRequest("/sys/menu/info", params); // 菜单管理查看
|
||||
export const getDataRouteKeyDuplication = params =>
|
||||
postRequest("/sys/menu/getDataRouteKeyDuplication", params); // 菜单检验key是否有重复
|
||||
export const setRouteAdd = params => postRequest("/sys/menu/save", params); // 菜单管理添加
|
||||
export const setRouteEdit = params => postRequest("/sys/menu/update", params); // 菜单管理修改
|
||||
export const setRouteDelete = params =>
|
||||
postRequest("/sys/menu/delete", params); // 菜单管理删除
|
||||
export const setRouteIcon = params => postRequest("/sys/menu/icon", params); // 菜单管理修改图标
|
||||
export const setRouteSort = params =>
|
||||
postRequest("/sys/menu/editMenuOrder", params); // 菜单管理排序
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
import { createRouter, createWebHashHistory } from "vue-router";
|
||||
import layout from "@/layout/index.vue";
|
||||
import children from "../components/children/index.vue";
|
||||
|
||||
const routes = [
|
||||
{
|
||||
path: "/login",
|
||||
name: "/login",
|
||||
meta: { title: "登录", isLogin: false },
|
||||
component: () => import("@/views/login/index"),
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
name: "app",
|
||||
redirect: "/index",
|
||||
meta: { title: "首页", isLogin: true },
|
||||
component: layout,
|
||||
children: [
|
||||
{
|
||||
path: "/index",
|
||||
name: "/index",
|
||||
meta: {
|
||||
title: "首页",
|
||||
breadcrumb: false,
|
||||
isMenu: false,
|
||||
isSubMenu: false,
|
||||
isBack: false,
|
||||
},
|
||||
component: () => import("@/views/index/index"),
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/404",
|
||||
name: "/404",
|
||||
meta: { title: "404", isLogin: false },
|
||||
component: () => import("@/views/404"),
|
||||
},
|
||||
{
|
||||
path: "/mobile",
|
||||
meta: { isLogin: false },
|
||||
component: children,
|
||||
children: [],
|
||||
},
|
||||
];
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
scrollBehavior(to, from, savedPosition) {
|
||||
if (savedPosition) {
|
||||
return savedPosition;
|
||||
}
|
||||
else {
|
||||
return { left: 0, top: 0 };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
export default router;
|
||||
|
|
@ -0,0 +1,574 @@
|
|||
<script setup>
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="containerer">
|
||||
<div class="container container-star">
|
||||
<div v-for="item in 30" :key="item" class="star-1" />
|
||||
<div v-for="item in 30" :key="item" class="star-2" />
|
||||
</div>
|
||||
<div class="container container-bird">
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top" />
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top" />
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top" />
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top" />
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top" />
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bird bird-anim">
|
||||
<div class="bird-container">
|
||||
<div class="wing wing-left">
|
||||
<div class="wing-left-top" />
|
||||
</div>
|
||||
<div class="wing wing-right">
|
||||
<div class="wing-right-top" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="container-title">
|
||||
<div class="title">
|
||||
<div class="number">
|
||||
4
|
||||
</div>
|
||||
<div class="moon">
|
||||
<div class="face">
|
||||
<div class="mouth" />
|
||||
<div class="eyes">
|
||||
<div class="eye-left" />
|
||||
<div class="eye-right" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="number">
|
||||
4
|
||||
</div>
|
||||
</div>
|
||||
<div class="subtitle">
|
||||
抱歉,您访问的页面不存在!
|
||||
</div>
|
||||
<button class="backbtn" @click="router.push({ name: '/index' })">
|
||||
返回首页
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import url("https://fonts.googleapis.com/css?family=Lato|Russo+One");
|
||||
*,
|
||||
*:after,
|
||||
*:before {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
body {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
.container {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
//stars
|
||||
.container-star {
|
||||
background-image: linear-gradient(to bottom, #04112b 0%, #474c88 70%, #a871d6 100%);
|
||||
&:after {
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0) 40%,
|
||||
rgba(15, 10, 38, 0.2) 100%
|
||||
);
|
||||
content: "";
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
.star-1 {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
animation: twinkle 5s infinite ease-in-out;
|
||||
&:after {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
transform: rotate(90deg);
|
||||
content: "";
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
border-radius: 50%;
|
||||
}
|
||||
&:before {
|
||||
background: radial-gradient(
|
||||
ellipse at center,
|
||||
rgba(255, 255, 255, 0.5) 0%,
|
||||
rgba(255, 255, 255, 0) 60%,
|
||||
rgba(255, 255, 255, 0) 100%
|
||||
);
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
content: "";
|
||||
top: -20%;
|
||||
left: -50%;
|
||||
}
|
||||
}
|
||||
@for $i from 1 through (30) {
|
||||
$top: random(100) + vh;
|
||||
$left: random(100) + vw;
|
||||
$size: random(6) + 3px;
|
||||
.star-1:nth-of-type(#{$i}) {
|
||||
top: $top;
|
||||
left: $left;
|
||||
width: $size;
|
||||
height: calc($size / 3);
|
||||
animation-delay: random(5) + s;
|
||||
&:before {
|
||||
width: $size * 2;
|
||||
height: $size * 2;
|
||||
top: -250%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.star-2 {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
background-color: #ffffff;
|
||||
animation: twinkle 5s infinite ease-in-out;
|
||||
}
|
||||
@for $i from 31 through (60) {
|
||||
$top: random(100) + vh;
|
||||
$left: random(100) + vw;
|
||||
$size: random(3) + 1px;
|
||||
.star-2:nth-of-type(#{$i}) {
|
||||
top: $top;
|
||||
left: $left;
|
||||
width: $size;
|
||||
height: $size;
|
||||
animation-delay: random(5) + s;
|
||||
&:before {
|
||||
width: $size * 2;
|
||||
height: $size * 2;
|
||||
top: -250%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//text and moon
|
||||
.container-title {
|
||||
width: 600px;
|
||||
height: 450px;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
position: absolute;
|
||||
color: white;
|
||||
line-height: 1;
|
||||
font-weight: 700;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
}
|
||||
.title > * {
|
||||
display: inline-block;
|
||||
font-size: 200px;
|
||||
}
|
||||
.number {
|
||||
text-shadow: 20px 20px 20px rgba(0, 0, 0, 0.2);
|
||||
padding: 0 0.2em;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 24px;
|
||||
margin-top: 3em;
|
||||
text-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
|
||||
font-weight: normal;
|
||||
}
|
||||
button {
|
||||
font-size: 14px;
|
||||
margin-top: 2em;
|
||||
padding: 0.5em 1em;
|
||||
letter-spacing: 1px;
|
||||
color: white;
|
||||
background-color: transparent;
|
||||
cursor: pointer;
|
||||
z-index: 999;
|
||||
border: 1px solid white;
|
||||
border-radius: 5px;
|
||||
text-shadow: 4px 4px 4px rgba(0, 0, 0, 0.2);
|
||||
transition: opacity 0.2s ease;
|
||||
&:hover {
|
||||
opacity: 0.7;
|
||||
}
|
||||
&:focus {
|
||||
outline: 0;
|
||||
}
|
||||
}
|
||||
.moon {
|
||||
position: relative;
|
||||
border-radius: 50%;
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
z-index: 2;
|
||||
background-color: #fff;
|
||||
box-shadow:
|
||||
0 0 10px #fff,
|
||||
0 0 20px #fff,
|
||||
0 0 30px #fff,
|
||||
0 0 40px #fff,
|
||||
0 0 70px #fff,
|
||||
0 0 80px #fff,
|
||||
0 0 100px #ff1177;
|
||||
animation: rotate 5s ease-in-out infinite;
|
||||
.face {
|
||||
top: 60%;
|
||||
left: 47%;
|
||||
position: absolute;
|
||||
.mouth {
|
||||
border-top-left-radius: 50%;
|
||||
border-bottom-right-radius: 50%;
|
||||
border-top-right-radius: 50%;
|
||||
background-color: #5c3191;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
position: absolute;
|
||||
animation: snore 5s ease-in-out infinite;
|
||||
transform: rotate(45deg);
|
||||
box-shadow: inset -4px -4px 4px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
.eyes {
|
||||
position: absolute;
|
||||
top: -30px;
|
||||
left: -30px;
|
||||
.eye-left,
|
||||
.eye-right {
|
||||
border: 4px solid #5c3191;
|
||||
width: 30px;
|
||||
height: 15px;
|
||||
border-bottom-left-radius: 100px;
|
||||
border-bottom-right-radius: 100px;
|
||||
border-top: 0;
|
||||
position: absolute;
|
||||
&:before,
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
width: 4px;
|
||||
height: 4px;
|
||||
background-color: #5c3191;
|
||||
top: -2px;
|
||||
left: -4px;
|
||||
}
|
||||
&:after {
|
||||
left: auto;
|
||||
right: -4px;
|
||||
}
|
||||
}
|
||||
.eye-right {
|
||||
left: 50px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//birds
|
||||
.container-bird {
|
||||
perspective: 2000px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
.bird {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
height: 40px;
|
||||
width: 50px;
|
||||
transform: translate3d(-100vw, 0, 0) rotateY(90deg);
|
||||
transform-style: preserve-3d;
|
||||
}
|
||||
.bird-container {
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
transform-style: preserve-3d;
|
||||
transform: translate3d(50px, 30px, -300px);
|
||||
}
|
||||
.wing {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
border-radius: 3px;
|
||||
transform-style: preserve-3d;
|
||||
transform-origin: center bottom;
|
||||
z-index: 300;
|
||||
}
|
||||
.wing-left {
|
||||
background: linear-gradient(to bottom, #a58dc4 0%, #7979a8 100%);
|
||||
transform: translate3d(0, 0, 0) rotateX(-30deg);
|
||||
animation: wingLeft 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
||||
}
|
||||
.wing-right {
|
||||
background: linear-gradient(to bottom, #d9d3e2 0%, #b8a5d1 100%);
|
||||
transform: translate3d(0, 0, 0) rotateX(-30deg);
|
||||
animation: wingRight 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
||||
}
|
||||
.wing-right-top,
|
||||
.wing-left-top {
|
||||
border-right: 25px solid transparent;
|
||||
border-left: 25px solid transparent;
|
||||
top: -20px;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
transform-origin: 100% 100%;
|
||||
}
|
||||
.wing-right-top {
|
||||
border-bottom: 20px solid #b8a5d1;
|
||||
transform: translate3d(0, 0, 0) rotateX(60deg);
|
||||
animation: wingRightTop 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
||||
}
|
||||
.wing-left-top {
|
||||
border-bottom: 20px solid #7979a8;
|
||||
transform: translate3d(0, 0, 0) rotateX(-60deg);
|
||||
animation: wingLeftTop 1.3s cubic-bezier(0.45, 0, 0.5, 0.95) infinite;
|
||||
}
|
||||
.bird-anim:nth-child(1) {
|
||||
animation: bird1 30s linear infinite forwards;
|
||||
}
|
||||
.bird-anim:nth-child(2) {
|
||||
animation: bird2 30s linear infinite forwards;
|
||||
animation-delay: 3s;
|
||||
z-index: -1;
|
||||
}
|
||||
.bird-anim:nth-child(3) {
|
||||
animation: bird3 30s linear infinite forwards;
|
||||
animation-delay: 5s;
|
||||
}
|
||||
.bird-anim:nth-child(4) {
|
||||
animation: bird4 30s linear infinite forwards;
|
||||
animation-delay: 7s;
|
||||
}
|
||||
.bird-anim:nth-child(5) {
|
||||
animation: bird5 30s linear infinite forwards;
|
||||
animation-delay: 14s;
|
||||
}
|
||||
.bird-anim:nth-child(6) {
|
||||
animation: bird6 30s linear infinite forwards;
|
||||
animation-delay: 10s;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
//keyframes
|
||||
@keyframes rotate {
|
||||
0%,
|
||||
100% {
|
||||
transform: rotate(-8deg);
|
||||
}
|
||||
50% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes snore {
|
||||
0%,
|
||||
100% {
|
||||
transform: scale(1) rotate(30deg);
|
||||
}
|
||||
50% {
|
||||
transform: scale(0.5) rotate(30deg);
|
||||
border-bottom-left-radius: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes twinkle {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wingLeft {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotateX(-50deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(0, -20px, 0) rotateX(-130deg);
|
||||
background: linear-gradient(to bottom, #d9d3e2 0%, #b8a5d1 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wingLeftTop {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotateX(-10deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(0px, 0px, 0) rotateX(-40deg);
|
||||
border-bottom: 20px solid #b8a5d1;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wingRight {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotateX(50deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(0, -20px, 0) rotateX(130deg);
|
||||
background: linear-gradient(to bottom, #a58dc4 0%, #7979a8 100%);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes wingRightTop {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate3d(0, 0, 0) rotateX(10deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate3d(0px, 0px, 0px) rotateX(40deg);
|
||||
border-bottom: 20px solid #7979a8;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird1 {
|
||||
0% {
|
||||
transform: translate3d(-120vw, -20px, -1000px) rotateY(-40deg) rotateX(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(100vw, -40vh, 1000px) rotateY(-40deg) rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird2 {
|
||||
0%,
|
||||
15% {
|
||||
transform: translate3d(100vw, -300px, -1000px) rotateY(10deg) rotateX(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(-100vw, -20px, -1000px) rotateY(10deg) rotateX(0deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird3 {
|
||||
0% {
|
||||
transform: translate3d(100vw, -50vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(-100vw, -10vh, 100px) rotateY(-5deg) rotateX(-20deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird4 {
|
||||
0% {
|
||||
transform: translate3d(100vw, 30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(-100vw, -30vh, 200px) rotateY(-5deg) rotateX(10deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird5 {
|
||||
0%,
|
||||
5% {
|
||||
transform: translate3d(100vw, 30vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(-100vw, 10vh, 400px) rotateY(-15deg) rotateX(-10deg);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes bird6 {
|
||||
0%,
|
||||
10% {
|
||||
transform: translate3d(-100vw, 20vh, -500px) rotateY(15deg) rotateX(10deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate3d(100vw, 40vh, -800px) rotateY(5deg) rotateX(10deg);
|
||||
}
|
||||
}
|
||||
@media screen and (max-width: 580px) {
|
||||
.container-404 {
|
||||
width: 100%;
|
||||
}
|
||||
.number {
|
||||
font-size: 100px;
|
||||
}
|
||||
.subtitle {
|
||||
font-size: 20px;
|
||||
padding: 0 1em;
|
||||
}
|
||||
.moon {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
}
|
||||
.face {
|
||||
transform: scale(0.7);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<script setup></script>
|
||||
|
||||
<template>
|
||||
<div>1</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
<script setup>
|
||||
import dayjs from "dayjs";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { ref } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { aesEncrypt, AppVerification, useForm } from "zy-vue-library";
|
||||
import { useUserStore } from "@/pinia/user";
|
||||
import { getUserInfo, Login } from "@/request/api";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const router = useRouter();
|
||||
const { formRef, validate } = useForm();
|
||||
const verificationPass = ref(false);
|
||||
const form = ref({
|
||||
username: "",
|
||||
password: "",
|
||||
code: "",
|
||||
});
|
||||
const rules = {
|
||||
username: [{ required: true, message: "请输入用户名", trigger: "blur" }],
|
||||
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
|
||||
code: [{ required: true, message: "请输入验证码", trigger: "blur" }],
|
||||
};
|
||||
const fnLogin = debounce(
|
||||
1000,
|
||||
() => {
|
||||
if (import.meta.env.DEV) {
|
||||
fnSubmitLogin();
|
||||
return;
|
||||
}
|
||||
if (verificationPass.value) {
|
||||
fnSubmitLogin();
|
||||
}
|
||||
else {
|
||||
ElMessage.warning("请进行登录验证");
|
||||
}
|
||||
},
|
||||
{ atBegin: true },
|
||||
);
|
||||
async function fnSubmitLogin() {
|
||||
await validate("请输入用户名密码");
|
||||
const { token } = await Login({
|
||||
username: aesEncrypt(form.value.username),
|
||||
password: aesEncrypt(form.value.password),
|
||||
});
|
||||
await userStore.setToken(token);
|
||||
await userStore.setTokenTime(dayjs().format("YYYY-MM-DD HH:mm:ss"));
|
||||
const { user } = await getUserInfo();
|
||||
userStore.setUserInfo({
|
||||
...userStore.getUserInfo,
|
||||
...user,
|
||||
});
|
||||
await router.replace("/index");
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login login-container">
|
||||
<div class="logo">
|
||||
<img
|
||||
src="https://picsum.photos/seed/picsum/500/84"
|
||||
alt=""
|
||||
width="500"
|
||||
height="84"
|
||||
>
|
||||
</div>
|
||||
<div class="form">
|
||||
<div class="title">
|
||||
template
|
||||
</div>
|
||||
<el-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
@submit.prevent="fnLogin"
|
||||
>
|
||||
<el-form-item prop="username">
|
||||
<el-input
|
||||
v-model="form.username"
|
||||
placeholder="请输入用户名"
|
||||
tabindex="1"
|
||||
>
|
||||
<template #prepend>
|
||||
<icon-people theme="filled" size="16" fill="#909399" />
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
type="password"
|
||||
placeholder="请输入密码"
|
||||
tabindex="2"
|
||||
>
|
||||
<template #prepend>
|
||||
<icon-lock
|
||||
theme="filled"
|
||||
size="16"
|
||||
fill="#909399"
|
||||
:stroke-width="3"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<app-verification v-model:verification-pass="verificationPass" @update:verification-pass="fnLogin" />
|
||||
</el-form-item>
|
||||
<el-form-item class="button">
|
||||
<el-button native-type="submit">
|
||||
登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="footer">
|
||||
ICP备案号: 冀ICP备15003849号 技术支持:河北秦安安全科技股份有限公司
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.login-container {
|
||||
height: 100vh;
|
||||
width: 100%;
|
||||
background-color: #2d3a4b;
|
||||
overflow: hidden;
|
||||
background-image: url(https://picsum.photos/seed/picsum/1920/1080);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
.login {
|
||||
width: 100%;
|
||||
max-width: 1920px;
|
||||
margin: 0 auto;
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
background-color: #fff;
|
||||
|
||||
.logo {
|
||||
margin-top: 20px;
|
||||
margin-left: 20px;
|
||||
width: 100%;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.form {
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 0 20px rgb(109 109 109 / 40%);
|
||||
position: absolute;
|
||||
right: 230px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 400px;
|
||||
height: 440px;
|
||||
padding: 40px 50px;
|
||||
background-color: #fff;
|
||||
z-index: 1;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 40px;
|
||||
left: -30px;
|
||||
width: calc(100% + 60px);
|
||||
height: calc(100% - 80px);
|
||||
border-radius: 10px;
|
||||
background-color: rgba(255, 255, 255, 0.3);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
&:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: -15px;
|
||||
width: calc(100% + 30px);
|
||||
height: calc(100% - 40px);
|
||||
border-radius: 10px;
|
||||
background-color: rgba(255, 255, 255, 0.5);
|
||||
z-index: -2;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 20px;
|
||||
color: #000;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
.el-form-item {
|
||||
.el-input {
|
||||
height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
.el-button {
|
||||
background: #0a7dfe;
|
||||
height: 45px;
|
||||
width: 100%;
|
||||
color: #ffffff;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
font-size: 14px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
text-align: center;
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
line-height: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep {
|
||||
.el-input-group__prepend {
|
||||
padding-top: 5px !important;
|
||||
}
|
||||
|
||||
.el-carousel__indicators {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
<script setup>
|
||||
import { ElMessage } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { useForm } from "zy-vue-library";
|
||||
import {
|
||||
getDataDictionaryRepeat,
|
||||
setDataDictionaryAdd,
|
||||
setDataDictionaryEdit,
|
||||
} from "@/request/system_management.js";
|
||||
|
||||
const props = defineProps({
|
||||
type: { type: String, required: true },
|
||||
parent: {
|
||||
type: Object,
|
||||
required: false,
|
||||
default: () => ({
|
||||
dictionariesId: "0",
|
||||
name: "(无)此项为顶级菜单",
|
||||
bianma: "",
|
||||
parentIds: "",
|
||||
parentBianmas: "",
|
||||
parentNames: "",
|
||||
rootId: "",
|
||||
}),
|
||||
},
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
const { formRef, validate, reset } = useForm();
|
||||
const rules = {
|
||||
name: [
|
||||
{ required: true, message: "字典名称不能为空", trigger: "change" },
|
||||
{ min: 2, max: 100, message: "长度在2到100个字符", trigger: "blur" },
|
||||
],
|
||||
bianma: [
|
||||
{ required: true, message: "字典编码名称不能为空", trigger: "change" },
|
||||
{ min: 2, max: 100, message: "长度在2到100个字符", trigger: "blur" },
|
||||
],
|
||||
orderBy: [
|
||||
{ required: true, message: "排序不能为空", trigger: ["change", "blur"] },
|
||||
{
|
||||
type: "number",
|
||||
message: "排序必须为数字",
|
||||
trigger: ["change", "blur"],
|
||||
},
|
||||
],
|
||||
};
|
||||
const fnClose = () => {
|
||||
reset();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
if (props.type === "add") {
|
||||
const { dictionaries } = await getDataDictionaryRepeat({
|
||||
bianma: form.value.bianma,
|
||||
});
|
||||
if (dictionaries) {
|
||||
ElMessage.error("添加失败,编码重复");
|
||||
return;
|
||||
}
|
||||
await setDataDictionaryAdd({
|
||||
...form.value,
|
||||
parentId: props.parent.dictionariesId,
|
||||
parentIds:
|
||||
props.parent.dictionariesId === "0"
|
||||
? ""
|
||||
: (props.parent.parentIds ? `${props.parent.parentIds},` : "")
|
||||
+ props.parent.dictionariesId,
|
||||
parentBianmas:
|
||||
props.parent.dictionariesId === "0"
|
||||
? ""
|
||||
: (props.parent.parentBianmas
|
||||
? `${props.parent.parentBianmas},`
|
||||
: "") + props.parent.bianma,
|
||||
parentNames:
|
||||
props.parent.dictionariesId === "0"
|
||||
? ""
|
||||
: (props.parent.parentNames ? `${props.parent.parentNames},` : "")
|
||||
+ props.parent.name,
|
||||
rootId: props.parent.dictionariesId === "0" ? "" : props.parent.rootId,
|
||||
dictionariesId: undefined,
|
||||
});
|
||||
}
|
||||
if (props.type === "edit") {
|
||||
await setDataDictionaryEdit({
|
||||
...form.value,
|
||||
});
|
||||
}
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="type === 'edit' ? '修改' : '新增'"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form-item label="上级菜单">
|
||||
<el-tag>{{ parent.name }}</el-tag>
|
||||
</el-form-item>
|
||||
<el-form-item label="名称" prop="name">
|
||||
<el-input v-model="form.name" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="编码" prop="bianma">
|
||||
<el-input
|
||||
v-model="form.bianma"
|
||||
:disabled="type === 'edit'"
|
||||
placeholder="请输入编码"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="排序" prop="orderBy">
|
||||
<el-input v-model.number="form.orderBy" placeholder="请输入排序" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="descr">
|
||||
<el-input
|
||||
v-model="form.descr"
|
||||
:autosize="{ minRows: 1 }"
|
||||
type="textarea"
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="fnClose">
|
||||
取 消
|
||||
</el-button>
|
||||
<el-button type="primary" @click="fnSubmit">
|
||||
确 定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,148 @@
|
|||
<script setup>
|
||||
import { ArrowLeft, ArrowRight } from "@element-plus/icons-vue";
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { nextTick, ref } from "vue";
|
||||
import { onBeforeRouteUpdate, useRoute, useRouter } from "vue-router";
|
||||
import { AppTable, useListData } from "zy-vue-library";
|
||||
import {
|
||||
getDataDictionaryInfo,
|
||||
getDataDictionaryList,
|
||||
setDataDictionaryDelete,
|
||||
} from "@/request/system_management.js";
|
||||
import Add from "./components/add.vue";
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const parentIdDefault = "0";
|
||||
const parentNameDefault = "(无)此项为顶级菜单";
|
||||
const parentId = ref(route.query.parentId || parentIdDefault);
|
||||
const parentName = ref(route.query.parentName || parentNameDefault);
|
||||
const { list, pagination, resetPagination, getData } = useListData(
|
||||
getDataDictionaryList,
|
||||
{
|
||||
params: () => ({ parentId: parentId.value }),
|
||||
},
|
||||
);
|
||||
const addOrEditDialog = ref({
|
||||
visible: false,
|
||||
type: "",
|
||||
form: {
|
||||
name: "",
|
||||
bianma: "",
|
||||
orderBy: "",
|
||||
descr: "",
|
||||
parentIds: "",
|
||||
parentBianmas: "",
|
||||
parentNames: "",
|
||||
rootId: "",
|
||||
},
|
||||
parent: {
|
||||
dictionariesId: parentIdDefault,
|
||||
name: parentNameDefault,
|
||||
bianma: "",
|
||||
orderBy: "",
|
||||
descr: "",
|
||||
parentIds: "",
|
||||
parentBianmas: "",
|
||||
parentNames: "",
|
||||
rootId: "",
|
||||
},
|
||||
});
|
||||
onBeforeRouteUpdate((to) => {
|
||||
parentId.value = to.query.parentId || parentIdDefault;
|
||||
parentName.value = to.query.parentName || parentNameDefault;
|
||||
resetPagination();
|
||||
});
|
||||
const fnDelete = async (dictionariesId) => {
|
||||
await ElMessageBox.confirm(`确定要删除吗?`, { type: "warning" });
|
||||
await setDataDictionaryDelete({ dictionariesId });
|
||||
ElMessage.success("删除成功");
|
||||
resetPagination();
|
||||
};
|
||||
const fnAddOrEdit = async (dictionariesId, type) => {
|
||||
addOrEditDialog.value.visible = true;
|
||||
addOrEditDialog.value.type = type;
|
||||
if (type === "add" && parentId.value !== "0") {
|
||||
const resData = await getDataDictionaryInfo({
|
||||
dictionariesId: parentId.value,
|
||||
});
|
||||
addOrEditDialog.value.parent = resData.dictionaries;
|
||||
}
|
||||
await nextTick();
|
||||
if (type === "edit") {
|
||||
const resData = await getDataDictionaryInfo({ dictionariesId });
|
||||
addOrEditDialog.value.form = resData.dictionaries;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<app-table v-model:pagination="pagination" :data="list" @get-data="getData">
|
||||
<el-table-column label="名称">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="
|
||||
router.push({
|
||||
path: '/system_management/data_dictionary',
|
||||
query: {
|
||||
parentName: row.name,
|
||||
parentId: row.dictionariesId,
|
||||
},
|
||||
})
|
||||
"
|
||||
>
|
||||
{{ row.name }} <el-icon><arrow-right /></el-icon>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="bianma" label="编码" />
|
||||
<el-table-column prop="dictionariesId" label="ID" width="300" />
|
||||
<el-table-column prop="orderBy" label="排序" width="50" />
|
||||
<el-table-column label="操作" width="100">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnAddOrEdit(row.dictionariesId, 'edit')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
text
|
||||
link
|
||||
@click="fnDelete(row.dictionariesId)"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #button>
|
||||
<el-button type="primary" @click="fnAddOrEdit('', 'add')">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="parentId !== '0'"
|
||||
:icon="ArrowLeft"
|
||||
@click="router.back()"
|
||||
>
|
||||
返回
|
||||
</el-button>
|
||||
</template>
|
||||
</app-table>
|
||||
<add
|
||||
v-model:form="addOrEditDialog.form"
|
||||
v-model:visible="addOrEditDialog.visible"
|
||||
:parent="addOrEditDialog.parent"
|
||||
:type="addOrEditDialog.type"
|
||||
@get-data="resetPagination"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<script setup>
|
||||
import { ElMessage } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { useForm } from "zy-vue-library";
|
||||
import { setRouteAdd, setRouteEdit } from "@/request/system_management.js";
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, required: true },
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
const { formRef, validate, reset } = useForm();
|
||||
const rules = {
|
||||
name: [{ required: true, message: "请输入按钮名称", trigger: "blur" }],
|
||||
perms: [{ required: true, message: "请输入标识", trigger: "blur" }],
|
||||
};
|
||||
const fnClose = () => {
|
||||
reset();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
const params = {
|
||||
...form.value,
|
||||
type: 2,
|
||||
};
|
||||
props.title === "编辑"
|
||||
? await setRouteEdit({ ...params })
|
||||
: await setRouteAdd(params);
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
width="60%"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-width="100px">
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<el-form-item label="当前路由" prop="parentMenuId">
|
||||
{{ form.currentName || "首页" }}
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="按钮名称" prop="name">
|
||||
<el-input v-model="form.name" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="标识" prop="perms">
|
||||
<el-input v-model="form.perms" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="fnSubmit">
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="fnClose">
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,185 @@
|
|||
<script setup>
|
||||
import icons from "@icon-park/vue-next/icons.json";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { ref } from "vue";
|
||||
import { paging } from "zy-vue-library";
|
||||
import { setRouteIcon } from "@/request/system_management.js";
|
||||
|
||||
const props = defineProps({
|
||||
menuId: { type: Number, required: true },
|
||||
meta: { type: Object, required: true },
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const keywords = ref("");
|
||||
const svgList = ref([]);
|
||||
const pagination = ref({
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
total: icons.length,
|
||||
});
|
||||
const iconIndex = ref("");
|
||||
const fnSetPagination = () => {
|
||||
pagination.value = {
|
||||
currentPage: 1,
|
||||
pageSize: 50,
|
||||
total: icons.length,
|
||||
};
|
||||
};
|
||||
const fnSearch = () => {
|
||||
const filterIcons = icons.filter(
|
||||
item =>
|
||||
item.name.includes(keywords.value)
|
||||
|| item.title.includes(keywords.value),
|
||||
);
|
||||
svgList.value = paging(
|
||||
filterIcons,
|
||||
pagination.value.currentPage,
|
||||
pagination.value.pageSize,
|
||||
);
|
||||
pagination.value.total = filterIcons.length;
|
||||
};
|
||||
const fnInit = () => {
|
||||
fnSetPagination();
|
||||
fnSearch();
|
||||
};
|
||||
fnInit();
|
||||
const fnClose = () => {
|
||||
iconIndex.value = "";
|
||||
keywords.value = "";
|
||||
fnInit();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await setRouteIcon({
|
||||
menuId: props.menuId,
|
||||
meta: JSON.stringify({
|
||||
...props.meta,
|
||||
icon: iconIndex.value !== "" ? svgList.value[iconIndex.value].name : "",
|
||||
}),
|
||||
});
|
||||
ElMessage.success("设置成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
title="设置图标"
|
||||
width="40%"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<div>
|
||||
<a href="https://iconpark.oceanengine.com/official" target="_blank">
|
||||
点击此处
|
||||
</a>
|
||||
查看所有图标
|
||||
</div>
|
||||
<div class="mt-20" style="display: flex; align-items: center">
|
||||
当前图标:
|
||||
<component
|
||||
:is="`icon-${meta.icon}`"
|
||||
theme="filled"
|
||||
fill="#a5b2c2"
|
||||
size="38"
|
||||
:stroke-width="3"
|
||||
/>
|
||||
</div>
|
||||
<div class="mt-20">
|
||||
<el-form label-width="80px" @submit.prevent="fnInit">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="图标名称">
|
||||
<el-input v-model="keywords" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label-width="10px">
|
||||
<el-button type="primary" native-type="submit">
|
||||
搜索
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</el-form>
|
||||
</div>
|
||||
<div class="icon_container">
|
||||
<template v-for="(item, index) in svgList" :key="index">
|
||||
<el-tooltip :content="item.title">
|
||||
<div
|
||||
class="item"
|
||||
:class="{ active: iconIndex === index }"
|
||||
@click="iconIndex = index"
|
||||
>
|
||||
<component
|
||||
:is="`icon-${item.name}`"
|
||||
theme="filled"
|
||||
fill="#a5b2c2"
|
||||
size="38"
|
||||
:stroke-width="3"
|
||||
/>
|
||||
</div>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</div>
|
||||
<div class="mt-20 flex-end">
|
||||
<el-pagination
|
||||
v-model:current-page="pagination.currentPage"
|
||||
v-model:page-size="pagination.pageSize"
|
||||
size="small"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="pagination.total"
|
||||
@update:current-page="fnSearch"
|
||||
@update:page-size="fnSearch"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="fnSubmit">
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="fnClose">
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.icon_container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.item {
|
||||
flex-basis: calc(5% - 10px);
|
||||
border: 1px solid var(--el-border-color);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 10px;
|
||||
margin-right: 13px;
|
||||
padding: 10px;
|
||||
cursor: pointer;
|
||||
|
||||
&:nth-child(10n) {
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
border: 1px solid #79bbff;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flex-end {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,218 @@
|
|||
<script setup>
|
||||
import { ElMessage } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { useForm } from "zy-vue-library";
|
||||
import {
|
||||
getDataRouteKeyDuplication,
|
||||
setRouteAdd,
|
||||
setRouteEdit,
|
||||
} from "@/request/system_management.js";
|
||||
|
||||
const props = defineProps({
|
||||
title: { type: String, required: true },
|
||||
navList: { type: Array, required: true },
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
const { formRef, validate, reset } = useForm();
|
||||
const rules = {
|
||||
component: [{ required: true, message: "请输入文件位置", trigger: "blur" }],
|
||||
orderNum: [
|
||||
{ required: true, message: "请输入序号", trigger: "blur" },
|
||||
{ type: "number", message: "序号必须是数字", trigger: "blur" },
|
||||
],
|
||||
};
|
||||
const fnClose = () => {
|
||||
reset();
|
||||
visible.value = false;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
if (form.value.routeKey) {
|
||||
const { menu } = await getDataRouteKeyDuplication({
|
||||
routeKey: form.value.routeKey,
|
||||
menuId: form.value.menuId,
|
||||
});
|
||||
if (menu) {
|
||||
ElMessage.error("路由名称(唯一标识)重复,请修改!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
const meta = {
|
||||
title: form.value.title,
|
||||
model: form.value.model,
|
||||
activeMenu: form.value.activeMenu,
|
||||
isMenu: form.value.isMenu,
|
||||
isLogin: form.value.isLogin,
|
||||
breadcrumb: form.value.breadcrumb,
|
||||
isBreadcrumb: form.value.isBreadcrumb,
|
||||
isSubMenu: form.value.isSubMenu,
|
||||
isBack: form.value.isBack,
|
||||
icon: form.value.icon || "",
|
||||
props: !!form.value.props,
|
||||
};
|
||||
const params = {
|
||||
path: form.value.path,
|
||||
name: form.value.title,
|
||||
parentId: form.value.parentId || 0,
|
||||
orderNum: form.value.orderNum,
|
||||
component: form.value.component,
|
||||
perms: form.value.perms,
|
||||
redirect: form.value.redirect,
|
||||
meta: JSON.stringify(meta),
|
||||
type: 1,
|
||||
menuId: form.value.menuId,
|
||||
props: form.value.props,
|
||||
routeKey: form.value.routeKey,
|
||||
};
|
||||
props.title === "编辑"
|
||||
? await setRouteEdit({ ...params })
|
||||
: await setRouteAdd(params);
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="title"
|
||||
width="60%"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top">
|
||||
<el-form-item label="上级路由" prop="parentMenuId">
|
||||
{{ form.parentName }}
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="路由名称(在左侧菜单和面包屑中显示的名字)"
|
||||
prop="title"
|
||||
>
|
||||
<el-input v-model="form.title" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="路由name(唯一值,有name可以使用params参数,不要使用中文,对应vue-router中的name)"
|
||||
:rules="[
|
||||
{
|
||||
required: form.props === 1,
|
||||
message: '路由名称不能为空',
|
||||
trigger: 'blur',
|
||||
},
|
||||
]"
|
||||
prop="routeKey"
|
||||
>
|
||||
<el-input v-model="form.routeKey" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="路由地址(从/开始,对应vue-router中的path)"
|
||||
prop="path"
|
||||
>
|
||||
<el-input v-model="form.path" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="路由重定向地址(重定向到哪一个路由,子级路由第一个的路由地址,(没有子级路由不需要填写))"
|
||||
prop="redirect"
|
||||
>
|
||||
<el-input v-model="form.redirect" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="文件位置(路由对应的组件位置(必填,views下的文件,views和.vue不需要填写,如果是children只需要填写children))"
|
||||
prop="component"
|
||||
>
|
||||
<el-input v-model="form.component" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="model(菜单在哪个头部导航中(一级路由需要填写))"
|
||||
prop="model"
|
||||
>
|
||||
<el-select v-model="form.model" clearable>
|
||||
<el-option
|
||||
v-for="item in navList"
|
||||
:key="item.dictionariesId"
|
||||
:label="item.name"
|
||||
:value="item.bianma"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="权限标识" prop="perms">
|
||||
<el-input v-model="form.perms" />
|
||||
</el-form-item>
|
||||
<el-form-item label="序号" prop="orderNum">
|
||||
<el-input v-model.number="form.orderNum" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="选中的菜单(当前路由选中状态是哪个导航(只有当父级路由或祖先级路由’当前菜单是否显示子菜单为否‘时需要填写,设置‘当前菜单是否显示子菜单为否’的路由地址))"
|
||||
prop="activeMenu"
|
||||
>
|
||||
<el-input v-model="form.activeMenu" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="是否显示当前菜单(左侧菜单中是否显示)"
|
||||
prop="isMenu"
|
||||
>
|
||||
<el-select v-model="form.isMenu">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否需要登录才可以访问" prop="isLogin">
|
||||
<el-select v-model="form.isLogin">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前页是否显示在面包屑中" prop="breadcrumb">
|
||||
<el-select v-model="form.breadcrumb">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前页是否显示面包屑" prop="isBreadcrumb">
|
||||
<el-select v-model="form.isBreadcrumb">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="当前菜单是否显示子菜单(菜单有增删改查等等子路由的情况下需要勾选为否,勾选为否下级所有子菜单都不会在左侧菜单中显示)"
|
||||
prop="isSubMenu"
|
||||
>
|
||||
<el-select v-model="form.isSubMenu">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="当前页是否显示全局返回按钮" prop="isBack">
|
||||
<el-select v-model="form.isBack">
|
||||
<el-option label="是" :value="true" />
|
||||
<el-option label="否" :value="false" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
label="是否将route.params解构为组件的props(需要将页面当成弹窗里的组件使用时需要选择是,对应vue-router中的props)"
|
||||
prop="props"
|
||||
>
|
||||
<el-select v-model="form.props">
|
||||
<el-option label="是" :value="1" />
|
||||
<el-option label="否" :value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="fnSubmit">
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="fnClose">
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
|
|
@ -0,0 +1,84 @@
|
|||
<script setup>
|
||||
import { ElMessage } from "element-plus";
|
||||
import { cloneDeep } from "lodash-es";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { VueDraggable } from "vue-draggable-plus";
|
||||
import { setRouteSort } from "@/request/system_management.js";
|
||||
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const modelValue = defineModel({ type: Array, required: true });
|
||||
const fnClose = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
const fnMetaToJson = (menuList) => {
|
||||
for (let i = 0; i < menuList.length; i++) {
|
||||
menuList[i].meta = JSON.stringify(menuList[i].meta);
|
||||
if (menuList[i].list.length > 0) {
|
||||
fnMetaToJson(menuList[i].list);
|
||||
}
|
||||
}
|
||||
return menuList;
|
||||
};
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
const menuList = fnMetaToJson(cloneDeep(modelValue.value));
|
||||
for (let i = 0; i < menuList.length; i++) {
|
||||
menuList[i].orderNum = i + 1;
|
||||
}
|
||||
await setRouteSort({ menuList });
|
||||
ElMessage.success("保存成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog v-model="visible" title="排序" width="60%" :before-close="fnClose">
|
||||
<div class="items">
|
||||
<vue-draggable v-model="modelValue" filter=".disabled">
|
||||
<div
|
||||
v-for="item in modelValue"
|
||||
:key="item.menuId"
|
||||
class="item" :class="[{ disabled: !item.path }]"
|
||||
>
|
||||
<div v-if="item.path">
|
||||
{{ item?.meta?.title || item.name }}
|
||||
</div>
|
||||
<div v-else style="color: red">
|
||||
空页面不需要排序
|
||||
</div>
|
||||
</div>
|
||||
</vue-draggable>
|
||||
</div>
|
||||
<template #footer>
|
||||
<el-button type="primary" @click="fnSubmit">
|
||||
保存
|
||||
</el-button>
|
||||
<el-button @click="fnClose">
|
||||
关闭
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.items {
|
||||
.item {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
border: 1px dashed #eee;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
cursor: move;
|
||||
&.disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,353 @@
|
|||
<script setup>
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { ref } from "vue";
|
||||
import {
|
||||
AppTable,
|
||||
conversionRouterMeta,
|
||||
getLabelName,
|
||||
useDataDictionary,
|
||||
useListData,
|
||||
} from "zy-vue-library";
|
||||
import { appFnGetMenuNavList } from "@/data_dictionary/index.js";
|
||||
import {
|
||||
getRouteList,
|
||||
getRouteView,
|
||||
setRouteDelete,
|
||||
} from "@/request/system_management.js";
|
||||
import AddButton from "./components/button.vue";
|
||||
import Icon from "./components/icon.vue";
|
||||
import AddMenu from "./components/menu.vue";
|
||||
import Sort from "./components/sort.vue";
|
||||
|
||||
const menusAll = ref([]);
|
||||
const { getData } = useListData(getRouteList, {
|
||||
params: { parentId: 0 },
|
||||
usePagination: false,
|
||||
key: "menuList",
|
||||
callback: (list) => {
|
||||
menusAll.value = conversionRouterMeta(list);
|
||||
},
|
||||
});
|
||||
const [navList] = useDataDictionary(appFnGetMenuNavList);
|
||||
const menuDialog = ref({
|
||||
visible: false,
|
||||
title: "",
|
||||
form: {
|
||||
parentId: "",
|
||||
parentName: "",
|
||||
title: "",
|
||||
path: "",
|
||||
redirect: "",
|
||||
component: "",
|
||||
model: "",
|
||||
perms: "",
|
||||
orderNum: "",
|
||||
activeMenu: "",
|
||||
menuId: "",
|
||||
icon: "",
|
||||
isMenu: true,
|
||||
isLogin: true,
|
||||
breadcrumb: true,
|
||||
isBreadcrumb: true,
|
||||
isSubMenu: true,
|
||||
isBack: true,
|
||||
props: 0,
|
||||
routeKey: "",
|
||||
isShowTip: false,
|
||||
},
|
||||
});
|
||||
const iconDialog = ref({
|
||||
visible: false,
|
||||
menuId: 0,
|
||||
meta: {},
|
||||
});
|
||||
const buttonDialog = ref({
|
||||
visible: false,
|
||||
title: "",
|
||||
form: {
|
||||
currentName: "",
|
||||
parentId: "",
|
||||
name: "",
|
||||
perms: "",
|
||||
},
|
||||
});
|
||||
const sortDialog = ref({
|
||||
visible: false,
|
||||
list: [],
|
||||
});
|
||||
|
||||
const fnAddRouter = async (row, type) => {
|
||||
menuDialog.value.visible = true;
|
||||
menuDialog.value.title = type;
|
||||
if (type === "编辑") {
|
||||
const resData = await getRouteView({ menuId: row.menuId });
|
||||
resData.menu.meta = JSON.parse(resData.menu.meta);
|
||||
menuDialog.value.form = {
|
||||
menuId: row.menuId,
|
||||
parentId: row.parentId,
|
||||
orderNum: row.orderNum,
|
||||
perms: row.perms,
|
||||
path: row.path,
|
||||
redirect: row.redirect,
|
||||
component: row.component,
|
||||
parentName: row.parentName || "(无)",
|
||||
title: row.meta.title,
|
||||
model: row.meta.model,
|
||||
activeMenu: row.meta.activeMenu,
|
||||
isMenu: row.meta.isMenu,
|
||||
isLogin: row.meta.isLogin,
|
||||
breadcrumb: row.meta.breadcrumb,
|
||||
isBreadcrumb: row.meta.isBreadcrumb,
|
||||
isSubMenu: row.meta.isSubMenu,
|
||||
isBack: row.meta.isBack,
|
||||
icon: row.meta.icon,
|
||||
isShowTip: row.meta.isShowTip,
|
||||
props: row.props,
|
||||
routeKey: row.routeKey,
|
||||
};
|
||||
}
|
||||
else if (type === "新增下级") {
|
||||
menuDialog.value.form.menuId = undefined;
|
||||
menuDialog.value.form.parentId = row.menuId;
|
||||
menuDialog.value.form.parentName = row.meta.title;
|
||||
menuDialog.value.form.path = row.path;
|
||||
if (row.meta.isSubMenu === false)
|
||||
menuDialog.value.form.activeMenu = row.path;
|
||||
else menuDialog.value.form.activeMenu = row.meta.activeMenu || "";
|
||||
menuDialog.value.form.orderNum = row.list?.length + 1 || 1;
|
||||
menuDialog.value.form.icon = "";
|
||||
}
|
||||
else if (type === "新增一级菜单") {
|
||||
menuDialog.value.form.menuId = undefined;
|
||||
menuDialog.value.form.parentId = 0;
|
||||
menuDialog.value.form.parentName = "(无)";
|
||||
menuDialog.value.form.orderNum = menusAll.value.length + 1;
|
||||
menuDialog.value.form.icon = "";
|
||||
}
|
||||
};
|
||||
const fnDelRouter = debounce(
|
||||
1000,
|
||||
async (row) => {
|
||||
await ElMessageBox.confirm(
|
||||
`确认删除名称为【${row?.meta?.title || row.name}】的这条数据吗?`,
|
||||
"提示",
|
||||
{ type: "warning" },
|
||||
);
|
||||
await setRouteDelete({
|
||||
menuId: row.menuId,
|
||||
});
|
||||
ElMessage.success("删除成功");
|
||||
await getData();
|
||||
},
|
||||
{ atBegin: true },
|
||||
);
|
||||
const fnAddIcon = (row) => {
|
||||
iconDialog.value.visible = true;
|
||||
iconDialog.value.menuId = row.menuId;
|
||||
iconDialog.value.meta = row.meta;
|
||||
};
|
||||
const fnAddButton = async (row, type) => {
|
||||
buttonDialog.value.visible = true;
|
||||
buttonDialog.value.title = type;
|
||||
if (type === "新增") {
|
||||
buttonDialog.value.form.currentName = row.name;
|
||||
buttonDialog.value.form.parentId = row.menuId;
|
||||
buttonDialog.value.form.menuId = undefined;
|
||||
}
|
||||
else if (type === "编辑") {
|
||||
const resData = await getRouteView({ menuId: row.menuId });
|
||||
buttonDialog.value.form = resData.menu;
|
||||
buttonDialog.value.form.currentName = resData.menu.parentName;
|
||||
}
|
||||
};
|
||||
const fnSort = (list) => {
|
||||
sortDialog.value.visible = true;
|
||||
if (!list) {
|
||||
sortDialog.value.list = menusAll.value;
|
||||
}
|
||||
else {
|
||||
sortDialog.value.list = list;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<app-table
|
||||
:data="menusAll"
|
||||
:show-pagination="false"
|
||||
row-key="menuId"
|
||||
:show-index="false"
|
||||
:tree-props="{ hasChildren: 'hasChildren', children: 'list' }"
|
||||
>
|
||||
<el-table-column label="路由名称" show-overflow-tooltip width="300">
|
||||
<template #default="{ row }">
|
||||
{{ row?.meta?.title || row.name }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="路由地址" prop="path" show-overflow-tooltip />
|
||||
<el-table-column label="文件位置" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{
|
||||
row.type === 1
|
||||
? row.component === "children"
|
||||
? "/src/components/children/index.vue"
|
||||
: `/src/views/${
|
||||
row.component.charAt(0) === "/"
|
||||
? row.component.substring(1)
|
||||
: row.component
|
||||
}.vue`
|
||||
: ""
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="路由name"
|
||||
prop="routeKey"
|
||||
show-overflow-tooltip
|
||||
width="90"
|
||||
/>
|
||||
<el-table-column label="排序" prop="orderNum" width="50" />
|
||||
<el-table-column label="所属导航栏" width="90">
|
||||
<template #default="{ row }">
|
||||
{{ getLabelName(row?.meta?.model, navList, "bianma") }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="权限标识"
|
||||
prop="perms"
|
||||
width="100"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="类型" width="65">
|
||||
<template #default="{ row }">
|
||||
<el-tag v-if="row.type === 1">
|
||||
路由
|
||||
</el-tag>
|
||||
<el-tag v-if="row.type === 2" type="danger">
|
||||
按钮
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="是否显示在菜单" width="120">
|
||||
<template #default="{ row }">
|
||||
{{
|
||||
row.type === 1
|
||||
? row.meta?.isMenu !== false && !row.meta?.activeMenu && row.path
|
||||
? "是"
|
||||
: "否"
|
||||
: ""
|
||||
}}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="320">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
v-if="
|
||||
row.type === 1
|
||||
&& row.meta?.isMenu !== false
|
||||
&& !row.meta?.activeMenu
|
||||
&& row.path
|
||||
"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnAddIcon(row)"
|
||||
>
|
||||
图标
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.type === 1"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnAddRouter(row, '编辑')"
|
||||
>
|
||||
编辑路由
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.type === 2"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnAddButton(row, '编辑')"
|
||||
>
|
||||
编辑按钮
|
||||
</el-button>
|
||||
<el-button text link type="danger" @click="fnDelRouter(row)">
|
||||
删除{{ row.type === 1 ? "路由" : "按钮" }}
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.type === 1 && row.component === 'children'"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnAddRouter(row, '新增下级')"
|
||||
>
|
||||
新增下级路由
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.type === 1 && row.component !== 'children'"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnAddButton(row, '新增')"
|
||||
>
|
||||
新增按钮
|
||||
</el-button>
|
||||
<el-button
|
||||
v-if="row.list.length > 0"
|
||||
text
|
||||
link
|
||||
type="primary"
|
||||
@click="fnSort(row.list)"
|
||||
>
|
||||
排序
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #button>
|
||||
<el-button type="primary" @click="fnAddRouter({}, '新增一级菜单')">
|
||||
新增一级路由
|
||||
</el-button>
|
||||
<el-button type="primary" @click="fnSort('')">
|
||||
排序
|
||||
</el-button>
|
||||
</template>
|
||||
</app-table>
|
||||
<add-menu
|
||||
v-if="menuDialog.visible"
|
||||
v-model:visible="menuDialog.visible"
|
||||
v-model:form="menuDialog.form"
|
||||
:title="menuDialog.title"
|
||||
:nav-list="navList"
|
||||
@get-data="getData"
|
||||
/>
|
||||
<add-button
|
||||
v-model:visible="buttonDialog.visible"
|
||||
v-model:form="buttonDialog.form"
|
||||
:title="buttonDialog.title"
|
||||
@get-data="getData"
|
||||
/>
|
||||
<icon
|
||||
v-model:visible="iconDialog.visible"
|
||||
:menu-id="iconDialog.menuId"
|
||||
:meta="iconDialog.meta"
|
||||
@get-data="getData"
|
||||
/>
|
||||
<sort
|
||||
v-model:visible="sortDialog.visible"
|
||||
v-model="sortDialog.list"
|
||||
@get-data="getData"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep {
|
||||
.el-table .el-table__cell {
|
||||
text-align: left !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,151 @@
|
|||
<script setup>
|
||||
import { ElMessage } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { nextTick, ref, watchEffect } from "vue";
|
||||
import { useForm } from "zy-vue-library";
|
||||
import {
|
||||
getRouteList,
|
||||
setRoleAdd,
|
||||
setRoleEdit,
|
||||
} from "@/request/system_management.js";
|
||||
|
||||
const props = defineProps({
|
||||
type: { type: String, required: true },
|
||||
});
|
||||
const emits = defineEmits(["getData"]);
|
||||
const visible = defineModel("visible", { type: Boolean, required: true });
|
||||
const form = defineModel("form", { type: Object, required: true });
|
||||
const { formRef, validate, reset } = useForm();
|
||||
const treeRef = ref(null);
|
||||
const menusAll = ref([]);
|
||||
const rules = {
|
||||
roleName: [
|
||||
{ required: true, message: "名称不能为空", trigger: "blur" },
|
||||
{ min: 2, max: 30, message: "长度在2到30个字符", trigger: "blur" },
|
||||
],
|
||||
};
|
||||
const fnConversion = (menuList) => {
|
||||
for (let i = 0; i < menuList.length; i++) {
|
||||
menuList[i].meta = JSON.parse(menuList[i].meta);
|
||||
if (menuList[i].type === 1) {
|
||||
menuList[i].name = `${menuList[i].name || "首页"}(页面)`;
|
||||
}
|
||||
if (menuList[i].type === 2) {
|
||||
menuList[i].name = `${menuList[i].name}(按钮)`;
|
||||
}
|
||||
if (menuList[i].list.length > 0) {
|
||||
fnConversion(menuList[i].list);
|
||||
}
|
||||
}
|
||||
return menuList;
|
||||
};
|
||||
const fnGetData = async () => {
|
||||
const resData = await getRouteList({ parentId: 0 });
|
||||
menusAll.value = fnConversion(resData.menuList);
|
||||
};
|
||||
const fnClose = () => {
|
||||
reset();
|
||||
visible.value = false;
|
||||
treeRef.value?.setCheckedKeys([]);
|
||||
};
|
||||
const fnGetChecked = (menuId, menusAll, checkedKeys) => {
|
||||
for (let i = 0; i < menusAll.length; i++) {
|
||||
if (menuId === menusAll[i].menuId) {
|
||||
if (menusAll[i].list.length === 0) {
|
||||
checkedKeys.push(menusAll[i].menuId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (menusAll[i].list.length > 0) {
|
||||
fnGetChecked(menuId, menusAll[i].list, checkedKeys);
|
||||
}
|
||||
}
|
||||
}
|
||||
return checkedKeys;
|
||||
};
|
||||
watchEffect(async () => {
|
||||
if (visible.value) {
|
||||
await fnGetData();
|
||||
await nextTick();
|
||||
let checkedKeys = [];
|
||||
form.value.menuIdList.forEach((item) => {
|
||||
checkedKeys = fnGetChecked(item, menusAll.value, checkedKeys);
|
||||
});
|
||||
treeRef.value?.setCheckedKeys(checkedKeys);
|
||||
}
|
||||
});
|
||||
const fnSubmit = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
await validate();
|
||||
const checkedKeys = treeRef.value?.getCheckedKeys() || [];
|
||||
const halfCheckedKeys = treeRef.value?.getHalfCheckedKeys() || [];
|
||||
if (props.type === "add") {
|
||||
await setRoleAdd({
|
||||
...form.value,
|
||||
menuIdList: [...checkedKeys, ...halfCheckedKeys],
|
||||
roleId: undefined,
|
||||
});
|
||||
}
|
||||
if (props.type === "edit") {
|
||||
await setRoleEdit({
|
||||
...form.value,
|
||||
menuIdList: [...checkedKeys, ...halfCheckedKeys],
|
||||
});
|
||||
}
|
||||
ElMessage.success("操作成功");
|
||||
fnClose();
|
||||
emits("getData");
|
||||
},
|
||||
{ atBegin: true },
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-dialog
|
||||
v-model="visible"
|
||||
:title="type === 'edit' ? '修改' : '新增'"
|
||||
:before-close="fnClose"
|
||||
>
|
||||
<el-form ref="formRef" :rules="rules" :model="form" label-width="110px">
|
||||
<el-form-item label="名称" prop="roleName">
|
||||
<el-input v-model="form.roleName" placeholder="请输入名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="备注" prop="remark">
|
||||
<el-input
|
||||
v-model="form.remark"
|
||||
type="textarea"
|
||||
:autosize="{ minRows: 1 }"
|
||||
placeholder="请输入备注"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="form.parentId !== '0'" label="菜单" prop="menuIdList">
|
||||
<el-tree
|
||||
ref="treeRef"
|
||||
node-key="menuId"
|
||||
:data="menusAll"
|
||||
:props="{
|
||||
label: 'name',
|
||||
children: 'list',
|
||||
}"
|
||||
show-checkbox
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="fnClose">
|
||||
关闭
|
||||
</el-button>
|
||||
<el-button type="primary" @click="fnSubmit">
|
||||
确定
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-tree) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,125 @@
|
|||
<script setup>
|
||||
import { ElMessage, ElMessageBox } from "element-plus";
|
||||
import { debounce } from "throttle-debounce";
|
||||
import { nextTick, ref } from "vue";
|
||||
import { AppSearch, AppTable, useListData } from "zy-vue-library";
|
||||
import {
|
||||
getRoleList,
|
||||
getRoleView,
|
||||
setRoleDelete,
|
||||
} from "@/request/system_management.js";
|
||||
import Add from "./components/add.vue";
|
||||
|
||||
const { list, pagination, searchForm, getData, resetPagination, tableRef }
|
||||
= useListData(getRoleList);
|
||||
const options = [{ key: "roleName", label: "名称" }];
|
||||
const addOrEditDialog = ref({
|
||||
visible: false,
|
||||
type: "",
|
||||
form: {
|
||||
roleName: "",
|
||||
remark: "",
|
||||
corpRoleType: "",
|
||||
menuIdList: [],
|
||||
},
|
||||
});
|
||||
const fnDelete = debounce(
|
||||
1000,
|
||||
async (roleIds) => {
|
||||
await ElMessageBox.confirm(`确定要删除吗?`, {
|
||||
type: "warning",
|
||||
});
|
||||
await setRoleDelete({ roleIds });
|
||||
ElMessage.success("删除成功");
|
||||
await resetPagination();
|
||||
},
|
||||
{ atBegin: true },
|
||||
);
|
||||
const fnDeleteMultiple = debounce(
|
||||
1000,
|
||||
async () => {
|
||||
const selectionData = tableRef.value.getSelectionRows();
|
||||
if (selectionData.length === 0) {
|
||||
ElMessage.warning("请选择要删除的数据");
|
||||
return;
|
||||
}
|
||||
await ElMessageBox.confirm(`确定要删除吗?`, {
|
||||
type: "warning",
|
||||
});
|
||||
const roleIds = selectionData.map(item => item.roleId);
|
||||
await setRoleDelete({ roleIds });
|
||||
ElMessage.success("删除成功");
|
||||
await resetPagination();
|
||||
},
|
||||
{ atBegin: true },
|
||||
);
|
||||
const fnAddOrEdit = async (roleId, type) => {
|
||||
addOrEditDialog.value.visible = true;
|
||||
addOrEditDialog.value.type = type;
|
||||
await nextTick();
|
||||
if (type === "edit") {
|
||||
const resData = await getRoleView({ roleId });
|
||||
addOrEditDialog.value.form = resData.role;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<app-search
|
||||
v-model="searchForm"
|
||||
:options
|
||||
label-width="50px"
|
||||
@submit="resetPagination"
|
||||
/>
|
||||
<app-table
|
||||
ref="tableRef"
|
||||
v-model:pagination="pagination"
|
||||
:data="list"
|
||||
row-key="roleId"
|
||||
show-selection
|
||||
@get-data="getData"
|
||||
>
|
||||
<el-table-column prop="roleName" label="名称" />
|
||||
<el-table-column prop="remark" label="备注" />
|
||||
<el-table-column label="操作" width="120">
|
||||
<template #default="{ row }">
|
||||
<el-button
|
||||
type="primary"
|
||||
text
|
||||
link
|
||||
@click="fnAddOrEdit(row.roleId, 'edit')"
|
||||
>
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
type="danger"
|
||||
text
|
||||
link
|
||||
:disabled="!row.corpinfoId"
|
||||
@click="fnDelete([row.roleId])"
|
||||
>
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<template #button>
|
||||
<el-button type="primary" @click="fnAddOrEdit('', 'add')">
|
||||
新增
|
||||
</el-button>
|
||||
<el-button type="danger" @click="fnDeleteMultiple">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</app-table>
|
||||
<add
|
||||
v-model:visible="addOrEditDialog.visible"
|
||||
v-model:form="addOrEditDialog.form"
|
||||
:type="addOrEditDialog.type"
|
||||
:corp-menu-type="addOrEditDialog.form.corpRoleType"
|
||||
@get-data="resetPagination"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped></style>
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
import vue from "@vitejs/plugin-vue";
|
||||
import AutoImport from "unplugin-auto-import/vite";
|
||||
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
|
||||
import Components from "unplugin-vue-components/vite";
|
||||
import { defineConfig, loadEnv } from "vite";
|
||||
// import EnhanceLog from "vite-plugin-enhance-log";
|
||||
import { envParse } from "vite-plugin-env-parse";
|
||||
import eslintPlugin from "vite-plugin-eslint";
|
||||
import removeConsole from "vite-plugin-remove-console";
|
||||
|
||||
export default ({ mode }) => {
|
||||
return defineConfig({
|
||||
base: loadEnv(mode, process.cwd()).VITE_BASE,
|
||||
plugins: [
|
||||
vue(),
|
||||
envParse(),
|
||||
eslintPlugin({
|
||||
cache: false,
|
||||
include: ["./src/**/*.js", "./src/**/*.vue", "./src/**/*.ts"],
|
||||
exclude: ["./node_modules/**", "./zy-vue-library/**"],
|
||||
}),
|
||||
removeConsole({
|
||||
includes: [
|
||||
"assert",
|
||||
"clear",
|
||||
"count",
|
||||
"countReset",
|
||||
"createTask",
|
||||
"debug",
|
||||
"dir",
|
||||
"dirxml",
|
||||
"error",
|
||||
"group",
|
||||
"groupCollapsed",
|
||||
"groupEnd",
|
||||
"info",
|
||||
"log",
|
||||
"profile",
|
||||
"profileEnd",
|
||||
"table",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"timeLog",
|
||||
"timeStamp",
|
||||
"trace",
|
||||
"warn",
|
||||
],
|
||||
}),
|
||||
AutoImport({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
Components({
|
||||
resolvers: [ElementPlusResolver()],
|
||||
}),
|
||||
// EnhanceLog({
|
||||
// splitBy: "🐶",
|
||||
// preTip: "🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀",
|
||||
// }),
|
||||
],
|
||||
optimizeDeps: {
|
||||
include: ["qrcode", "@vue-office/pdf", "crypto-js"],
|
||||
},
|
||||
server: {
|
||||
host: true, // 本机的局域网IP,不然其他人无法通过IP访问到,0.0.0.0或true会自动获取本机的IP
|
||||
port: 18471, // 端口号
|
||||
open: true, // 是否自动打开浏览器
|
||||
proxy: {
|
||||
[loadEnv(mode, process.cwd()).VITE_PROXY]: {
|
||||
target: loadEnv(mode, process.cwd()).VITE_BASE_URL,
|
||||
changeOrigin: true,
|
||||
ws: true,
|
||||
rewrite: path => path.replace(/^\/api/, ""),
|
||||
},
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": "/src", // 别名,@代表src目录
|
||||
},
|
||||
extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue"], // 引入文件时哪些后缀名可以不写
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
// 打包多个入口文件
|
||||
// input: {
|
||||
// admin: path.resolve(__dirname, "index.html"),
|
||||
// 其它入口文件路径需要为src/views/*/index.html
|
||||
// },
|
||||
output: {
|
||||
chunkFileNames: "static/js/[name]-[hash].js",
|
||||
entryFileNames: "static/js/[name]-[hash].js",
|
||||
assetFileNames: "static/[ext]/name-[hash].[ext]",
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/web-types",
|
||||
"framework": "vue",
|
||||
"name": "name written in package.json",
|
||||
"version": "version written in package.json",
|
||||
"contributions": {
|
||||
"html": {
|
||||
"types-syntax": "typescript",
|
||||
"attributes": [
|
||||
{
|
||||
"name": "v-permission"
|
||||
},
|
||||
{
|
||||
"name": "v-viewer"
|
||||
},
|
||||
{
|
||||
"name": "v-print"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue