Commit 3b5af969 authored by 苏堉's avatar 苏堉 🖐

Feat: init

parents
# svi-cli smartweb-vue3 Template
> 一个基于vite搭建的vue + typescript + smartweb应用模板,提供给svi-cli脚手架获取.
> 提供内部用Vue3 + smartweb开发页面的初始模板
> 主要包含 `vue`, `smartweb`, `@smart/eslint-config-typescript` 智能的规范
## Usage / 用法
``` bash
$ npm install -g svi-cli
$ svi init smartweb-vue3 my-project
$ cd my-project
$ yarn
$ yarn dev
```
\ No newline at end of file
module.exports = {
renderFiles: ['package.json', 'README.md'],
prompts: {
// 收集用户的自定义数据
name: {
type: 'string',
required: true,
message: 'Project name',
},
description: {
type: 'string',
required: false,
message: 'Project description',
default: 'a smartweb api vue3 project',
},
author: {
type: 'string',
message: 'Author',
},
},
// filters:根据条件过滤文件
// completeMessage: 模板渲染完成后给予的提示信息,支持handlebars的mustaches表达式
// complete<Function>: 模板渲染完成后的回调函数, 优先于 completeMessage
// helpers<Object>: 自定义的 Handlebars 辅助函数
};
{
"name": "smartweb-vue3",
"version": "0.0.1",
"description": "a svi CLI smart3d vue3 app template.",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"template"
],
"author": "South Smart",
"license": "MIT"
}
# http://editorconfig.org
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
VITE_APP_TITLE = 系统
VITE_PORT = 3888
VITE_IS_3D = true
VITE_APP_MOCK = true
VITE_PROXY=[]
# 公司内网
# VITE_APP_BASE_URL=http://172.16.11.137:3002/api
# 公司内网(测试用)
VITE_APP_BASE_URL=http://172.16.11.137:33333
# 公司外网
# VITE_APP_BASE_URL=http://www.southsmart.com/smartweb-v2/api
VITE_PROXY = [["/api","http://172.16.11.137:3332/api"]]
# 公司内网
# VITE_APP_BASE_URL=http://172.16.11.137:3002/api
# 公司内网(测试用)
VITE_APP_BASE_URL=http://172.16.11.137:33333
# 公司外网
# VITE_APP_BASE_URL=http://www.southsmart.com/smartweb-v2/api
# 慧珍本地
# VITE_APP_BASE_URL=http://172.16.123.61:3333
# 毛总本地
# VITE_APP_BASE_URL=http://172.16.123.80:3333
VITE_PROXY=[["/api","http://localhost:3001"]]
VITE_APP_MOCK=true
# 公司内网
VITE_APP_BASE_URL=http://172.16.11.137:3002/api
# 公司内网(测试用)
# VITE_APP_BASE_URL=http://172.16.11.137:3332
# 公司外网
# VITE_APP_BASE_URL=http://www.southsmart.com/smartweb-v2/api
*.d.ts
vite.config.ts
public/
assets/
build/
*.md
*.html
const { defineConfig } = require('eslint-define-config');
module.exports = defineConfig({
root: true,
env: {
node: true,
browser: true,
},
parser: 'vue-eslint-parser',
parserOptions: {
parser: require.resolve('@typescript-eslint/parser'),
extraFileExtensions: ['.vue'],
ecmaFeatures: {
jsx: true,
tsx: true,
},
},
extends: [
'plugin:@typescript-eslint/eslint-recommended',
'plugin:vue/vue3-recommended',
'prettier',
'@vue/prettier',
'@smart/typescript',
],
rules: {
'no-plusplus': 'off',
'@typescript-eslint/no-unused-vars': [2, { 'args': 'none' }],
'no-param-reassign': 'off',
'semi': ['warn', 'always'],
'indent': [
'warn',
2,
{
'SwitchCase': 1,
'ignoredNodes': ['VariableDeclaration[declarations.length=0]']
}
],
'import/no-relative-packages': 'off',
'arrow-parens': ['error', 'always', { 'requireForBlockBody': true }],
'@typescript-eslint/consistent-type-imports': [
'error',
{ disallowTypeAnnotations: false },
],
// vue
'vue/html-self-closing': [
'error',
{
html: {
void: 'any',
normal: 'never',
component: 'always',
},
svg: 'always',
math: 'always',
},
],
'vue/max-attributes-per-line': [
'error',
{
singleline: {
max: 4,
allowFirstLine: true,
},
multiline: {
max: 1,
allowFirstLine: true,
},
},
],
'vue/require-default-prop': 'off',
'import/no-extraneous-dependencies': 'off',
'camelcase': 'off',
'@typescript-eslint/camelcase': 0,
},
globals: {
defineProps: 'readonly',
defineEmits: 'readonly',
defineExpose: 'readonly',
withDefaults: 'readonly',
},
});
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install commitlint --edit "$1"
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx --no-install lint-staged
npx --no-install pretty-quick --staged
registry = http://npm.southsmart.com
# Ignore everything
*
# Unignore directories (to all depths) and unignore code and style files in these directories
!src/**/
!**/*.js
!**/*.jsx
!**/*.css
!**/*.scss
!**/*.html
!**/*.vue
!**/*.md
!**/*.ts
!**/*.tsx
# some souces directories
src/assets
#
CHANGELOG.md
vue.config.js
babel.config.js
commitlint.config.js
vite.config.js
.eslintrc.js
module.exports = {
tabWidth: 2,
trailingComma: 'all',
semi: true,
singleQuote: true,
printWidth: 100,
overrides: [
{
files: '.prettierrc',
options: {
parser: 'json',
},
},
],
};
{
"extends": [
"stylelint-config-standard",
"stylelint-config-standard-scss",
"stylelint-config-recommended-vue",
"stylelint-config-recess-order",
"stylelint-config-prettier"
],
"rules": {
"font-family-no-missing-generic-family-keyword": null,
"selector-class-pattern": null,
"string-quotes": "single",
"color-hex-length": [
"long",
"short"
],
"at-rule-no-unknown": null
}
}
{
"recommendations": [
"vue.volar",
"vue.vscode-typescript-vue-plugin",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode",
"stylelint.vscode-stylelint"
],
}
{
"typescript.tsdk": "node_modules/typescript/lib",
"npm.packageManager": "yarn",
"files.exclude": {
"**/.git": true,
"**/.DS_Store": true,
"**/Thumbs.db": true,
".idea": true,
".metadata": true,
".settings": true,
".externalToolBuilders": true,
".project": true,
"launches": true
},
"search.exclude": {
"dist": true,
"coverage": true,
"node_modules": true,
"lib": true
},
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true,
"source.fixAll.stylelint": true
},
"eslint.probe": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue",
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact",
"vue"
],
"stylelint.validate": [
"css",
"less",
"postcss",
"scss",
"vue",
"sass"
],
}
This diff is collapsed.
## vX.X.X
### :boom: 重要改动
### :sparkles: 功能
### :bug: Bug 修复
- **系统管理/用户管理:**
- **系统管理/资源管理:**
- **系统管理/角色管理:**
- **系统管理/岗位管理:**
- **配置管理/字典管理:**
- **配置管理/行政地区管理:**
- **配置管理/组织架构管理:**
- **配置管理/系统配置:**
- **配置管理/安全配置:**
- **审计管理/在线用户:**
- **审计管理/登录日志:**
- **审计管理/操作日志:**
- **审计管理/系统日志:**
- **审计管理/日志统计:**
- **其他:**
---
# SmartWeb-vue3 [![nodejs](https://img.shields.io/badge/node-v16.x-brightgreen)](https://nodejs.org/en/) [![yarn](https://img.shields.io/badge/yarn-v1.22.18-blue)](https://yarnpkg.com/getting-started/install#nodejs-1610)
## 开始使用
```bash
# 安装依赖 (.npmrc中可切换依赖库资源地址)
yarn
# 启动服务
yarn dev
```
## 代码校验
```bash
# 代码校验
yarn lint
```
## 自定义主题
在以下文件修改主题变量
- 修改 Element-Plus 主题: `src\assets\styles\element-plus.scss`
- 修改本地主题: `src\config\theme.ts`
## 安装 VSCode 插件
- [Vue Language Features (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.volar)
- [TypeScript Vue Plugin (Volar)](https://marketplace.visualstudio.com/items?itemName=Vue.vscode-typescript-vue-plugin)
- [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint)
- [Prettier - Code formatter](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
- [Stylelint](https://marketplace.visualstudio.com/items?itemName=stylelint.vscode-stylelint)
## 部署
部署可以使用 `deploy.sh` 脚本,`windows` 下需使用 `git bash`终端执行
注:登录服务器需要上传自己的 ssh_key
```
./deploy.sh
```
## 作者
South Smart EF 团队。
// 转换全部环境变量属性
export function wrapperEnv(envConf: any) {
const ret: any = {};
const keys = Object.keys(envConf);
for (let i = 0; i < keys.length; i += 1) {
const envName = keys[i];
let realName = envConf[keys[i]].replace(/\\n/g, '\n');
realName = realName === 'true' ? true : realName === 'false' ? false : realName;
if (envName === 'VITE_PORT') {
realName = Number(realName);
}
if (envName === 'VITE_PROXY' && realName) {
try {
realName = JSON.parse(realName.replace(/'/g, '"'));
} catch (error) {
realName = '';
}
}
ret[envName] = realName;
if (typeof realName === 'string') {
process.env[envName] = realName;
} else if (typeof realName === 'object') {
process.env[envName] = JSON.stringify(realName);
}
}
return ret;
}
// 代理配置转换
export function createProxy(list: [string, string][]) {
const obj: any = {};
list.forEach(v => {
obj[v[0]] = {
target: v[1],
changeOrigin: true,
rewrite: (path:any) => path.replace(new RegExp(`^${v[0]}`), ''),
...(/^https:\/\//.test(v[1]) ? { secure: false } : {})
};
});
return obj;
}
import { Plugin } from 'vite';
import * as fse from 'fs-extra';
import { posix as path } from 'path';
import * as glob from 'glob';
import { Chalk } from 'chalk';
import * as originChalk from 'chalk';
const chalk = originChalk as unknown as Chalk;
export interface TargetObject {
isDir?: boolean
path: string
}
export interface CopyOptions {
src: string[] | string
target: string[] | string | TargetObject | TargetObject[]
}
function isTargetObject(
target: string[] | string | TargetObject | TargetObject[]
): target is TargetObject {
return typeof target === 'object';
}
function copyFile(src: string, target: string, isDir: boolean) {
const targetFile = isDir ? path.join(target, path.basename(src)) : target;
try {
if (isDir) {
fse.copySync(src, targetFile);
} else {
fse.copySync(src, target);
}
} catch (error) {
console.log(chalk.red(`Copy failed: ${src} > ${targetFile}`));
console.error(error);
}
}
/**
*
* @param options {from: to}
* @returns: Plugin
*/
function vitePluginCopy(
options: CopyOptions[],
globOptions?: glob.IOptions
): Plugin {
let copyed = false;
return {
name: 'vite-plugin-copy',
// apply: 'build',
buildStart() {
if (copyed) return;
const cacheMap: Record<string, string[]> = {};
options.forEach(({ src, target }) => {
const srcArr = Array.isArray(src) ? src : [src];
const targetArr = Array.isArray(target) ? target : [target];
srcArr.forEach(s => {
let sourceFiles = cacheMap[s];
if (!sourceFiles) {
sourceFiles = glob.sync(s, globOptions);
cacheMap[s] = sourceFiles;
}
sourceFiles.forEach(sourceFile => {
targetArr.forEach(t => {
if (isTargetObject(t)) {
const { isDir, path: pathname } = t;
copyFile(sourceFile, pathname, isDir || !path.extname(pathname));
} else {
copyFile(sourceFile, t, !path.extname(t));
}
});
});
});
});
copyed = true;
},
closeBundle() {
// const cacheMap: Record<string, string[]> = {};
// options.forEach(({ src, target }) => {
// const srcArr = Array.isArray(src) ? src : [src];
// const targetArr = Array.isArray(target) ? target : [target];
// srcArr.forEach(s => {
// let sourceFiles = cacheMap[s];
// if (!sourceFiles) {
// sourceFiles = glob.sync(s, globOptions);
// cacheMap[s] = sourceFiles;
// }
// sourceFiles.forEach(sourceFile => {
// targetArr.forEach(t => {
// if (isTargetObject(t)) {
// const { isDir, path: pathname } = t;
// copyFile(sourceFile, pathname, isDir || !path.extname(pathname));
// } else {
// copyFile(sourceFile, t, !path.extname(t));
// }
// });
// });
// });
// });
}
};
}
export { vitePluginCopy };
export default vitePluginCopy;
'use strict';
const { writeFileSync, existsSync, mkdirSync, emptyDirSync } = require('fs-extra');
const { join } = require('path');
const { isArray } = Array;
function createCJSExportDeclaration(external, type='esm') {
// return type === 'esm'
// ? `export default window['${external}']`
// : `module.exports = ${external};`;
return `export default globalThis['${external}'];`;
}
module.exports = function (opts = {}) {
let externals;
let externalLibs;
let shouldSkip = false;
const externalCacheDir = join(process.cwd(), 'node_modules', '.vite_external');
return {
name: 'external',
config(config, { mode }) {
let tmp;
externals = Object.assign({}, opts.externals, (tmp = opts[mode]) && tmp.externals);
externalLibs = Object.keys(externals);
shouldSkip = !externalLibs.length;
if (shouldSkip) {
return;
}
if (!existsSync) {
mkdirSync(externalCacheDir);
} else {
emptyDirSync(externalCacheDir);
}
let { resolve } = config;
if (!resolve) {
resolve = {};
config.resolve = resolve;
}
let { alias } = resolve;
if (!alias || typeof alias !== 'object') {
alias = [];
resolve.alias = alias;
}
// 处理 alias 为 object 的情况
if (!isArray(alias)) {
alias = Object.entries(alias).map(([key, value]) => {
return { find: key, replacement: value };
});
}
for (const libName of externalLibs) {
const libPath = join(externalCacheDir, `${libName.replace(/\//g, '_')}.js`);
writeFileSync(libPath, createCJSExportDeclaration(externals[libName], opts.type));
alias.push({ find: new RegExp(`^${libName}$`), replacement: libPath });
}
},
options(opts) {
if (shouldSkip) {
return;
}
let { output, external } = opts;
if (!output) {
output = {};
opts.output = output;
}
let { globals } = output;
if (!globals) {
globals = {};
output.globals = globals;
}
Object.assign(globals, externals);
if (!external) {
external = [];
opts.external = external;
}
if (typeof external === 'function') {
return;
}
external.push(...externalLibs);
},
};
};
import vitePluginCopy from '../../vite-plugin-copy';
import { resolve } from 'path';
const appRoot = resolve(__dirname, '../../../')
export function copyPlugin(VITE_IS_3D: boolean) {
if (VITE_IS_3D) {
return vitePluginCopy([
{
src: resolve(appRoot, 'node_modules/@smart/cesium/Build/Cesium/*'),
target: [resolve(appRoot, 'public/libs/Cesium')],
},
{
src: resolve(appRoot, 'node_modules/smart3d/dist/*'),
target: [resolve(appRoot, 'public/libs')],
},
])
} else {
return [];
}
}
import type { Plugin } from 'vite'
import vueJsx from '@vitejs/plugin-vue-jsx'
import vueSetupExtend from 'vite-plugin-vue-setup-extend'
import vue from '@vitejs/plugin-vue'
import { viteMockServe } from 'vite-plugin-mock'
export function createVitePlugins(viteEnv, isBuild: boolean, mode) {
const {
VITE_APP_MOCK
} = viteEnv
const vitePlugins: (Plugin | Plugin[])[] = [
vue(),
vueJsx(),
vueSetupExtend(),
viteMockServe({
mockPath: 'mock',
localEnabled: !isBuild,
prodEnabled: isBuild && VITE_APP_MOCK as boolean,
// 控制关闭mock的时候不让mock打包到最终代码内
injectCode: `
import { setupProdMockServer } from '/mock/mockProdServer';
setupProdMockServer();
`
})
]
return vitePlugins
}
'use strict';
if (process.env.npm_execpath.indexOf('yarn') === -1) {
throw new Error(
'Please use Yarn instead of NPM to install dependencies. See: https://yarnpkg.com/en/docs/cli/install'
);
}
module.exports = {
extends: ['@smart/commitlint-config'],
};
#!/bin/bash
DEPLOY_URL="172.16.11.137"
echo "【测试环境打包】"
echo -e "\033[32m -O- 部署开始 -O- \033[0m"
#####
echo -e "\033[33m >>>>> 开始打包 >>>>> \033[0m"
yarn run build:dev
echo -e "\033[33m <<<<< 打包完毕 <<<<< \033[0m"
#####
echo -e "\033[33m >>>>> 删除原文件 >>>>> \033[0m"
ssh smart@$DEPLOY_URL "
cd /usr/local/nginx/html;
rm -r smartweb-v2-dev
"
#####
echo -e "\033[33m >>>>> 开始上传文件 >>>>> \033[0m"
scp -r smartweb-v2-dev smart@$DEPLOY_URL:/usr/local/nginx/html
echo -e "\033[33m <<<<< 上传文件完毕 <<<<< \033[0m"
echo -e "\033[32m 部署成功 请打开 http://172.16.11.137/smartweb-v2-dev 查看\033[0m"
#!/bin/bash
DEPLOY_URL="172.16.11.137"
echo " 🚀🚀🚀"
echo -e "\033[32m -O- 部署开始 -O- \033[0m"
#####
echo -e "\033[33m >>>>> 开始打包 >>>>> \033[0m"
yarn run build
echo -e "\033[33m <<<<< 打包完毕 <<<<< \033[0m"
#####
date=`date '+%Y-%m-%d-%H%M%S'`
echo -e "\033[33m >>>>> 开始备份 >>>>> \033[0m"
ssh smart@$DEPLOY_URL "
cd /usr/local/nginx/html;
mv smartweb-v2 smartweb-v2_$date
"
echo -e "\033[33m <<<<< 备份完毕 <<<<< \033[0m"
#####
echo -e "\033[33m >>>>> 开始上传文件 >>>>> \033[0m"
scp -r smartweb-v2 smart@$DEPLOY_URL:/usr/local/nginx/html
echo -e "\033[33m <<<<< 上传文件完毕 <<<<< \033[0m"
echo " 🚀🚀🚀"
echo -e "\033[32m 部署成功 请打开 http://172.16.11.137/smartweb-v2 查看\033[0m"
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
import type { MockMethod } from 'vite-plugin-mock';
import { routeList } from './menuList';
export interface IReq {
body: any;
query: any,
headers: any;
}
const responseData = (code: number, msg: string, data: any) => ({
code: code,
msg: msg,
data: data
});
export default [
{
url: '/api/User/login',
method: 'post',
timeout: 300,
response: (req: IReq) => {
const { username} = req.body;
return responseData(200, '登陆成功', `token_${username}_token`);
}
},
{
url: '/api/User/getRoute',
method: 'get',
timeout: 300,
response: () => responseData(200, '返回成功', routeList)
},
{
url: '/api/getDictionary',
method: 'get',
timeout: 300,
response: () => responseData(200, '返回成功', [1, 2, 3])
},
{
url: '/api/getDueryData',
method: 'get',
timeout: 300,
response: () => responseData(200, '返回成功', { total: 3, status: 0, records: [
{
date: '2016-05-03',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-02',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-04',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
},
{
date: '2016-05-01',
name: 'Tom',
address: 'No. 189, Grove St, Los Angeles'
}
] }
)
}
] as MockMethod[];
import type { MenuList } from '../src/types/store/layout';
export const routeList: Array<MenuList> = [
{
id: 2,
pid: 0,
name: 'Demo',
path: '/Demo',
component: 'mapLayout',
redirect: '/Demo/DemoList',
meta: { title: '示例管理', icon: 'grid' }
},
{
id: 20,
pid: 2,
name: 'DemoList',
path: '/Demo/DemoList',
component: 'DemoList',
meta: { title: '示例详情', icon: '' }
},
{
id: 21,
pid: 2,
name: 'DemoList1',
path: '/Demo/DemoList1',
component: 'DemoList1',
meta: { title: '示例详情1', icon: '' }
}
];
import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer';
import testModule from './index';
export function setupProdMockServer():void {
createProdMockServer([...testModule]);
}
{
"name": "{{ name }}",
"version": "0.0.0",
"description": "{{ description }}",
"author": "{{ author }}",
"engines": {
"node": ">= 16"
},
"scripts": {
"dev": "vite",
"build": "vite build",
"build:dev": "vite build --mode dev",
"preview": "vite preview",
"serve": "vite preview",
"lint": "yarn lint:es && yarn lint:style && vue-tsc --noEmit",
"lint:es": "eslint src/**/*.{js,jsx,ts,tsx,vue} --max-warnings 0 --fix",
"lint:style": "stylelint src/**/*.{css,scss,vue} --fix",
"lint:vue-tsc": "vue-tsc --noEmit",
"deploy": "./deploy.sh",
"deploy:dev": "./deploy-dev.sh",
"format": "prettier --write .",
"prepare": "husky install",
"preinstall": "node ./build/yarn/check-yarn.js",
"postinstall": "patch-package"
},
"dependencies": {
"@element-plus/icons-vue": "^1.1.4",
"bignumber.js": "^9.1.0",
"buffer": "^6.0.3",
"dayjs": "^1.10.8",
"echarts": "^5.3.1",
"element-plus": "2.2.x",
"jsencrypt": "^3.2.1",
"lodash-es": "^4.17.21",
"nprogress": "^0.2.0",
"qs": "^6.10.2",
"sm-crypto": "^0.3.6",
"uuid": "^9.0.0",
"vue": "^3.2.35",
"vue-router": "^4.0.15"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@commitlint/cli": "^15.0.0",
"@smart/commitlint-config": "^0.1.2",
"@smart/eslint-config-typescript": "^1.1.0",
"@types/babel__core": "^7.1.16",
"@types/ejs": "^3.1.0",
"@types/html-minifier-terser": "^6.1.0",
"@types/lodash": "^4.14.176",
"@types/lodash-es": "*",
"@types/qs": "^6.9.7",
"@types/sm-crypto": "^0.3.0",
"@typescript-eslint/eslint-plugin": "^4.31.1",
"@typescript-eslint/parser": "^4.31.1",
"@vitejs/plugin-vue": "^1.6.1",
"@vitejs/plugin-vue-jsx": "^1.3.8",
"@vue/compiler-sfc": "^3.2.6",
"@vue/eslint-config-prettier": "^6.0.0",
"@vue/eslint-config-typescript": "^7.0.0",
"axios": "^0.24.0",
"eslint": "^7.32.0",
"eslint-define-config": "^1.1.2",
"eslint-plugin-prettier": "^3.1.0",
"eslint-plugin-vue": "^7.17.0",
"husky": "^7.0.4",
"lint-staged": "11.2.6",
"mockjs": "^1.1.0",
"patch-package": "^6.4.7",
"pinia": "^2.0.14",
"postcss": "^8.4.14",
"postcss-html": "^1.4.1",
"prettier": "^2.4.1",
"pretty-quick": "^3.1.2",
"rollup": "^2.74.1",
"rollup-plugin-copy": "^3.4.0",
"rollup-plugin-external-globals": "^0.6.1",
"sass": "^1.44.0",
"stylelint": "^14.8.3",
"stylelint-config-prettier": "^9.0.3",
"stylelint-config-recess-order": "^3.0.0",
"stylelint-config-recommended-vue": "^1.4.0",
"stylelint-config-standard": "^25.0.0",
"stylelint-config-standard-scss": "^3.0.0",
"typescript": "4.3.2",
"vite": "^2.5.4",
"vite-plugin-externals": "^0.3.0",
"vite-plugin-html": "^2.1.1",
"vite-plugin-mock": "^2.9.6",
"vite-plugin-vue-setup-extend": "^0.1.0",
"vue-tsc": "^1.0.3"
},
"lint-staged": {
"*.{js,jsx,ts,tsx,vue}": "eslint --fix",
"*.{css,scss,vue}": "stylelint --fix"
},
"browserslist": [
"> 1%",
"not ie 11",
"not op_mini all"
]
}
diff --git a/node_modules/jsencrypt/lib/lib/jsbn/rsa.js b/node_modules/jsencrypt/lib/lib/jsbn/rsa.js
index b47d793..0f8c29d 100644
--- a/node_modules/jsencrypt/lib/lib/jsbn/rsa.js
+++ b/node_modules/jsencrypt/lib/lib/jsbn/rsa.js
@@ -206,7 +206,8 @@ var RSAKey = /** @class */ (function () {
// "ctext" is an even-length hex string and the output is a plain string.
RSAKey.prototype.decrypt = function (ctext) {
var c = parseBigInt(ctext, 16);
- var m = this.doPrivate(c);
+ // var m = this.doPrivate(c);
+ var m = this.doPublic(c);
if (m == null) {
return null;
}
@@ -312,9 +313,9 @@ function pkcs1unpad2(d, n) {
while (i < b.length && b[i] == 0) {
++i;
}
- if (b.length - i != n - 1 || b[i] != 2) {
- return null;
- }
+ // if (b.length - i != n - 1 || b[i] != 2) {
+ // return null;
+ // }
++i;
while (b[i] != 0) {
if (++i >= b.length) {
This diff is collapsed.
<template>
<ElConfigProvider :locale="locale">
<router-view />
</ElConfigProvider>
</template>
<script setup lang="ts">
import { onMounted } from 'vue';
import locale from 'element-plus/lib/locale/lang/zh-cn';
import { ElConfigProvider } from 'element-plus';
import { saveSystemLog } from '@/api/systemManagement/loginLog';
onMounted(() => {
if (process.env.NODE_ENV !== 'development') {
window.addEventListener(
'error',
(e) => {
if ((e.target as Element).nodeName === 'IMG') {
const params = {
browser: window.navigator.userAgent,
target: window.location.href,
source: 'FRONT_END',
content: '图片加载失败',
};
saveSystemLog(params);
}
},
true,
);
}
});
</script>
/* eslint-disable camelcase */
import type { AxiosResponse } from 'axios';
import qs from 'qs';
import request from '@/utils/request';
export interface loginData {
grant_type: string;
username: string;
password: string;
ver_key?: string | undefined;
ver_code?: string;
}
/**
* @description 获取菜单数组
*/
export function getRouterList(): Promise<AxiosResponse> {
return request({
url: '/system/menu/navigatePerMenuList',
method: 'get',
});
}
/**
* @description 获取菜单树
*/
export function getRouterTree(): Promise<AxiosResponse> {
return request({
url: '/system/menu/navigatePerMenu',
method: 'get',
});
}
/**
* @description 获取验证码
*/
export function getCaptcha(): Promise<AxiosResponse> {
return request({
url: '/system/login/captcha',
method: 'get',
});
}
/**
* @description 登录
*/
export function onLogin(data: loginData): Promise<AxiosResponse> {
return request({
url: '/auth/oauth/token',
method: 'post',
data: qs.stringify(data),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
}
/**
* @description 退出登录
*/
export function userLogout(): Promise<AxiosResponse> {
return request({
url: '/system/user/userLogout',
method: 'post',
});
}
/**
* 修改用户密码
*/
export function updateUserPassword(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/updateUserPassword',
method: 'post',
data,
});
}
/**
* 根据token获取用户信息
*/
export function selectUserByToken(): Promise<AxiosResponse> {
return request({
url: '/system/user/selectUserByToken',
method: 'get',
});
}
/**
* 个人信息-需改用户
*/
export function updateBySelf(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/updateBySelf',
method: 'post',
data,
});
}
/**
* 上传文件
*/
export const uploadFile = (data = {}): Promise<AxiosResponse> =>
request({
url: '/system/file/avatar',
method: 'post',
data,
});
/**
* 轮询接口
* */
export const pollOfflineStatus = (data: { loginId: string }): Promise<AxiosResponse> =>
request({
url: 'system/login/pollOfflineStatus',
method: 'get',
params: data,
});
/**
* 同意下线接口
*/
export const agreeOffline = (): Promise<AxiosResponse> =>
request({
url: 'system/user/agreeOffline',
method: 'get',
});
/**
* 拒绝下线接口
*/
export const refuseOffline = (): Promise<AxiosResponse> =>
request({
url: 'system/user/refuseOffline',
method: 'get',
});
export const refreshToken = () => {
const token = localStorage.getItem('TOKEN');
let refreshToken = '';
if (token && token !== 'undefined') {
refreshToken = JSON.parse(token).refresh_token;
}
const params = {
client_id: 'lin',
client_secret: '123123',
grant_type: 'refresh_token',
refresh_token: refreshToken,
};
return request({
url: '/auth/oauth/token',
method: 'post',
data: qs.stringify(params),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
};
import type { AxiosResponse } from 'axios';
import type { BlobData } from '@/types/tools';
import request from '@/utils/request';
/**
* @description 树状组织架构列表(分页)
*/
export function getOrganize(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/org/tree',
method: 'get',
params: data,
});
}
/**
* @description 树状组织架构列表(不分页)
*/
export function getOrgAll(): Promise<AxiosResponse> {
return request({
url: '/system/org/tree/all',
method: 'get',
});
}
/**
* @description 添加组织信息
*/
export function addOrganize(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/org',
method: 'post',
data,
});
}
/**
* @description 修改组织信息
*/
export function updateOrganize(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/org/update',
method: 'post',
data,
});
}
/**
* @description 根据id数组删除组织信息
*/
export function deleteOrganize(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/org/delete',
method: 'post',
data,
});
}
/**
* @description 导出数据
*/
export function getFile(): Promise<AxiosResponse<BlobData>> {
return request({
url: '/system/org/export',
method: 'get',
responseType: 'blob',
});
}
/**
* @description 获取树状行政区域列表
*/
export function getDistrict(): Promise<AxiosResponse> {
return request({
url: '/system/district/tree',
method: 'get',
});
}
/**
* @description 判断当前组织是否能删除
*/
export function isOrgHasChildren(data: unknown): Promise<AxiosResponse> {
return request({
method: 'post',
url: '/system/org/existChildren',
data,
});
}
/**
* @description 查询组织负责人
*/
export function getOrgDirector(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/org/getOrgDirector',
method: 'get',
params: data,
});
}
/**
* @description 获取用户列表下拉值
*/
export function getUserListByOrgId(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/org/getUserListByOrgId',
method: 'get',
params: data,
});
}
import type { AxiosResponse } from 'axios';
import type { BlobData } from '@/types/tools';
import request from '@/utils/request';
/**
* @description 岗位类型列表
*/
export function getPostList(): Promise<AxiosResponse> {
return request({
url: '/system/position/list',
method: 'get',
});
}
/**
* @description 分页查询岗位信息
*/
export function getPostPage(data: any): Promise<AxiosResponse> {
return request({
url: '/system/position/page',
method: 'get',
params: data,
});
}
/**
* @description 添加岗位
*/
export function addPost(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/position',
method: 'post',
data,
});
}
/**
* @description 修改岗位
*/
export function editPost(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/position/update',
method: 'post',
data,
});
}
/**
* @description 删除岗位
*/
export function deletePost(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/position/delete',
method: 'post',
data,
});
}
/**
* @description 岗位禁用或启用
*/
export function disableOrEnable(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/position/disableOrEnable',
method: 'post',
data,
});
}
/**
* @description 导出数据
*/
export function getFile(): Promise<AxiosResponse<BlobData>> {
return request({
url: '/system/position/export',
method: 'get',
responseType: 'blob',
});
}
/**
* @description 岗位数据导入
*/
export function importExcel(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/position/importExcel',
method: 'post',
data,
headers: {
'Content-Type': 'multipart/form-data;boundary=<calculated when request is sent>',
},
});
}
/**
* @description 根据岗位id获取该岗位可选用户及已选用户信息
*/
export function getUserInfoByPositionId(
data: { positionId: string } = { positionId: '' },
): Promise<AxiosResponse> {
return request({
url: `/system/position/user/${data.positionId}`,
method: 'get',
});
}
/**
* @description 岗位新增关联用户
*/
export function addUserToPosition(data: any): Promise<AxiosResponse> {
return request({
url: '/system/position/addUserToPos',
method: 'post',
data,
});
}
/**
* @description 岗位移除关联用户
*/
export function removeUserFromPosition(data: any): Promise<AxiosResponse> {
return request({
url: '/system/position/removeUserFromPos',
method: 'post',
data,
});
}
import type { AxiosResponse } from 'axios';
import type { BlobData } from '@/types/tools';
import request from '@/utils/request';
import type { MenuTreeData } from '@/types/api/resourceManagement';
/**
* @description 返回当前用户的树形菜单集合
*/
export function getMenu(): Promise<AxiosResponse<MenuTreeData>> {
return request({
url: '/system/menu/tree',
method: 'get',
});
}
/**
* @description 导出数据
*/
export function getFile(): Promise<AxiosResponse<BlobData>> {
return request({
url: '/system/menu/export',
method: 'get',
responseType: 'blob',
});
}
/**
* @description 新增菜单
*/
export function addMenu(data: any): Promise<AxiosResponse> {
return request({
url: '/system/menu/save',
method: 'post',
data,
});
}
/**
* @description 修改菜单
*/
export function updateMenu(data: any): Promise<AxiosResponse> {
return request({
url: '/system/menu/update',
method: 'post',
data,
});
}
/**
* @description 修改系统资源菜单
*/
export function updateSystemMenu(data: any): Promise<AxiosResponse> {
return request({
url: 'system/menu/updateSystemMenu',
method: 'post',
data,
});
}
/**
* @description 删除菜单
*/
export function deleteMenu(data: any): Promise<AxiosResponse> {
return request({
url: '/system/menu/delete',
method: 'post',
data,
});
}
import type { AxiosResponse } from 'axios';
import type { BlobData } from '@/types/tools';
import request from '@/utils/request';
/**
* @description 根据角色id获取资源树
*/
export function getRoleInfo(id: string): Promise<AxiosResponse> {
return request({
url: `/system/menu/getTreeMenusByRoleId/${id}`,
method: 'get',
});
}
/**
* @description 添加角色
*/
export function addRole(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/save',
method: 'post',
data,
});
}
/**
* @description 修改角色
*/
export function editRole(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/update',
method: 'post',
data,
});
}
/**
* @description 删除角色
*/
export function deleteRole(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/delete',
method: 'post',
data,
});
}
/**
* @description 分页查询角色列表
*/
export function getRolePage(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/page',
method: 'get',
params: data,
});
}
/**
* @description 查询角色列表树
*/
export function getRoleTree(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/tree',
method: 'get',
params: data,
});
}
/**
* @description 获取全部角色列表
*/
export function getAllRole(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/all',
method: 'get',
params: data,
});
}
/**
* @description 角色关联权限菜单(单次操作)
*/
export function addMenu(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/roleAndMenu/add',
method: 'post',
data,
});
}
/**
* @description 删除角色关联权限菜单(单次操作)
*/
export function deleteMenu(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/roleAndMenu/delete',
method: 'post',
data,
});
}
/**
* @description 更新 角色 关联 权限菜单 批量操作(暂不使用)
*/
export function roleAndMenu(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/roleAndMenu/update',
method: 'post',
data,
});
}
/**
* @description 返回当前用户的树形菜单集合
*/
export function getMenu(): Promise<AxiosResponse> {
return request({
url: '/system/menu',
method: 'get',
});
}
/**
* @description 根据角色id获取最底层资源信息列表
*/
export function getLowestMenus(id: string): Promise<AxiosResponse> {
return request({
url: `/system/menu/getLowestMenusByRoleId/${id}`,
method: 'get',
});
}
/**
* @description 导出数据
*/
export function getFile(): Promise<AxiosResponse<BlobData>> {
return request({
url: '/system/role/export',
method: 'get',
responseType: 'blob',
});
}
/**
* @description 查询-角色已关联的用户
*/
export function getRoleUser(id: any): Promise<AxiosResponse> {
return request({
url: `/system/role/user/${id}`,
method: 'get',
});
}
/**
* @description 角色新增用户
*/
export function addUserToRole(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/addUserToRole',
method: 'post',
data,
});
}
/**
* @description 删除角色已有的用户
*/
export function deleteUserToRole(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/removeUserFromRole',
method: 'post',
data,
});
}
/**
* @description 岗位数据导入
*/
export function importExcel(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/importExcel',
method: 'post',
data,
headers: {
'Content-Type': 'multipart/form-data;boundary=<calculated when request is sent>',
},
});
}
/**
* @description 更新用户关联角色
* @description @param data
*/
export function updateUserRole(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/userAndRole/update',
method: 'post',
data,
});
}
/**
* @description 更新角色关联资源
*/
export function updateRoleResource(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/roleAndMenu/update',
method: 'post',
data,
});
}
/**
* @description 更新角色,关联的用户
* @description @param data
*/
export function updateUserToRole(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/updateUserToRole',
method: 'post',
data,
});
}
/**
* @description 查询角色/分组树(全部)
*/
export function getTreeRoleGroup(): Promise<AxiosResponse> {
return request({
url: 'system/role/selectTreeRoleGroup',
method: 'get',
});
}
/**
* @description 查询组织-人员树
*/
export function getRecipient(data = {}): Promise<AxiosResponse> {
return request({
url: 'system/org/getRecipient',
method: 'get',
params: data,
});
}
/**
* @description 查询数据字典-类别-具体字典项
* @description @param code
*/
export function getDictionaryTree(code: string): Promise<AxiosResponse> {
return request({
url: `/system/dict/item/info/${code}`,
method: 'get',
});
}
/**
* @description 根据id查询角色信息
* @description @param id
*/
export function getRoleDetail(id: string): Promise<AxiosResponse> {
return request({
url: `/system/role/${id}`,
method: 'get',
});
}
/**
* @description 角色禁用或启用
*/
export function disableOrEnable(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/role/disableOrEnable',
method: 'post',
data,
});
}
/**
* @description 判断当前角色是否能删除
*/
export function hasRelatedUser(data: unknown): Promise<AxiosResponse> {
return request({
method: 'post',
url: '/system/role/hasRelatedUser',
data,
});
}
/**
* @description 查询-根据角色id查询可选资源和已选资源
*/
export function getRoleMenuTree(id: string): Promise<AxiosResponse> {
return request({
url: `/system/role/menu/${id}`,
method: 'get',
});
}
/**
* @description 修改角色关联的菜单操作权限(0操作,1只读,2管理)
*/
export function updateMenuModify(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/role/roleAndMenu/modify',
method: 'post',
data,
});
}
import type { AxiosResponse } from 'axios';
import qs from 'qs';
import request from '@/utils/request';
import type { BlobData } from '@/types/tools';
/**
* @description 分页获取用户列表
*/
export function getUserList(data: any): Promise<AxiosResponse> {
return request({
url: '/system/user/pageUser',
method: 'get',
params: data,
});
}
/**
* @description 通过ID查询用户信息
*/
export function getUserInfo(userId: string): Promise<AxiosResponse> {
return request({
url: `/system/user/${userId}`,
method: 'get',
});
}
/**
* @description 添加用户
*/
export function addUser(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/saveUser',
method: 'post',
data,
});
}
/**
* @description 修改用户
*/
export function editUser(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/update',
method: 'post',
data,
});
}
/**
* @description 个人信息-需改用户
*/
export function updateBySelf(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/updateBySelf',
method: 'post',
data,
});
}
/**
* @description 删除用户
*/
export function deleteUser(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/delete',
method: 'post',
data,
});
}
/**
* @description 用户禁用或启用
*/
export function disableOrEnable(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/disableOrEnable',
method: 'post',
data,
});
}
/**
* @description 查询-用户已关联的组织
*/
export function getUserOrg(id: any): Promise<AxiosResponse> {
return request({
url: `/system/user/org/${id}`,
method: 'get',
});
}
/**
* @description 用户和组织关联
*/
export function userAndOrg(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/userAndOrg/add',
method: 'post',
data,
});
}
/**
* @description 删除用户和组织关联
*/
export function deleteUserAndOrg(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/userAndOrg/delete',
method: 'post',
data,
});
}
/**
* @description 查询-用户已关联的角色
*/
export function getUserRole(id: any): Promise<AxiosResponse> {
return request({
url: `/system/user/role/${id}`,
method: 'get',
});
}
/**
* @description 用户和角色关联
*/
export function deleteUserAndRole(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/userAndRole/delete',
method: 'post',
data,
});
}
/**
* @description 导出用户管理数据
*/
export function exportData(data = {}): Promise<AxiosResponse<BlobData>> {
return request({
url: '/system/user/export',
method: 'get',
params: data,
responseType: 'blob',
});
}
/**
* @description 修改用户锁状态
*/
export function updateUserLockFlag<T>(data: T): Promise<AxiosResponse> {
return request({
url: '/system/user/updateLockFlag',
method: 'post',
data: qs.stringify(data),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
}
/**
* @description 组织人员迁移
*/
export function personMagration(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/org/orgPersonMigration',
method: 'post',
data,
});
}
/**
* @description 用户密码重置
*/
export function resetUserPassword<T>(userId: T): Promise<AxiosResponse> {
return request({
url: `/system/user/resetUserPassword/${userId}`,
method: 'post',
});
}
/**
* @description 查询-用户已关联的岗位
*/
export function getUserPost<T>(id: T): Promise<AxiosResponse> {
return request({
url: `/system/user/position/${id}`,
method: 'get',
});
}
/**
* @description 删除用户和岗位关联
*/
export function deleteUserAndPost(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/userAndPost/delete',
method: 'post',
data,
});
}
/**
* @description 用户和岗位关联
*/
export function userAndPost(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/userAndPost/add',
method: 'post',
data,
});
}
/**
* @description 用户和角色关联
*/
export function userAndRole(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/userAndRole/add',
method: 'post',
data,
});
}
/**
* @description 用户下线
*/
export function offlineUser(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/user/offline',
method: 'post',
data: qs.stringify(data),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
});
}
import type { AxiosResponse } from 'axios';
import request from '@/utils/request';
/**
* @description 树状行政区域列表
*/
export function getDistrict(): Promise<AxiosResponse> {
return request({
url: '/system/district/tree',
method: 'get',
});
}
/**
* @description 分页查询行政地区信息
*/
export function getDistrictPage(data: any): Promise<AxiosResponse> {
return request({
url: '/system/district/page',
method: 'get',
params: data,
});
}
/**
* @description 添加行政地区信息
*/
export function addDistrict(data: any): Promise<AxiosResponse> {
return request({
url: '/system/district/save',
method: 'post',
data,
});
}
/**
* @description 修改行政地区信息
*/
export function updateDistrict(data: any): Promise<AxiosResponse> {
return request({
url: '/system/district/update',
method: 'post',
data,
});
}
/**
* @description 删除行政地区
*/
export function deleteDistrict(data: any): Promise<AxiosResponse> {
return request({
url: '/system/district/delete',
method: 'post',
data,
});
}
/**
* @description 导出指定行政地区
*/
export function getDistrictFile(): Promise<AxiosResponse> {
return request({
url: '/system/district/export',
method: 'get',
responseType: 'blob',
});
}
/**
* @description 行政区域数据导入
*/
export function importExcel(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/district/importExcel',
method: 'post',
data,
headers: {
'Content-Type': 'multipart/form-data;boundary=<calculated when request is sent>',
},
});
}
import type { AxiosResponse } from 'axios';
import request from '@/utils/request';
/**
* @description 根据数据字典类型code获取 字典项信息列表 【页面下拉 调用】
*/
export function getDictInfo(data: string): Promise<AxiosResponse> {
return request({
url: `/system/dict/item/info/${data}`,
method: 'get',
});
}
/**
* @description 查询分页字典项信息
*/
export function getDictionaryManagement(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/page',
method: 'get',
params: data,
});
}
/**
* @description 查询字典类型列表
*/
export function getDictionaryList(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/type/selectList',
method: 'get',
params: data,
});
}
/**
* @description 添加数据字典信息
*/
export function addDictionaryData(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/save',
method: 'post',
data,
});
}
/**
* @description 添加数据字典类别信息
*/
export function addDictionaryType(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/type',
method: 'post',
data,
});
}
/**
* @description 获取数据字典详情
*/
export function getDictionaryDetail(id = ''): Promise<AxiosResponse> {
return request({
url: `/system/dict/info/${id}`,
method: 'get',
});
}
/**
* @description 修改数据字典信息
*/
export function editDictionaryData(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/update',
method: 'post',
data,
});
}
/**
* @description 删除数据字典信息
*/
export function deleteDictionaryData(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/delete',
method: 'post',
data,
});
}
/**
* @description 修改数据字典类别信息
*/
export function editDictionaryType(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/type/update',
method: 'post',
data,
});
}
/**
* @description 删除数据字典类别信息
*/
export function deleteDictionaryType(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/type/delete',
method: 'post',
data,
});
}
/**
* @description 数据字典项的禁用和启用
*/
export function disableOrEnable(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/disableOrEnable',
method: 'post',
data,
});
}
/**
* @description 导出所有数据字典
*/
export function exportAll(): Promise<AxiosResponse> {
return request({
url: '/system/dict/exportAll',
method: 'get',
responseType: 'blob',
});
}
/**
* @description 导出指定数据字典
*/
export function exportDict(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/export',
method: 'get',
params: data,
responseType: 'blob',
});
}
/**
* @description 判断当前字典是否有子文件夹
*/
export function isDictHasChildren(data: unknown): Promise<AxiosResponse> {
return request({
method: 'post',
url: '/system/dict/type/existChildren',
data,
});
}
/**
* @description 字典数据导入
*/
export function importExcel(data: unknown): Promise<AxiosResponse> {
return request({
url: '/system/dict/importExcel',
method: 'post',
data,
headers: {
'Content-Type': 'multipart/form-data;boundary=<calculated when request is sent>',
},
});
}
/**
* @description 获取父节点下拉选项
*/
export function getParentNodesOptions(data = {}): Promise<AxiosResponse> {
return request({
url: '/system/dict/getParentNodes',
method: 'get',
params: data,
});
}
import type { AxiosResponse } from 'axios';
import qs from 'qs';
import request from '@/utils/request';
/**
* @description 查询日志统计折线图
*/
export function getLogLineChart(data: any): Promise<AxiosResponse> {
return request({
url: `/system/log/getLogLineChart/${data}`,
method: 'get',
});
}
/**
* @description 查询日志统计饼图和柱状图
*/
export function getLogHistogramAndPieChart(data: any): Promise<AxiosResponse> {
return request({
url: '/system/log/getLogHistogramAndPieChart',
method: 'get',
params: data,
paramsSerializer: (params) => qs.stringify(params, { indices: false }),
});
}
import type { AxiosResponse } from 'axios';
import qs from 'qs';
import request from '@/utils/request';
/**
* @description 查询日志信息
*/
export function getLoginlog(data: any): Promise<AxiosResponse> {
return request({
url: '/system/loginRecord/page',
method: 'get',
params: data,
paramsSerializer: (params) => qs.stringify(params, { indices: false }),
});
}
/**
* @description 导出数据
*/
export function getFile(data: any): Promise<AxiosResponse> {
return request({
url: '/system/loginRecord/export',
method: 'get',
params: data,
responseType: 'blob',
paramsSerializer: (params) => qs.stringify(params, { indices: false }),
});
}
/**
* @description 删除日志
*/
export function deleteLog(type: number): Promise<AxiosResponse> {
return request({
url: `/system/loginRecord/deleteHistoryLog/${type}`,
method: 'post',
});
}
/**
* @description 日志阙值查询
*/
export function getIsThreshold(): Promise<AxiosResponse> {
return request({
url: '/system/loginRecord/isThreshold',
method: 'get',
});
}
/**
* @description 获取备份文件列表
*/
export function getBackupFiles(): Promise<AxiosResponse> {
return request({
url: '/system/loginRecord/getBackupFiles',
method: 'get',
});
}
/**
* @description 批量删除历史备份
*/
export function deleteBackupFiles(data): Promise<AxiosResponse> {
return request({
url: '/system/loginRecord/deleteBackupFiles',
method: 'post',
data,
});
}
/**
* @description 备份
*/
export function backup(): Promise<AxiosResponse> {
return request({
url: '/system/loginRecord/backup',
method: 'get',
});
}
/**
* @description 恢复
*/
export function recover(data): Promise<AxiosResponse> {
return request({
url: '/system/loginRecord/recover',
method: 'get',
params: data,
});
}
/**
* @description 保存日志
*/
export function saveSystemLog(data): Promise<AxiosResponse> {
return request({
url: '/system/systemLog/save',
method: 'post',
data,
});
}
import qs from 'qs';
import type { AxiosResponse } from 'axios';
import request from '@/utils/request';
/**
* @description 获取公告列表
*/
export const getAnnouncementPage = (params = {}): Promise<AxiosResponse> =>
request({
url: '/message/information/getAnnouncementPage',
method: 'get',
params,
paramsSerializer: (params) => qs.stringify(params, { indices: false }),
});
/**
* @description 发布公告
*/
export const releaseAnnouncement = (data = {}): Promise<AxiosResponse> =>
request({
url: '/message/information/releaseAnnouncement',
method: 'post',
data,
});
/**
* @description 新增公告
*/
export const saveAnnouncement = (data = {}): Promise<AxiosResponse> =>
request({
url: '/message/information/saveAnnouncement',
method: 'post',
data,
});
/**
* @description 更新公告
*/
export const updateAnnouncement = (data = {}): Promise<AxiosResponse> =>
request({
url: '/message/information/updateAnnouncement',
method: 'post',
data,
});
/**
* @description 撤回公告
*/
export const returnAnnouncement = (data = {}): Promise<AxiosResponse> =>
request({
url: '/message/information/announcementWithdrawn',
method: 'post',
data,
});
/**
* @description 获取私信列表
*/
export const getPersonalLetterPage = (params): Promise<AxiosResponse> =>
request({
url: '/message/information/getPersonalLetterPage',
method: 'get',
params,
});
/**
* @description 获取人员树
*/
export const getPeopleTree = (): Promise<AxiosResponse> =>
request({
url: '/system/org/getRecipient',
method: 'get',
});
/**
* @description 新增私信
*/
export const savePersonalLetter = (data = {}): Promise<AxiosResponse> =>
request({
url: '/message/information/savePersonalLetter',
method: 'post',
data,
});
/**
* @description 更新私信
*/
export const updatePersonalLetter = (data = {}): Promise<AxiosResponse> =>
request({
url: '/message/information/updatePersonalLetter',
method: 'post',
data,
});
/**
* @description 发送私信
*/
export const releasePersonalLetter = (data = {}): Promise<AxiosResponse> =>
request({
url: '/message/information/releasePersonalLetter',
method: 'post',
data,
});
/**
* @description 获取未读消息列表
*/
export const getAllUnreadPersonalLetter = (data = {}): Promise<AxiosResponse> =>
request({
url: '/message/information/getAllUnreadPersonalLetter',
method: 'get',
data,
});
/**
* @description 查看详情(私信)
*/
export const getPersonalLetterInfo = (id: string): Promise<AxiosResponse> =>
request({
url: `/message/information/getPersonalLetterInfo/${id}`,
method: 'get',
});
/**
* @description 查看详情(公告)
*/
export const getAnnouncementInfo = (id: string): Promise<AxiosResponse> =>
request({
url: `/message/information/getAnnouncementInfo/${id}`,
method: 'get',
});
/**
* @description 上传文件
*/
export const uploadFile = (data = {}): Promise<AxiosResponse> =>
request({
url: '/message/information/upload',
method: 'post',
data,
});
import type { AxiosResponse } from 'axios';
import request from '@/utils/request';
/**
* @description 查询在线人数列表
*/
export function selectOnlineUser(data: any): Promise<AxiosResponse> {
return request({
url: '/system/user/selectOnlineUser',
method: 'get',
params: data,
});
}
/**
* @description 查询在线人数
*/
export function getPersonSize(): Promise<AxiosResponse> {
return request({
url: '/system/user/OnlineUserNumber',
method: 'get',
});
}
import type { AxiosResponse } from 'axios';
import qs from 'qs';
import request from '@/utils/request';
/**
* @description 查询日志信息
*/
export function getOperationLog(data: any): Promise<AxiosResponse> {
return request({
url: '/system/log/page',
method: 'get',
params: data,
paramsSerializer: (params) => qs.stringify(params, { indices: false }),
});
}
/**
* @description 日志阙值查询
*/
export function getIsThreshold(): Promise<AxiosResponse> {
return request({
url: '/system/log/isThreshold',
method: 'get',
});
}
/**
* @description 删除日志
*/
export function deleteHistoryLog(id = ''): Promise<AxiosResponse> {
return request({
url: `/system/log/deleteHistoryLog/${id}`,
method: 'post',
});
}
/**
* @description 导出数据
*/
export function getFile(data: any): Promise<AxiosResponse> {
return request({
url: '/system/log/export',
method: 'get',
params: data,
responseType: 'blob',
paramsSerializer: (params) => qs.stringify(params, { indices: false }),
});
}
/**
* @description 获取备份文件列表
*/
export function getBackupFiles(): Promise<AxiosResponse> {
return request({
url: '/system/log/getBackupFiles',
method: 'get',
});
}
/**
* @description 批量删除历史备份
*/
export function deleteBackupFiles(data): Promise<AxiosResponse> {
return request({
url: '/system/log/deleteBackupFiles',
method: 'post',
data,
});
}
/**
* @description 备份
*/
export function backup(): Promise<AxiosResponse> {
return request({
url: '/system/log/backup',
method: 'get',
});
}
/**
* @description 恢复
*/
export function recover(data): Promise<AxiosResponse> {
return request({
url: '/system/log/recover',
method: 'get',
params: data,
});
}
import type { AxiosResponse } from 'axios';
import request from '@/utils/request';
/**
* @description 查询安评配置项
*/
export const getSecurityConfig = (params = {}): Promise<AxiosResponse> =>
request({
url: '/system/config/safe',
method: 'get',
params,
});
/**
* @description 修改安评配置项值
*/
export const updateSecurityConfig = (data = {}): Promise<AxiosResponse> =>
request({
url: '/system/config/update',
method: 'post',
data,
});
/**
* @description 获取安全配置各项数据的安评配置
*/
export const getSafeState = (): Promise<AxiosResponse> =>
request({
url: '/system/config/safeState',
method: 'get',
});
/**
* @description 获取修改密码的安评配置
*/
export const getSafePasswordSecurityState = (): Promise<AxiosResponse> =>
request({
url: '/system/config/state/passwordSecurity',
method: 'get',
});
import type { AxiosResponse } from 'axios';
import request from '@/utils/request';
/**
* @description 查询安全配置信息
*/
export const getSystemConfig = (): Promise<AxiosResponse> =>
request({
url: '/system/config/project/info',
method: 'get',
});
/**
* @description 上传系统图标(未修改、单纯上传)
*/
export const uploadSystemIcon = (data = {}): Promise<AxiosResponse> =>
request({
url: '/system/config/file/uploadSystemIcon',
method: 'post',
data,
});
/**
* @description 修改系统图标文件
*/
export const updateSystemIcon = (data = {}): Promise<AxiosResponse> =>
request({
url: '/system/config/update/systemIcon',
method: 'post',
data,
});
/**
* @description 修改系统标题
*/
export const updateSystemName = (systemName = ''): Promise<AxiosResponse> =>
request({
url: `/system/config/update/systemName/${systemName}`,
method: 'post',
});
/**
* @description 修改系统副标题
*/
export const updateSystemSubTitle = (systemSubTitle = ''): Promise<AxiosResponse> =>
request({
url: `/system/config/update/systemSubTitle/${systemSubTitle}`,
method: 'post',
});
import type { AxiosResponse } from 'axios';
import qs from 'qs';
import request from '@/utils/request';
/**
* @description 查询日志信息
*/
export function getSystemLog(data: any): Promise<AxiosResponse> {
return request({
url: '/system/systemLog/page',
method: 'get',
params: data,
paramsSerializer: (params) => qs.stringify(params, { indices: false }),
});
}
/**
* @description 删除日志
*/
export function deleteHistoryLog(code = ''): Promise<AxiosResponse> {
return request({
url: `/system/systemLog/deleteHistoryLog/${code}`,
method: 'post',
});
}
/**
* @description 导出数据
*/
export function getFile(data: any): Promise<AxiosResponse> {
return request({
url: '/system/systemLog/export',
method: 'get',
params: data,
responseType: 'blob',
paramsSerializer: (params) => qs.stringify(params, { indices: false }),
});
}
// cover some element-plus styles
// element-plus样式变量覆盖在下方定义,对应变量名见element-plus/theme-chalk/src/common/var.scss
@forward 'element-plus/theme-chalk/src/common/var.scss' with (
$colors: (
'white': #ffffff,
'black': #000000,
'primary': (
'base': #3691FF,
),
'success': (
'base': #67c23a,
),
'warning': (
'base': #e6a23c,
),
'danger': (
'base': #f56c6c,
),
'error': (
'base': #f56c6c,
),
'info': (
'base': #909399,
),
),
$pagination: (
'font-size': 12px,
),
$table: (
'row-hover-bg-color': transparent
)
);
@use 'element-plus/theme-chalk/src/index.scss';
@use './element-plus.scss';
@use 'src/assets/styles/views/index.scss';
body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
font-family:
'Microsoft YaHei',
-apple-system,
BlinkMacSystemFont,
'Segoe UI',
'PingFang SC',
'Hiragino Sans GB',
'Helvetica Neue',
Helvetica,
Arial,
sans-serif,
'Apple Color Emoji',
'Segoe UI Emoji',
'Segoe UI Symbol';
}
html {
box-sizing: border-box;
width: 100%;
height: 100%;
}
*,
*::before,
*::after {
box-sizing: inherit;
}
a,
a:focus,
a:hover {
color: inherit;
text-decoration: none;
cursor: pointer;
}
a:focus,
a:active {
outline: none;
}
div:focus {
outline: none;
}
ul {
padding: 0;
margin: 0;
list-style: none;
}
.clearfix {
&::after {
display: block;
height: 0;
clear: both;
font-size: 0;
visibility: hidden;
content: ' ';
}
}
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f6f6f6;
border-radius: 2px;
}
::-webkit-scrollbar-thumb {
background: #cdcdcd;
border-radius: 2px;
}
::-webkit-scrollbar-thumb:hover {
background: #747474;
}
::-webkit-scrollbar-corner {
background: #f6f6f6;
}
// .el-popper {
// max-width: 500px;
// }
// Websocket Notification
.forced-offline, .go-offline, .query-offline {
.el-dialog__footer {
padding: 0 !important;
}
.el-dialog__body {
padding-bottom: 15px;
}
}
.el-tree-node__label {
width: 100%;
}
// 查询操作容器
.common-search-container {
display: flex;
justify-content: space-between;
.smart-search {
flex: 1;
}
.operation-btns {
min-width: 85px;
overflow: hidden;
text-align: right;
}
}
This diff is collapsed.
<svg xmlns="http://www.w3.org/2000/svg" data-name="Layer 1" width="1120.59226" height="777.91584" viewBox="0 0 1120.59226 777.91584" xmlns:xlink="http://www.w3.org/1999/xlink"><title>not found</title><circle cx="212.59226" cy="103" r="64" fill="#ff6584"/><path d="M563.68016,404.16381c0,151.01141-89.77389,203.73895-200.51559,203.73895S162.649,555.17522,162.649,404.16381,363.16457,61.04208,363.16457,61.04208,563.68016,253.1524,563.68016,404.16381Z" transform="translate(-39.70387 -61.04208)" fill="#f2f2f2"/><polygon points="316.156 523.761 318.21 397.378 403.674 241.024 318.532 377.552 319.455 320.725 378.357 207.605 319.699 305.687 319.699 305.687 321.359 203.481 384.433 113.423 321.621 187.409 322.658 0 316.138 248.096 316.674 237.861 252.547 139.704 315.646 257.508 309.671 371.654 309.493 368.625 235.565 265.329 309.269 379.328 308.522 393.603 308.388 393.818 308.449 394.99 293.29 684.589 313.544 684.589 315.974 535.005 389.496 421.285 316.156 523.761" fill="#3f3d56"/><path d="M1160.29613,466.01367c0,123.61-73.4842,166.77-164.13156,166.77s-164.13156-43.16-164.13156-166.77S996.16457,185.15218,996.16457,185.15218,1160.29613,342.40364,1160.29613,466.01367Z" transform="translate(-39.70387 -61.04208)" fill="#f2f2f2"/><polygon points="950.482 552.833 952.162 449.383 1022.119 321.4 952.426 433.154 953.182 386.639 1001.396 294.044 953.382 374.329 953.382 374.329 954.741 290.669 1006.369 216.952 954.954 277.514 955.804 124.11 950.467 327.188 950.906 318.811 898.414 238.464 950.064 334.893 945.173 428.327 945.027 425.847 884.514 341.294 944.844 434.608 944.232 446.293 944.123 446.469 944.173 447.428 931.764 684.478 948.343 684.478 950.332 562.037 1010.514 468.952 950.482 552.833" fill="#3f3d56"/><ellipse cx="554.59226" cy="680.47903" rx="554.59226" ry="28.03433" fill="#3f3d56"/><ellipse cx="892.44491" cy="726.79663" rx="94.98858" ry="4.80162" fill="#3f3d56"/><ellipse cx="548.71959" cy="773.11422" rx="94.98858" ry="4.80162" fill="#3f3d56"/><ellipse cx="287.94432" cy="734.27887" rx="217.01436" ry="10.96996" fill="#3f3d56"/><circle cx="97.08375" cy="566.26982" r="79" fill="#2f2e41"/><rect x="99.80546" y="689.02332" width="24" height="43" transform="translate(-31.32451 -62.31008) rotate(0.67509)" fill="#2f2e41"/><rect x="147.80213" y="689.58887" width="24" height="43" transform="translate(-31.31452 -62.87555) rotate(0.67509)" fill="#2f2e41"/><ellipse cx="119.54569" cy="732.61606" rx="7.5" ry="20" transform="translate(-654.1319 782.47948) rotate(-89.32491)" fill="#2f2e41"/><ellipse cx="167.55414" cy="732.18168" rx="7.5" ry="20" transform="translate(-606.25475 830.05533) rotate(-89.32491)" fill="#2f2e41"/><circle cx="99.31925" cy="546.29477" r="27" fill="#fff"/><circle cx="99.31925" cy="546.29477" r="9" fill="#3f3d56"/><path d="M61.02588,552.94636c-6.04185-28.64075,14.68758-57.26483,46.30049-63.93367s62.13813,11.14292,68.18,39.78367-14.97834,38.93-46.59124,45.59886S67.06774,581.58712,61.02588,552.94636Z" transform="translate(-39.70387 -61.04208)" fill="#6c63ff"/><path d="M257.29613,671.38411c0,55.07585-32.73985,74.3063-73.13,74.3063q-1.40351,0-2.80255-.0312c-1.87139-.04011-3.72494-.1292-5.55619-.254-36.45135-2.57979-64.77127-22.79937-64.77127-74.02113,0-53.00843,67.73872-119.89612,72.827-124.84633l.00892-.00889c.19608-.19159.29409-.28516.29409-.28516S257.29613,616.30827,257.29613,671.38411Z" transform="translate(-39.70387 -61.04208)" fill="#6c63ff"/><path d="M181.50168,737.26482l26.747-37.37367-26.81386,41.4773-.07125,4.29076c-1.87139-.04011-3.72494-.1292-5.55619-.254l2.88282-55.10258-.0223-.42775.049-.0802.27179-5.20415-26.88076-41.5798,26.96539,37.67668.06244,1.105,2.17874-41.63324-23.0132-42.96551,23.29391,35.6583,2.26789-86.31419.00892-.294v.28516l-.37871,68.064,22.91079-26.98321-23.00435,32.84678-.60595,37.27566L204.18523,621.958l-21.4805,41.259-.33863,20.723,31.05561-49.79149-31.17146,57.023Z" transform="translate(-39.70387 -61.04208)" fill="#3f3d56"/><circle cx="712.48505" cy="565.41532" r="79" fill="#2f2e41"/><rect x="741.77716" y="691.82355" width="24" height="43" transform="translate(-215.99457 191.86399) rotate(-17.08345)" fill="#2f2e41"/><rect x="787.6593" y="677.72286" width="24" height="43" transform="matrix(0.95588, -0.29376, 0.29376, 0.95588, -209.82788, 204.72037)" fill="#2f2e41"/><ellipse cx="767.887" cy="732.00275" rx="20" ry="7.5" transform="translate(-220.8593 196.83312) rotate(-17.08345)" fill="#2f2e41"/><ellipse cx="813.47537" cy="716.94619" rx="20" ry="7.5" transform="translate(-214.42477 209.56103) rotate(-17.08345)" fill="#2f2e41"/><circle cx="708.52153" cy="545.71023" r="27" fill="#fff"/><circle cx="708.52153" cy="545.71023" r="9" fill="#3f3d56"/><path d="M657.35526,578.74316c-14.48957-25.43323-3.47841-59.016,24.59412-75.0092s62.57592-8.34055,77.06549,17.09268-2.39072,41.6435-30.46325,57.63671S671.84483,604.17639,657.35526,578.74316Z" transform="translate(-39.70387 -61.04208)" fill="#6c63ff"/><path d="M611.29613,661.29875c0,50.55711-30.05368,68.20979-67.13,68.20979q-1.28835,0-2.57261-.02864c-1.71785-.03682-3.41933-.1186-5.10033-.23313-33.46068-2.36813-59.45707-20.92878-59.45707-67.948,0-48.65932,62.18106-110.05916,66.85186-114.60322l.00819-.00817c.18-.17587.27-.26177.27-.26177S611.29613,610.74164,611.29613,661.29875Z" transform="translate(-39.70387 -61.04208)" fill="#6c63ff"/><path d="M541.72029,721.77424l24.55253-34.30732-24.6139,38.07426-.0654,3.93872c-1.71785-.03682-3.41933-.1186-5.10033-.23313l2.6463-50.58165-.02047-.39266.045-.07361.24949-4.77718-24.67531-38.16836,24.753,34.58547.05731,1.01433,2-38.21741-21.12507-39.44039L541.80616,625.928l2.08182-79.23247.00819-.26994v.26177l-.34764,62.47962,21.031-24.76934-21.11693,30.15184-.55624,34.21735,19.63634-32.839-19.71812,37.87389-.31085,19.0228,28.50763-45.70631-28.614,52.34448Z" transform="translate(-39.70387 -61.04208)" fill="#3f3d56"/><path d="M875.29613,682.38411c0,55.07585-32.73985,74.3063-73.13,74.3063q-1.4035,0-2.80255-.0312c-1.87139-.04011-3.72494-.1292-5.55619-.254-36.45135-2.57979-64.77127-22.79937-64.77127-74.02113,0-53.00843,67.73872-119.89612,72.827-124.84633l.00892-.00889c.19608-.19159.29409-.28516.29409-.28516S875.29613,627.30827,875.29613,682.38411Z" transform="translate(-39.70387 -61.04208)" fill="#6c63ff"/><path d="M799.50168,748.26482l26.747-37.37367-26.81386,41.4773-.07125,4.29076c-1.87139-.04011-3.72494-.1292-5.55619-.254l2.88282-55.10258-.0223-.42775.049-.0802.27179-5.20415L770.108,654.01076l26.96539,37.67668.06244,1.105,2.17874-41.63324-23.0132-42.96551,23.29391,35.6583,2.26789-86.31419.00892-.294v.28516l-.37871,68.064,22.91079-26.98321-23.00435,32.84678-.606,37.27566L822.18523,632.958l-21.4805,41.259-.33863,20.723,31.05561-49.79149-31.17146,57.023Z" transform="translate(-39.70387 -61.04208)" fill="#3f3d56"/><ellipse cx="721.51694" cy="656.82212" rx="12.40027" ry="39.5" transform="translate(-220.83517 966.22323) rotate(-64.62574)" fill="#2f2e41"/><ellipse cx="112.51694" cy="651.82212" rx="12.40027" ry="39.5" transform="translate(-574.07936 452.71367) rotate(-68.15829)" fill="#2f2e41"/></svg>
<!-- 备份与恢复 -->
<template>
<el-dialog
v-model="state.isDialogVisible"
title="数据备份与恢复"
destroy-on-close
:width="530"
style="padding-bottom: 20px"
:close-on-press-escape="false"
:close-on-click-modal="false"
:show-close="!state.isTableDataLoading"
@close="handleCloseDialog"
>
<div class="operation">
<el-button plain type="primary" :loading="state.isBackuping" @click="handleBackup">
立即备份
</el-button>
<el-button plain type="danger" @click="handleBatchDelete">批量删除</el-button>
</div>
<div
v-loading="state.isTableDataLoading"
class="table"
:element-loading-text="state.elementLoadingText"
>
<el-table
ref="tableRef"
border
height="410"
:data="state.tableData"
@selection-change="handleSelectionChange"
>
<el-table-column type="selection" width="55" />
<el-table-column property="fileName" label="备份文件" />
<el-table-column fixed="right" label="操作" width="140">
<template #default="scope">
<el-button plain type="success" size="small" @click="handleRecover(scope.row)">
恢复
</el-button>
<el-button plain type="danger" size="small" @click="handleDelete([scope.row.fileName])">
删除
</el-button>
</template>
</el-table-column>
</el-table>
</div>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive, toRefs, watch } from 'vue';
import type { ElTable } from 'element-plus';
import { ElMessage, ElMessageBox } from 'element-plus';
const props = defineProps<{
fetchData: any;
backup: any;
batchDelete: any;
recover: any;
refreshTable: any;
}>();
const tableRef = ref<InstanceType<typeof ElTable>>();
const multipleSelection = ref();
const state = reactive<{
isDialogVisible: boolean;
isDeleteing: boolean;
isBackuping: boolean;
isTableDataLoading: boolean;
tableData: [{ fileName: string }] | [];
elementLoadingText: string;
}>({
isDialogVisible: false,
isDeleteing: false,
isBackuping: false,
isTableDataLoading: false,
tableData: [],
elementLoadingText: '处理中,请稍后...',
});
const handleSelectionChange = (val) => {
multipleSelection.value = val;
};
// 关闭dialog
const handleCloseDialog = () => {
state.isDialogVisible = false;
state.isDeleteing = false;
state.isBackuping = false;
state.isTableDataLoading = false;
};
// 显示dialog
const handleShowDialog = () => {
state.isDialogVisible = true;
};
const fetchBackupFiles = async () => {
state.isTableDataLoading = true;
try {
const res = await props.fetchData();
state.tableData = res.data.map((i) => ({
fileName: i,
}));
} catch (err) {
} finally {
state.isTableDataLoading = false;
}
};
// 立即备份
const handleBackup = async () => {
state.isBackuping = true;
state.isTableDataLoading = true;
state.elementLoadingText = '数据备份中,请稍后...';
try {
await props.backup();
await fetchBackupFiles();
} catch (err) {
console.error(err);
} finally {
state.isBackuping = false;
state.isTableDataLoading = false;
state.elementLoadingText = '处理中,请稍后...';
}
};
// 恢复备份后等待两1.5秒请求列表接口
const delay = (time) =>
new Promise((resolve) => {
setTimeout(resolve, time);
});
// 恢复数据
const handleRecover = async (data) => {
state.isTableDataLoading = true;
state.elementLoadingText = '数据恢复中,请稍后...';
try {
await props.recover({ fileName: data.fileName });
await delay(1500);
await props.refreshTable();
} catch (err) {
} finally {
state.isTableDataLoading = false;
state.elementLoadingText = '处理中,请稍后...';
}
};
// 删除数据(单个)
const handleDelete = async (data) => {
try {
ElMessageBox.confirm('确定删除备份文件?', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(async () => {
const formData = new FormData();
formData.append('fileNames', data);
await props.batchDelete(formData);
await fetchBackupFiles();
})
.catch(() => {});
} catch (err) {}
};
// 批量删除
const handleBatchDelete = async () => {
const selected = tableRef.value!.getSelectionRows();
if (selected.length === 0) {
ElMessage.warning('请选择要删除的数据');
return;
}
const selectedFilename = selected.map((i) => i.fileName);
try {
await handleDelete(selectedFilename);
} catch (err) {}
};
watch(
() => state.isDialogVisible,
(newV) => {
if (newV) {
fetchBackupFiles();
}
},
);
defineExpose({
...toRefs(state),
handleShowDialog,
handleCloseDialog,
});
</script>
<style lang="scss" scoped>
.operation {
display: flex;
justify-content: end;
margin-bottom: 10px;
}
.table {
min-height: 140px;
}
</style>
<template>
<el-popover placement="top-start" :width="200" trigger="hover" :content="props.content">
<template #reference>
<el-button
plain
type="primary"
:loading="uploadLoading"
v-bind="attrs"
@click="handleDownload"
>
<el-icon class="el-icon--left"><Upload /></el-icon>
{{ props.btnText }}
</el-button>
</template>
</el-popover>
</template>
<script lang="ts" setup>
import { useAttrs, ref } from 'vue';
import { ElMessage } from 'element-plus';
const emit = defineEmits(['completeUpload']);
const props = withDefaults(
defineProps<{
uploadApi: any;
btnText?: string;
uploadFileType: 'Excel' | 'others';
content?: string;
warning?: string;
}>(),
{
btnText: '导入数据',
content: '支持xls,xlsx,csv格式数据(模板请参照导出数据)',
warning: '请选择xls,xlsx,csv格式数据',
},
);
const UploadFileTypeList: { [propname: string]: string } = {
Excel:
'.csv, application/vnd.ms-excel, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
};
const attrs = useAttrs();
const uploadLoading = ref(false);
const handleDownload = () => {
let inputEle: null | HTMLInputElement = document.createElement('input');
inputEle.type = 'file';
inputEle.accept = UploadFileTypeList[props.uploadFileType];
inputEle.onchange = async (e: any) => {
if (e.target.files.length) {
const [file] = e.target.files;
const fType = file.type === 'text/csv' ? 'csv' : file.type;
// 导入格式不正确
if (UploadFileTypeList[props.uploadFileType].indexOf(fType) === -1) {
return ElMessage.warning(props.warning);
}
const formData = new FormData();
formData.append('fileData', file);
e.target.value = '';
uploadLoading.value = true;
props
.uploadApi(formData)
.then(() => {
emit('completeUpload');
})
.finally(() => {
uploadLoading.value = false;
});
inputEle = null;
}
};
inputEle.click();
};
</script>
<template>
<el-dialog
v-model="isDialogVisible"
title="导出数据"
destroy-on-close
:width="530"
:close-on-press-escape="false"
:close-on-click-modal="false"
@close="handleCloseDialog"
>
<el-form ref="exportLogFormRef" :model="exportLogForm">
<el-form-item v-if="isFormItemShow('createTime')" label="时间范围">
<el-date-picker
v-model="exportLogForm.createTime"
type="datetimerange"
:shortcuts="timeRange"
range-separator="~"
start-placeholder="开始日期"
end-placeholder="结束时间"
style="width: 100%"
/>
</el-form-item>
<el-form-item v-if="isFormItemShow('riskLevel')" label="风险等级">
<el-select v-model="exportLogForm.riskLevel" clearable placeholder="请选择风险等级">
<el-option
v-for="item in riskLevelOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="isFormItemShow('logLevel')" label="日志类别">
<el-select v-model="exportLogForm.logLevel" clearable placeholder="请选择日志类别">
<el-option
v-for="item in systemOptions.logType"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="isFormItemShow('source')" label="日志来源">
<el-select v-model="exportLogForm.source" clearable placeholder="请选择日志来源">
<el-option
v-for="item in systemOptions.sourceType"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<el-form-item v-if="isFormItemShow('logCount')" label="日志数量">
<el-input-number
v-model="exportLogForm.logCount"
:min="0"
:max="10000"
style="width: 215px"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="btn">
<el-button @click="handleResetForm">重置</el-button>
<el-button @click="handleCloseDialog">取消</el-button>
<el-button type="primary" :loading="isDownloading" @click="handleExportDialogForm">
导出
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue';
import { optionsData as systemOptions } from '@/views/SystemManagement/SystemLog/composables/data';
// 风险等级
const riskLevelOptions = [
{
value: '0',
label: '高',
},
{
value: '1',
label: '中',
},
{
value: '2',
label: '低',
},
];
// 时间范围
const timeRange = [
{
text: '一小时',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000);
return [start, end];
},
},
{
text: '十二小时',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 12);
return [start, end];
},
},
{
text: '二十四小时',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24);
return [start, end];
},
},
{
text: '三天',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 3);
return [start, end];
},
},
{
text: '一周',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 7);
return [start, end];
},
},
{
text: '一个月',
value: () => {
const end = new Date();
const start = new Date();
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30);
return [start, end];
},
},
];
const emits = defineEmits(['export-log']);
const props = defineProps<{
formKeyValue?: string[];
}>();
// 是否正在下载
const isDownloading = ref<boolean>(false);
// 控制dialog显隐
const isDialogVisible = ref(false);
// form ref
const exportLogFormRef = ref();
// 表单数据
const exportLogForm = reactive<{
createTime?: [Date, Date] | Date | undefined;
riskLevel?: string | undefined;
logCount?: number | undefined;
logLevel?: '错误' | '警告' | '信息' | undefined;
source?: 'FRONT_END' | 'BACK_END';
}>({
createTime: undefined,
riskLevel: undefined,
logCount: undefined,
logLevel: undefined,
source: undefined,
});
const isFormItemShow = (formKey: string) => {
if (props.formKeyValue) {
return props.formKeyValue.includes(formKey);
}
};
// 重置表单
const handleResetForm = () => {
exportLogForm.createTime = undefined;
exportLogForm.riskLevel = undefined;
exportLogForm.logCount = undefined;
exportLogForm.logLevel = undefined;
exportLogForm.source = undefined;
};
// 关闭dialog
const handleCloseDialog = () => {
handleResetForm();
isDialogVisible.value = false;
isDownloading.value = false;
};
// 显示dialog
const handleShowDialog = () => {
isDialogVisible.value = true;
};
// 导出
const handleExportDialogForm = () => {
emits('export-log');
};
defineExpose({
exportLogForm,
isDownloading,
exportLogFormRef,
handleShowDialog,
handleCloseDialog,
});
</script>
This diff is collapsed.
<template>
<div class="smart-search">
<el-form
ref="formRef"
:model="form"
label-width="80px"
class="smart-search__form"
label-suffix=":"
>
<el-row :gutter="10">
<el-col v-for="(val, index) in options" :key="val.label || index" :span="val.span || 6">
<el-form-item :label="val.label" :prop="val.key" :label-width="val.labelWidth || 'auto'">
<!-- 选择框 -->
<template v-if="val.type === 'select'">
<el-select
v-model="form[val.key]"
:placeholder="val.placeholder || '请选择'"
:disabled="val.disabled || false"
:clearable="val.clearable && true"
filterable
>
<el-option
v-for="item in val.options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
<template v-if="val.type === 'virtualSelect'">
<el-select-v2
v-model="form[val.key]"
:placeholder="val.placeholder || '请选择'"
:disabled="val.disabled || false"
:options="val.options"
clearable
filterable
/>
</template>
<!-- 输入框 -->
<template v-else-if="val.type === 'text'">
<el-input
v-model="form[val.key]"
:placeholder="val.placeholder || val.label"
:disabled="val.disabled || false"
clearable
>
<template #prefix>
<el-icon class="el-input__icon"><search /></el-icon>
</template>
</el-input>
</template>
<!-- 时间范围选择 -->
<template v-else-if="val.type === 'dateTimePicker'">
<el-date-picker
v-model="form[val.key]"
:type="val.datePickerType || 'daterange'"
:clearable="val.clearable && true"
range-separator="~"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 100%"
/>
</template>
</el-form-item>
</el-col>
<el-col v-action="props.queryPermission" :span="2">
<el-button @click="resetForm">
<el-icon class="el-icon--left">
<Refresh />
</el-icon>
重置
</el-button>
</el-col>
</el-row>
</el-form>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, watch } from 'vue';
import { handleFormatDate } from '@/utils/tools';
import type { SearchOption } from '@/types/tools';
const props = withDefaults(
defineProps<{
options: SearchOption[];
queryPermission: string;
}>(),
{
options: () => [],
},
);
const emit = defineEmits(['handleSearch', 'handleReset']);
const formRef = ref(null);
const form = reactive<any>({});
// 初始化检索框
props.options.forEach((v: any) => {
form[v.key] = v.value || v.value === 0 ? v.value : '';
});
// 查询数据
const handleSearch = () => {
const resultForm: any = {};
props.options.forEach((item: any) => {
// 日期选择器
if (
form[item.key] &&
item.type === 'dateTimePicker' &&
form[item.key].length !== 0 &&
typeof form[item.key][0] === 'object'
) {
// 有指定起始和终止时间的key
if (item.rangeKey) {
resultForm[item.rangeKey[0]] = handleFormatDate(form[item.key][0]);
resultForm[item.rangeKey[1]] = handleFormatDate(form[item.key][1]);
} else {
// 没有指定起始和终止时间的key
resultForm[item.key] = form[item.key].map((e) => handleFormatDate(e));
}
} else {
resultForm[item.key] = form[item.key];
}
});
emit('handleSearch', resultForm);
};
// 重置检索框
const resetForm = () => {
(formRef.value as any).resetFields();
handleSearch();
};
// 监听查询条件
watch(
() => form,
() => {
handleSearch();
},
{
deep: true,
},
);
</script>
<style lang="scss" scoped>
.smart-search {
display: flex;
justify-content: space-between;
&__form {
flex: 1;
width: 100%;
}
}
:deep(.el-date-editor) {
width: 100%;
}
:deep(.el-select) {
width: 100%;
}
:deep(.el-select-v2) {
width: 100%;
}
</style>
<template>
<div class="smart-table" style="height: 100%">
<el-table
ref="tableRef"
v-loading="loading"
border
highlight-current-row
:data="tableData"
:max-height="state.tableHeight"
:height="state.tableHeight"
v-bind="attrs"
:row-class-name="rowClass"
:default-expand-all="defaultExpandAll"
@select="handleSelect"
@select-all="handleSelectAll"
@selection-change="handleSelectionChange"
>
<!-- 多选 -->
<el-table-column v-if="selectionIsNeed" type="selection" min-width="55" align="center" />
<!-- 序号 -->
<el-table-column v-if="indexIsNeed" type="index" min-width="50" align="center" />
<!-- 表头 -->
<template v-for="column in columns" :key="column.prop">
<!-- 指定 slot 属性配置特殊列 -->
<el-table-column
v-if="column.slot"
:prop="column.prop"
:label="column.label"
:min-width="column.minWidth"
:align="column.align"
:fixed="column.fixed || false"
:show-overflow-tooltip="column.showTooltip || false"
>
<template #default="scope">
<slot v-if="column.slot" :name="column.slot" :scope="scope"></slot>
</template>
</el-table-column>
<!-- 指定 component 属性配置特殊列 -->
<component :is="column.component" v-else-if="column.component" :col-config="column" />
<!-- 普通配置列 -->
<el-table-column
v-else
:prop="column.prop"
:label="column.label"
:min-width="column.minWidth"
:align="column.align"
:fixed="column.fixed || false"
:show-overflow-tooltip="column.showTooltip || false"
/>
</template>
<!-- 操作 -->
<el-table-column
v-if="operationIsNeed"
label="操作"
fixed="right"
align="center"
:width="operationWidth"
>
<template #default="scope">
<slot name="operation" :scope="scope"></slot>
</template>
</el-table-column>
</el-table>
<div v-if="paginationIsNeed" class="pagination-wrapper">
<el-pagination
:current-page="currentPage"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, useAttrs, onMounted, onUnmounted, reactive, watch } from 'vue';
import { debounce } from '@/utils/tools';
import getTheme from '@/config/theme';
const { theme } = getTheme();
const props = withDefaults(
defineProps<{
columns: any[];
selectionIsNeed?: boolean;
indexIsNeed?: boolean;
operationIsNeed?: boolean;
operationWidth?: string;
tableData: any[];
paginationIsNeed?: boolean;
currentPage: number;
pageSize: number;
total: number;
loading: boolean;
defaultExpandAll?: boolean;
}>(),
{
columns: () => [],
selectionIsNeed: false,
indexIsNeed: true,
operationIsNeed: true,
operationWidth: '120',
tableData: () => [],
paginationIsNeed: false,
currentPage: 1,
pageSize: 10,
total: 0,
loading: false,
defaultExpandAll: false,
},
);
const state = reactive<{
tableHeight: number;
myObserver: any;
selectIndex: number[];
selectedArr: any[];
}>({
tableHeight: 0,
myObserver: null,
selectIndex: [],
selectedArr: [],
});
const attrs = useAttrs();
const emit = defineEmits(['size-change', 'current-change', 'selection-change']);
const tableRef = ref(null);
// 定义复选框选中行类名
const rowClass = ({ rowIndex }) => {
const arr = state.selectIndex;
if (arr.includes(rowIndex)) {
return 'current-row';
}
};
const handleSizeChange = (v: any) => emit('size-change', v);
const handleCurrentChange = (v: any) => emit('current-change', v);
const emitSelectionChange = (selected: any[]) => {
emit('selection-change', selected);
};
// @selection-change事件
const handleSelectionChange = (v: any[]) => {
// 储存选中行下标,用于复选框选中行样式
const ids = v.map((item) => item.id);
const arr: number[] = [];
props.tableData.forEach((item, index) => {
if (ids.includes(item.id)) {
arr.push(index);
}
});
state.selectIndex = arr;
// emit('selection-change', v);
};
// 改变行的选中状态
const toggleChildrenSelection = (row, selected) => {
const selectedIdsArr = state.selectedArr.map((item: any) => item.id);
if (row) {
(tableRef.value as any).toggleRowSelection(row, selected);
if (selected) {
if (!selectedIdsArr.includes(row.id)) {
state.selectedArr.push(row);
}
} else {
if (selectedIdsArr.includes(row.id)) {
const deleteIndex = selectedIdsArr.indexOf(row.id);
state.selectedArr.splice(deleteIndex, 1);
}
}
emitSelectionChange([...new Set(state.selectedArr)]);
}
};
// 递归遍历子节点
const handleDeepSelect = (row, isSelected) => {
toggleChildrenSelection(row, isSelected);
if (row.children && Array.isArray(row.children)) {
row.children.forEach((row) => {
handleDeepSelect(row, isSelected);
});
}
};
// @select事件
const handleSelect = (selection: any, row) => {
// 判断当前的row是否被选中
const isCurrentSelected = selection.some((el) => row.id === el.id);
if (isCurrentSelected) {
handleDeepSelect(row, true);
} else {
handleDeepSelect(row, false);
}
};
// @select-all事件
const handleSelectAll = (selection: any) => {
state.selectedArr = [];
const isSelect = selection.some((el) => {
const tableDataIds = props.tableData.map((j) => j.id);
return tableDataIds.includes(el.id);
});
const isCancel = !props.tableData.every((el) => {
const selectIds = selection.map((j) => j.id);
return selectIds.includes(el.id);
});
if (isSelect) {
selection.forEach((row) => {
handleDeepSelect(row, true);
});
}
if (isCancel) {
(tableRef.value as any).clearSelection();
emitSelectionChange([]);
}
};
const toggleSelection = (rows: any[]) => {
if (rows) {
rows.forEach((row) => {
(tableRef.value as any).toggleRowSelection(row);
});
} else {
(tableRef.value as any).clearSelection();
}
};
// 调整表格高度
const adjustTableHeight = () => {
let _layoutContent: any = document.getElementsByClassName('smart-table')[0].clientHeight;
state.tableHeight = _layoutContent - 50;
_layoutContent = null;
};
const resetTableState = () => {
// 清空表格的选中状态
state.selectedArr = [];
if (tableRef.value) {
(tableRef.value as any).clearSelection();
}
};
watch(
() => props.tableData,
() => {
resetTableState();
},
);
onMounted(() => {
resetTableState();
state.myObserver = new ResizeObserver(debounce(adjustTableHeight, 100));
state.myObserver.observe(document.getElementsByClassName('smart-table')[0]);
});
onUnmounted(() => {
state.myObserver.disconnect();
});
defineExpose({
toggleSelection,
});
</script>
<style scoped lang="scss">
/* stylelint-disable no-descending-specificity */
.pagination-wrapper {
display: flex;
align-items: center;
justify-content: flex-end;
margin: 10px 10px 0 0;
}
.el-pagination {
font-weight: normal;
}
:deep(.el-table) {
width: 99.5%; // 避免到达临界值,缩放时发生抖动
font-size: v-bind('theme.tableFontSize');
// 选中行样式
.el-table__body tr.current-row > td.el-table__cell {
color: v-bind('theme.primaryColor');
background-color: v-bind('theme.tableActiveRowBg');
.el-button--text {
color: v-bind('theme.primaryColor');
&:hover {
background-color: #ffffff;
}
&.danger {
color: v-bind('theme.tableDangerBtnColor');
}
}
}
// 悬停行样式(hover-row类用于控制fix的列)
.el-table__body tr:hover,
.el-table__body tr.hover-row {
& > td.el-table__cell,
& > td.el-table__cell .el-button--text {
color: v-bind('theme.primaryColor');
&.danger {
color: v-bind('theme.tableDangerBtnColor');
}
}
}
// 表头行样式
.el-table__header-wrapper .el-table__header th.el-table__cell,
.el-table__fixed-header-wrapper .el-table__header th.el-table__cell {
font-size: v-bind('theme.tableHeaderCellSize');
font-weight: normal;
color: v-bind('theme.tableHeaderCellColor');
background-color: v-bind('theme.tableHeaderCellBg');
}
// 操作按钮样式
.el-table__body tr > td.el-table__cell .el-button--small {
font-size: v-bind('theme.tableFontSize');
}
.el-table__body tr > td.el-table__cell .el-button--text {
padding: 2px 5px;
color: v-bind('theme.t2Color');
border-radius: 4px;
&:hover {
background-color: v-bind('theme.tableActiveRowBg');
}
}
}
</style>
import { toRefs, onMounted, reactive } from 'vue';
import type { AxiosResponse } from 'axios';
export interface AccountRequest {
pageNo: number;
pageSize: number;
[propName: string]: number | string;
}
export default function useRecordsSearch(
getAccountApi: (params: AccountRequest) => Promise<AxiosResponse>,
searchParmas: any = {},
): any {
const state = reactive({
data: [],
total: 0,
pageNo: 1,
pageSize: 20,
loading: false,
multipleSelection: [],
searchValue: {},
});
// 判断是否为空对象
const isEmpty = (obj: any) => Reflect.ownKeys(obj).length === 0 && obj.constructor === Object;
// 请求数据
const initData = async () => {
state.data = [];
state.loading = true;
const queryForm = {
pageNo: state.pageNo,
pageSize: state.pageSize,
...state.searchValue,
...(!isEmpty(searchParmas) && searchParmas.value),
};
try {
const res = await getAccountApi(queryForm);
state.data = res.data.records;
state.total = res.data.total;
} catch (err) {
} finally {
state.loading = false;
}
};
// 重置数据
const resetTableState = () => {
state.pageNo = 1;
};
// 检索数据
const handleSearch = (val: any) => {
state.searchValue = val;
resetTableState();
initData();
};
const handleSizeChange = (v: number) => {
(state.pageSize = v) && initData();
};
const handleCurrentChange = (v: number) => {
(state.pageNo = v) && initData();
};
const handleSelectionChange = (v: never[]) => {
state.multipleSelection = v;
};
onMounted(async () => {
isEmpty(searchParmas) && initData();
});
return {
...toRefs(state),
initData,
handleSizeChange,
handleCurrentChange,
handleSelectionChange,
handleSearch,
};
}
import { computed, reactive } from 'vue';
import { storeToRefs } from 'pinia';
import type { ITheme } from '@/types/theme';
import { useLayoutStore } from '@/store/modules/layout';
export default function getTheme(): { theme: ITheme } {
const { setting } = storeToRefs(useLayoutStore());
const headerBg = computed(() => setting.value.color.headerBg);
const primaryColor = computed(() => setting.value.color.primary);
const theme = reactive<ITheme>({
// 颜色
primaryColor,
t1Color: '#262626', // 标题色
t2Color: '#595959', // 文字内容色
t3Color: '#8c8c8c', // 文字说明色
t4Color: '#BFBFBF', // 文字占位色
s1Color: '#D9D9D9', // 边框色1
s2Color: '#E6E6E6', // 边框色2
d1Color: '#EBEBEB', // 分割线1
d2Color: '#F0F0F0', // 分割线2
d3Color: '#FAFAFA', // 分割线2
n1Color: '#666666', // 图标色1
n2Color: '#999999', // 图标色2
n3Color: '#CCCCCC', // 图标色2
// 头部样式
headerHeight: '48px',
headerBg,
headerColor: '#fff',
// 路由标签
tagsActiveColor: primaryColor,
tagsActiveBg: '#EFF5FF',
// 侧边栏
sidebarWidth: '200px',
sidebarColor: '#fff',
sidebarActiveBg: primaryColor,
menuInlineBg: '#FAFBFF',
menuItemHeight: '48px',
// 表格
tableActiveRowBg: '#eff5ff',
tableHeaderCellBg: '#fafafa',
tableHeaderCellSize: '14px',
tableHeaderCellColor: '#595959',
tableFontSize: '14px',
tableDangerBtnColor: '#f56c6c',
// 面包屑
breadcrumbFontSize: '12px',
});
return {
theme,
};
}
import type { App, DirectiveBinding } from 'vue';
import { fetchPermissionOperate } from '@/utils/tools';
const handleDOMDisabled = (el: Element) => {
const disabledDOM = (ele: Element) => {
ele.setAttribute('disabled', '');
ele.className = ele.className.split(' ').concat(['is-disabled']).join(' ').trimStart();
};
disabledDOM(el);
// 针对div内部嵌套button
if (el.tagName.toLowerCase() === 'div') {
[...el.getElementsByTagName('button')].forEach((btnEl) => {
disabledDOM(btnEl);
});
}
};
const actionPermission = (el: HTMLElement, binding: DirectiveBinding) => {
const [hasPermission, operate] = fetchPermissionOperate(binding.value);
// 根据 permission 判断是否显示
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el);
} else {
if (operate === 1) {
// 1 只读
handleDOMDisabled(el);
}
}
};
export default (app: App<Element>): void => {
app.directive('action', {
mounted: (el, binding) => {
actionPermission(el, binding);
},
});
};
import type { App, DirectiveBinding } from 'vue';
type Coordinate = [number, number];
type CoordinateRange = [number, number, number, number];
interface IOptions {
/** 拖拽范围元素 */
outerElement?: HTMLElement | string;
/** 可拖拽元素 */
dragElement?: HTMLElement | string;
/** 拖拽开始的回调 */
onDragStart?: (el: HTMLElement, v: Coordinate) => void;
/** 拖拽中的回调 */
onDrag?: (el: HTMLElement, v: Coordinate) => void;
/** 拖拽结束的回调 */
onDragEnd?: (el: HTMLElement, v: Coordinate) => void;
}
/**
* 计算坐标差
* @param oldCoordinate 旧坐标
* @param newCoordinate 当前坐标
*/
const diffCoordinate = (oldCoordinate: Coordinate, newCoordinate: Coordinate): Coordinate => {
const x = newCoordinate[0] - oldCoordinate[0];
const y = newCoordinate[1] - oldCoordinate[1];
return [x, y];
};
/**
* 获取拖拽位移
* @param vector 坐标差向量
* @param range 拖拽向量取值范围
*/
const getMoveVector = (vector: Coordinate, range: number[]): Coordinate => {
let [x, y] = vector;
y = y < 0 ? Math.max(y, range[0]) : Math.min(y, range[1]);
x = x < 0 ? Math.max(x, range[2]) : Math.min(x, range[3]);
return [x, y];
};
// 获取DOM
const getDom = (el: string | HTMLElement | undefined): HTMLElement | null => {
if (!el) return null;
if (typeof el === 'string') {
return document.querySelector(el);
} else {
return el;
}
};
/**
* 获取新transform属性
* @param transform 旧transform属性
* @param vector 偏移向量
*/
const getTranslatePosition = (transform: string, vector: Coordinate): string =>
`translate3d(${vector[0]}px, ${vector[1]}px, 0px) ${transform.replace('none', '')}`;
let onMouseMove: (e: MouseEvent) => void;
let onMouseUp: (e: MouseEvent) => void;
let onMouseDown: (e: MouseEvent) => void;
export default (app: App<Element>): void => {
app.directive('drag', {
mounted: (el: HTMLElement, binding: DirectiveBinding<IOptions>) => {
const { onDragStart, onDrag, onDragEnd, dragElement, outerElement } = binding.value ?? {};
const dragRangeElement: HTMLElement = getDom(outerElement) ?? document.body; // 拖拽范围元素
const myDragElement: HTMLElement = getDom(dragElement) ?? el; // 可拖拽的元素
myDragElement.style.cursor = 'move';
myDragElement.style.userSelect = 'none';
let startPosition: Coordinate | null = null; // 开始拖拽位置
let draggingMoveVectorRange: CoordinateRange | null = null; // 可拖拽位移范围
let startTransform = ''; // 初始transform值
onMouseDown = (e: MouseEvent) => {
startPosition = [e.pageX, e.pageY];
startTransform = window.getComputedStyle(el).transform;
if (dragRangeElement && myDragElement) {
// 记录可拖拽位移范围
const dragRangeElementRect = dragRangeElement.getBoundingClientRect();
const myDragElementRect = el.getBoundingClientRect();
draggingMoveVectorRange = [
dragRangeElementRect.top - myDragElementRect.top,
dragRangeElementRect.bottom - myDragElementRect.bottom,
dragRangeElementRect.left - myDragElementRect.left,
dragRangeElementRect.right - myDragElementRect.right,
];
typeof onDragStart === 'function' && onDragStart(el, startPosition);
}
};
onMouseMove = (e: MouseEvent) => {
if (startPosition && draggingMoveVectorRange) {
const currentPosition: Coordinate = [e.pageX, e.pageY];
// 本次的拖拽位移
const currentMoveVector = getMoveVector(
diffCoordinate(startPosition, currentPosition),
draggingMoveVectorRange,
);
// 赋新transform属性
el.style.transform = getTranslatePosition(startTransform, currentMoveVector);
typeof onDrag === 'function' && onDrag(el, currentPosition);
}
};
onMouseUp = (e: MouseEvent) => {
typeof onDragEnd === 'function' && onDragEnd(el, [e.pageX, e.pageY]);
startPosition = null;
};
const addEventListener = () => {
if (myDragElement) {
myDragElement.addEventListener('mousedown', onMouseDown);
document.addEventListener('mousemove', onMouseMove);
document.addEventListener('mouseup', onMouseUp);
}
};
addEventListener();
},
beforeUnmount: (el: HTMLElement) => {
el.removeEventListener('mousedown', onMouseDown);
document.removeEventListener('mousemove', onMouseMove);
document.removeEventListener('mouseup', onMouseUp);
},
});
};
import type { App } from 'vue';
const modules = import.meta.glob('../directive/**/**.ts');
// 自动导入当前文件夹下的所有自定义指令(默认导出项)
export default (app: App<Element>): void => {
Object.keys(modules).forEach((path) => {
// 排除当前文件
if (path !== '../directive/index.ts') {
modules[path]().then((mod) => {
mod.default(app);
});
}
});
};
/// <reference types="vite/client" />
declare module '*.vue' {
import { DefineComponent } from 'vue';
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>;
export default component;
}
declare interface Fn<T = any, R = T> {
(...arg: T[]): R;
}
import { ref, watch } from 'vue';
import { tryOnUnmounted } from '@vueuse/core';
import type { TimeoutHandle, Fn } from '@/types/global';
export function isFunction(val: unknown): val is Function {
return typeof val === 'function';
}
export function useTimeoutFn(handle: Fn<any>, wait: number, native = false) {
if (!isFunction(handle)) {
throw new Error('handle is not Function!');
}
const { readyRef, stop, start } = useTimeoutRef(wait);
if (native) {
handle();
} else {
watch(
readyRef,
(maturity) => {
maturity && handle();
},
{ immediate: false },
);
}
return { readyRef, stop, start };
}
export function useTimeoutRef(wait: number) {
const readyRef = ref(false);
let timer: TimeoutHandle;
function stop(): void {
readyRef.value = false;
timer && window.clearTimeout(timer);
}
function start(): void {
stop();
timer = setTimeout(() => {
readyRef.value = true;
}, wait);
}
start();
tryOnUnmounted(stop);
return { readyRef, stop, start };
}
<template>
<router-view />
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'LayoutBlank',
setup() {
return {};
},
});
</script>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
<template>
<el-scrollbar wrap-style="display:flex;" view-style="flex:1;">
<router-view v-slot="{ Component }">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="getSetting.showTabs ? getTabs.cachedViews : []">
<component :is="Component" :key="key" />
</keep-alive>
</transition>
</router-view>
</el-scrollbar>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import { useLayoutStore } from '@/store/modules/layout';
const { getSetting, getTabs } = useLayoutStore();
const route = useRoute();
const key = computed(() => route.path);
</script>
<template>
<div class="logo">
<div class="icon">
<el-avatar :size="38" :src="systemLogo" />
</div>
<div class="title">
<span class="main-title">{{ systemMainTitle }}</span>
<span class="sub-title">{{ systemSubTitle }}</span>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { storeToRefs } from 'pinia';
import getTheme from '@/config/theme';
import { useSystemStore } from '@/store/modules/system';
const { theme } = getTheme();
const { getSystemImage, getSystemName, getSystemSubTitle } = storeToRefs(useSystemStore());
const systemMainTitle = computed(() =>
getSystemName.value ? getSystemName.value : 'SmartWeb for SouthZN',
);
const systemSubTitle = computed(() =>
getSystemSubTitle.value ? getSystemSubTitle.value : 'This is a sub-title',
);
const systemLogo = computed(() => (getSystemImage.value ? getSystemImage.value.url : ''));
</script>
<style lang="scss" scoped>
.logo {
display: flex;
height: 100%;
padding-right: 30px;
.icon {
display: flex;
align-items: center;
justify-content: center;
width: v-bind('theme.headerHeight');
height: 100%;
margin: 0 10px;
}
.title {
display: flex;
flex-direction: column;
padding-bottom: 4px;
.main-title {
display: flex;
flex: 1;
align-items: center;
font-size: 1.1rem;
color: v-bind('theme.headerColor');
letter-spacing: 1px;
}
.sub-title {
display: flex;
align-items: center;
font-size: 0.7rem;
color: v-bind('theme.s2Color');
letter-spacing: 0.5px;
}
}
}
</style>
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
import { createPinia } from 'pinia';
export const pinia = createPinia();
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment