初始化
|
@ -0,0 +1,19 @@
|
|||
VITE_APP_NAME=多元警情
|
||||
VITE_APP_ENV=development
|
||||
VITE_APP_PORT=9527
|
||||
VITE_DROP_CONSOLE=false
|
||||
|
||||
# axios http://172.10.10.203:8083
|
||||
VITE_APP_BASE_API=/api
|
||||
VITE_APP_PROXY_URL=http://175.6.124.250:8083
|
||||
|
||||
#crypto js 前后端需保持一致
|
||||
VITE_APP_CRYPTO_JS_SECRET_KEY=f0234d57c311beb2
|
||||
VITE_APP_CRYPTO_JS_SECRET_IV=eb7905b31669ad1e
|
||||
|
||||
#高德b886c3f67152803e081f0b0f5594a55e
|
||||
VITE_APP_GAODE_KEY=b886c3f67152803e081f0b0f5594a55e
|
||||
VITE_APP_GAODE_VERSION=2.0
|
||||
|
||||
VITE_APP_WS_API=/wscts
|
||||
VITE_APP_WS_PROXY_URL=ws://175.6.124.250:8083
|
|
@ -0,0 +1,19 @@
|
|||
VITE_APP_NAME=长沪
|
||||
VITE_APP_ENV=production
|
||||
VITE_APP_PORT=9528
|
||||
VITE_DROP_CONSOLE=true
|
||||
|
||||
# axios
|
||||
VITE_APP_BASE_API=/api
|
||||
VITE_APP_PROXY_URL=http://175.6.124.250:8083
|
||||
|
||||
#crypto js 前后端需保持一致
|
||||
VITE_APP_CRYPTO_JS_SECRET_KEY=f0234d57c311beb2
|
||||
VITE_APP_CRYPTO_JS_SECRET_IV=eb7905b31669ad1e
|
||||
|
||||
#高德
|
||||
VITE_APP_GAODE_KEY=b886c3f67152803e081f0b0f5594a55e
|
||||
VITE_APP_GAODE_VERSION=2.0
|
||||
|
||||
VITE_APP_WS_API=/wssct
|
||||
VITE_APP_WS_PROXY_URL=ws://175.6.124.250:8083
|
|
@ -0,0 +1,50 @@
|
|||
### IntelliJ IDEA ###
|
||||
HELP.md
|
||||
target/
|
||||
.idea/
|
||||
!.mvn/wrapper/maven-wrapper.jar
|
||||
!**/src/main/**/target/
|
||||
!**/src/test/**/target/
|
||||
*.iws
|
||||
*.iml
|
||||
*.ipr
|
||||
|
||||
### VS Code ###
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
|
||||
### Eclipse ###
|
||||
.apt_generated
|
||||
.classpath
|
||||
.factorypath
|
||||
.project
|
||||
.settings
|
||||
.springBeans
|
||||
.sts4-cache
|
||||
|
||||
### Mac OS ###
|
||||
.DS_Store
|
||||
|
||||
### 自定义文件夹 ###
|
||||
logs/
|
||||
.hbuilderx
|
||||
work/
|
||||
*.zip
|
||||
*.war
|
||||
*.jar
|
||||
*.rar
|
77
README.md
|
@ -1,3 +1,74 @@
|
|||
# multiple-police-situations
|
||||
2
|
||||
多元警情web
|
||||
# 长沪信息[云控系统后台页面]
|
||||
|
||||
> 项目基于 vue3x AntDesignVue4x VueRouter4x pinia的后台管理前端框架
|
||||
|
||||
[TOC]
|
||||
|
||||
## 1. 项目安装
|
||||
|
||||
```bash
|
||||
# 依赖下载
|
||||
yarn or npm i
|
||||
# 开发环境运行
|
||||
yarn dev
|
||||
# 生产构建
|
||||
yarn build
|
||||
# 预览
|
||||
yarn preview
|
||||
```
|
||||
|
||||
## 2. 代码结构规范
|
||||
|
||||
```bash
|
||||
|--analyze.html 打包依赖分析
|
||||
|--components.d.ts antdesign组件自动导入
|
||||
|--package.json 项目依赖管理
|
||||
|--tsconfig.json ts配置
|
||||
|--vite.config.ts vite配置
|
||||
|--src 根目录
|
||||
|--assets 静态资源文件 不参与打包
|
||||
|--axios axios封装
|
||||
|--components 全局公用组件封装
|
||||
|--codemirror 代码编辑器
|
||||
|--form antDesign表单二次封装
|
||||
|--table antDesign表格二次封装
|
||||
|--configs 全局的一些配置文件
|
||||
|--directives 自定义指令
|
||||
|--hooks hooks
|
||||
|--router router
|
||||
|--modules--dynamicRouters.ts 动态路由
|
||||
|--modules--staticRouters.ts 静态路由
|
||||
|--stores pinia
|
||||
|--utils utils
|
||||
|--views 页面存放位置
|
||||
|--global.d.ts 全局的类型声明
|
||||
|--.env.development 开发环境参数配置
|
||||
|--.env.production 生产构建参数配置
|
||||
```
|
||||
|
||||
## 3. 注意
|
||||
|
||||
- 项目中所有公共组件使用首字母大写命名
|
||||
|
||||
> 例如 FormPro TablePro
|
||||
|
||||
- 项目中的页面使用驼峰命名
|
||||
|
||||
> 例如 userManage roleManage
|
||||
|
||||
- 在使用动态路由的时候,需要严格遵守指定规则,否则不会生效
|
||||
|
||||
```tex
|
||||
项目默认扫描views下的所有.vue的文件
|
||||
例如:需要配置一个开发管理(dev)=》用户管理(userManage)
|
||||
开发管理为目录
|
||||
用户管理为菜单
|
||||
则path为/dev/userManage 不需要加上views这一层 也不需要以.vue结尾
|
||||
```
|
||||
|
||||
- 项目严重不推荐在开发过程中产生的红色错误或者黄色警告 如有 请自行解决
|
||||
|
||||
- 代码规范整洁
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/* eslint-disable */
|
||||
/* prettier-ignore */
|
||||
// @ts-nocheck
|
||||
// Generated by unplugin-vue-components
|
||||
// Read more: https://github.com/vuejs/core/pull/3399
|
||||
export {}
|
||||
|
||||
declare module 'vue' {
|
||||
export interface GlobalComponents {
|
||||
404: typeof import('./src/components/errorMessage/404.vue')['default']
|
||||
500: typeof import('./src/components/errorMessage/500.vue')['default']
|
||||
AButton: typeof import('ant-design-vue/es')['Button']
|
||||
AConfigProvider: typeof import('ant-design-vue/es')['ConfigProvider']
|
||||
ADropdown: typeof import('ant-design-vue/es')['Dropdown']
|
||||
AForm: typeof import('ant-design-vue/es')['Form']
|
||||
AFormItem: typeof import('ant-design-vue/es')['FormItem']
|
||||
AImage: typeof import('ant-design-vue/es')['Image']
|
||||
AInput: typeof import('ant-design-vue/es')['Input']
|
||||
AInputPassword: typeof import('ant-design-vue/es')['InputPassword']
|
||||
AMenu: typeof import('ant-design-vue/es')['Menu']
|
||||
AMenuItem: typeof import('ant-design-vue/es')['MenuItem']
|
||||
ASegmented: typeof import('ant-design-vue/es')['Segmented']
|
||||
ASpin: typeof import('ant-design-vue/es')['Spin']
|
||||
AStatistic: typeof import('ant-design-vue/es')['Statistic']
|
||||
ATimeline: typeof import('ant-design-vue/es')['Timeline']
|
||||
ATimelineItem: typeof import('ant-design-vue/es')['TimelineItem']
|
||||
ATree: typeof import('ant-design-vue/es')['Tree']
|
||||
ATreeSelect: typeof import('ant-design-vue/es')['TreeSelect']
|
||||
CodemirrorPro: typeof import('./src/components/codemirror/CodemirrorPro.vue')['default']
|
||||
Digitalscroll: typeof import('./src/components/index/digitalscroll.vue')['default']
|
||||
Eldrawertransition: typeof import('./src/components/eldrawertransition.vue')['default']
|
||||
FormPro: typeof import('./src/components/form/FormPro.vue')['default']
|
||||
FullScreenContainer: typeof import('./src/components/fullScreenContainer.vue')['default']
|
||||
IconFont: typeof import('./src/components/iconfont/IconFont.vue')['default']
|
||||
RouterLink: typeof import('vue-router')['RouterLink']
|
||||
RouterView: typeof import('vue-router')['RouterView']
|
||||
SelectIcon: typeof import('./src/components/selecticon/SelectIcon.vue')['default']
|
||||
TablePro: typeof import('./src/components/table/TablePro.vue')['default']
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<!-- <meta http-equiv="Content-Security-Policy" content="upgrade-insecure-requests"> -->
|
||||
<link rel="icon" type="image/svg+xml" href="/gw.png" style="width:40px;height:40px;" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>多元警情</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script src="https://webapi.amap.com/maps?v=1.4.15&key=8910226d8d36a41d856262b1a588850c&plugin=AMap.MarkerClusterer"></script>
|
||||
<script src="./jquery-3.4.1.min.js"></script>
|
||||
<script src="./h5player.min.js"></script>
|
||||
<script type="module" src="/src/main.ts">
|
||||
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,77 @@
|
|||
{
|
||||
"name": "web",
|
||||
"private": true,
|
||||
"version": "1.0.0",
|
||||
"appName": "云控后台管理系统",
|
||||
"type": "module",
|
||||
"description": "这是云控后台管理系统",
|
||||
"author": {
|
||||
"name": "罗准",
|
||||
"email": "2025254074@qq.com"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite --mode development",
|
||||
"pro": "vite --mode production",
|
||||
"build": "vite build --mode production",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@amap/amap-jsapi-loader": "^1.0.1",
|
||||
"@amap/amap-jsapi-types": "^0.0.13",
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@codemirror/lang-java": "^6.0.1",
|
||||
"@codemirror/lang-json": "^6.0.1",
|
||||
"@codemirror/lang-sql": "^6.5.4",
|
||||
"@codemirror/theme-one-dark": "^6.1.2",
|
||||
"@element-plus/icons-vue": "^2.3.1",
|
||||
"@jiaminghi/data-view": "^2.10.0",
|
||||
"@kjgl77/datav-vue3": "^1.7.2",
|
||||
"@types/node": "^20.5.0",
|
||||
"@vitejs/plugin-vue-jsx": "^3.0.1",
|
||||
"@vuemap/vue-amap": "^2.0.24",
|
||||
"@vueuse/core": "^10.4.1",
|
||||
"animate.css": "^4.1.1",
|
||||
"ant-design-vue": "4.x",
|
||||
"autofit.js": "^3.1.0",
|
||||
"axios": "^1.4.0",
|
||||
"codemirror": "^6.0.1",
|
||||
"crypto-js": "^4.1.1",
|
||||
"dayjs": "^1.11.10",
|
||||
"echarts": "^5.4.3",
|
||||
"echarts-gl": "^2.0.9",
|
||||
"element-china-area-data": "^6.1.0",
|
||||
"element-plus": "^2.4.2",
|
||||
"element-ui": "^2.15.14",
|
||||
"js-beautify": "^1.14.9",
|
||||
"js-md5": "^0.8.3",
|
||||
"jwt-decode": "^3.1.2",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"nprogress": "^0.2.0",
|
||||
"pinia": "^2.1.6",
|
||||
"pinia-plugin-persistedstate": "^3.2.0",
|
||||
"terser": "^5.19.2",
|
||||
"v-scale-screen": "^2.2.0",
|
||||
"vue": "^3.3.4",
|
||||
"vue-codemirror": "^6.1.1",
|
||||
"vue-router": "4",
|
||||
"vue-seamless-scroll": "^1.1.23",
|
||||
"vue-uuid": "^3.0.0",
|
||||
"vue3-scale-box": "^0.1.9",
|
||||
"vue3-seamless-scroll": "^2.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.1.1",
|
||||
"@types/js-beautify": "^1.14.1",
|
||||
"@types/lodash-es": "^4.17.8",
|
||||
"@types/nprogress": "^0.2.1",
|
||||
"@vitejs/plugin-vue": "^4.2.3",
|
||||
"less": "^4.2.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"sass": "^1.65.1",
|
||||
"typescript": "^4.9.3",
|
||||
"unplugin-vue-components": "^0.25.2",
|
||||
"vite": "^4.4.5",
|
||||
"vue-tsc": "^1.8.5"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,225 @@
|
|||
/**
|
||||
* Created by wangweijie5 on 2016/12/16.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
|
||||
|
||||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
|
||||
|
||||
var __instance = function () {
|
||||
var instance = void 0;
|
||||
return function (newInstance) {
|
||||
if (newInstance) instance = newInstance;
|
||||
return instance;
|
||||
};
|
||||
}();
|
||||
|
||||
var AudioRenderer = function () {
|
||||
function AudioRenderer() {
|
||||
_classCallCheck(this, AudioRenderer);
|
||||
|
||||
if (__instance()) return __instance();
|
||||
|
||||
// 确保只有单例
|
||||
if (AudioRenderer.unique !== undefined) {
|
||||
return AudioRenderer.unique;
|
||||
}
|
||||
|
||||
AudioRenderer.unique = this;
|
||||
|
||||
this.oAudioContext = null;
|
||||
this.currentVolume = 80; // 初始音量
|
||||
this.bSetVolume = false;
|
||||
this.gainNode = null;
|
||||
this.iWndNum = -1; // 窗口号
|
||||
this.mVolumes = new Map(); // 用于存储所有音量
|
||||
|
||||
// Init AudioContext
|
||||
var AudioContext = window.AudioContext || window.webkitAudioContext;
|
||||
this.oAudioContext = new AudioContext();
|
||||
|
||||
this.writeString = function (view, offset, string) {
|
||||
for (var i = 0; i < string.length; i++) {
|
||||
view.setUint8(offset + i, string.charCodeAt(i));
|
||||
}
|
||||
};
|
||||
|
||||
this.setBufferToDataview = function (output, offset, input) {
|
||||
for (var i = 0; i < input.length; i++, offset++) {
|
||||
output.setUint8(offset, input[i]);
|
||||
}
|
||||
};
|
||||
|
||||
__instance(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* @synopsis 音频播放
|
||||
*
|
||||
* @param dataBuf [IN] 音频缓存
|
||||
* @param dataLen [IN] 缓存长度
|
||||
* @param audioInfo [IN] 音频参数
|
||||
*
|
||||
* @returns 状态码
|
||||
*/
|
||||
|
||||
|
||||
_createClass(AudioRenderer, [{
|
||||
key: 'Play',
|
||||
value: function Play(dataBuf, dataLen, audioInfo) {
|
||||
var bufferData = new ArrayBuffer(44 + dataLen);
|
||||
var viewTalk = new DataView(bufferData);
|
||||
var sampleRates = audioInfo.samplesPerSec;
|
||||
var channels = audioInfo.channels;
|
||||
var bitsPerSample = audioInfo.bitsPerSample;
|
||||
|
||||
//console.log("audiorender sampleRates"+sampleRates+"channels:"+channels+"bitsPerSample:"+bitsPerSample);
|
||||
|
||||
/* RIFF identifier */
|
||||
this.writeString(viewTalk, 0, 'RIFF');
|
||||
/* file length */
|
||||
viewTalk.setUint32(4, 32 + dataLen * 2, true);
|
||||
/* RIFF type */
|
||||
this.writeString(viewTalk, 8, 'WAVE');
|
||||
/* format chunk identifier */
|
||||
this.writeString(viewTalk, 12, 'fmt ');
|
||||
/* format chunk length */
|
||||
viewTalk.setUint32(16, 16, true);
|
||||
/* sample format (raw) */
|
||||
viewTalk.setUint16(20, 1, true);
|
||||
/* channel count */
|
||||
viewTalk.setUint16(22, channels, true);
|
||||
/* sample rate */
|
||||
viewTalk.setUint32(24, sampleRates, true);
|
||||
/* byte rate (sample rate * block align) */
|
||||
viewTalk.setUint32(28, sampleRates * 2, true);
|
||||
/* block align (channel count * bytes per sample)/8 */
|
||||
viewTalk.setUint16(32, channels * bitsPerSample / 8, true);
|
||||
/* bits per sample */
|
||||
viewTalk.setUint16(34, bitsPerSample, true);
|
||||
/* data chunk identifier */
|
||||
this.writeString(viewTalk, 36, 'data');
|
||||
/* data chunk length */
|
||||
viewTalk.setUint32(40, dataLen, true);
|
||||
this.setBufferToDataview(viewTalk, 44, dataBuf);
|
||||
|
||||
var self = this;
|
||||
this.oAudioContext.decodeAudioData(viewTalk.buffer, function (buffer) {
|
||||
|
||||
var bufferSource = self.oAudioContext.createBufferSource();
|
||||
if (bufferSource == null) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
bufferSource.buffer = buffer;
|
||||
bufferSource.start(0);
|
||||
|
||||
if (self.gainNode == null || self.bSetVolume) {
|
||||
self.gainNode = self.oAudioContext.createGain();
|
||||
// self.gainNode.gain.value = self.currentVolume;
|
||||
// // self.currentVolume = self.gainNode.gain.value;
|
||||
// self.gainNode.connect(self.oAudioContext.destination);
|
||||
|
||||
self.bSetVolume = false;
|
||||
}
|
||||
|
||||
self.gainNode.gain.value = self.currentVolume/100;
|
||||
// self.currentVolume = self.gainNode.gain.value;
|
||||
self.gainNode.connect(self.oAudioContext.destination);
|
||||
|
||||
bufferSource.connect(self.gainNode);
|
||||
}, function (e) {
|
||||
console.log("decode error");
|
||||
return -1;
|
||||
});
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @synopsis 停止播放
|
||||
*
|
||||
* @returns 返回音量
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'Stop',
|
||||
value: function Stop() {
|
||||
if (this.gainNode != null) {
|
||||
this.gainNode.disconnect();
|
||||
this.gainNode = null;
|
||||
}
|
||||
|
||||
// this.oAudioContext.close();
|
||||
|
||||
// AudioRenderer.unique = undefined;
|
||||
// __instance() = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @synopsis 设置音量
|
||||
*
|
||||
* @param iVolume [IN] 音量
|
||||
*
|
||||
* @returns 状态码
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'SetVolume',
|
||||
value: function SetVolume(iVolume) {
|
||||
this.bSetVolume = true;
|
||||
this.currentVolume = iVolume;
|
||||
|
||||
// 储存当前窗口设置音量值
|
||||
this.mVolumes.set(this.iWndNum, this.currentVolume);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @synopsis 设置窗口号
|
||||
*
|
||||
* @param iWndNum [IN] 窗口号
|
||||
*
|
||||
* @returns 状态码
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'SetWndNum',
|
||||
value: function SetWndNum(iWndNum) {
|
||||
this.iWndNum = iWndNum;
|
||||
|
||||
// 获取当前窗口设置音量值
|
||||
var iVolume = this.mVolumes.get(iWndNum);
|
||||
if (iVolume == undefined) {
|
||||
iVolume = 80; // 默认音量
|
||||
}
|
||||
this.currentVolume = iVolume;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @synopsis 获取音量
|
||||
*
|
||||
* @returns 返回音量
|
||||
*/
|
||||
|
||||
}, {
|
||||
key: 'GetVolume',
|
||||
value: function GetVolume() {
|
||||
// 获取当前窗口设置音量值
|
||||
var iVolume = this.mVolumes.get(this.iWndNum);
|
||||
if (iVolume == undefined) {
|
||||
iVolume = 80; // 默认音量
|
||||
}
|
||||
|
||||
return iVolume;
|
||||
}
|
||||
}]);
|
||||
|
||||
return AudioRenderer;
|
||||
}();
|
||||
//# sourceMappingURL=AudioRenderer.js.map
|
|
@ -0,0 +1,621 @@
|
|||
/**
|
||||
* Created by wangweijie5 on 2016/12/5.
|
||||
*/
|
||||
(function (event) {
|
||||
const AUDIO_TYPE = 0; // 音频
|
||||
const VIDEO_TYPE = 1; // 视频
|
||||
const PRIVT_TYPE = 2; // 私有帧
|
||||
|
||||
const PLAYM4_AUDIO_FRAME = 100; // 音频帧
|
||||
const PLAYM4_VIDEO_FRAME = 101; // 视频帧
|
||||
|
||||
const PLAYM4_OK = 1;
|
||||
const PLAYM4_ORDER_ERROR = 2;
|
||||
const PLAYM4_DECODE_ERROR = 44 // 解码失败
|
||||
const PLAYM4_NOT_KEYFRAME = 48; // 非关键帧
|
||||
const PLAYM4_NEED_MORE_DATA = 31; // 需要更多数据才能解析
|
||||
const PLAYM4_NEED_NEET_LOOP = 35; //丢帧需要下个循环
|
||||
const PLAYM4_SYS_NOT_SUPPORT = 16; // 不支持
|
||||
|
||||
importScripts('Decoder.js');
|
||||
Module.addOnPostRun(function () {
|
||||
postMessage({'function': "loaded"});
|
||||
});
|
||||
|
||||
var iStreamMode = 0; // 流模式
|
||||
|
||||
var bOpenMode = false;
|
||||
var bOpenStream = false;
|
||||
|
||||
var funGetFrameData = null;
|
||||
var funGetAudFrameData = null;
|
||||
|
||||
var bWorkerPrintLog=false;//worker层log开关
|
||||
|
||||
var g_nPort = -1;
|
||||
|
||||
onmessage = function (event)
|
||||
{
|
||||
var eventData = event.data;
|
||||
var res = 0;
|
||||
switch (eventData.command)
|
||||
{
|
||||
case "printLog":
|
||||
let downloadFlag=eventData.data;
|
||||
if(downloadFlag===true)
|
||||
{
|
||||
bWorkerPrintLog=true;
|
||||
res = Module._SetPrintLogFlag(g_nPort,downloadFlag);
|
||||
}
|
||||
else
|
||||
{
|
||||
bWorkerPrintLog=false;
|
||||
res = Module._SetPrintLogFlag(g_nPort,downloadFlag);
|
||||
}
|
||||
|
||||
if (res !== PLAYM4_OK)
|
||||
{
|
||||
console.log("DecodeWorker.js: PlayerSDK print log failed,res"+res);
|
||||
postMessage({'function': "printLog", 'errorCode': res});
|
||||
}
|
||||
break;
|
||||
case "SetPlayPosition":
|
||||
let nFrameNumOrTime=eventData.data;
|
||||
let enPosType=eventData.type;
|
||||
// res = Module._SetPlayPosition(nFrameNumOrTime,enPosType);
|
||||
// if (res !== PLAYM4_OK)
|
||||
// {
|
||||
// postMessage({'function': "SetPlayPosition", 'errorCode': res});
|
||||
// return;
|
||||
// }
|
||||
// //有没有buffer需要清除
|
||||
|
||||
break;
|
||||
case "SetStreamOpenMode":
|
||||
//获取端口号
|
||||
g_nPort = Module._GetPort();
|
||||
//设置流打开模式
|
||||
iStreamMode = eventData.data;
|
||||
res = Module._SetStreamOpenMode(g_nPort,iStreamMode);
|
||||
if (res !== PLAYM4_OK)
|
||||
{
|
||||
postMessage({'function': "SetStreamOpenMode", 'errorCode': res});
|
||||
return;
|
||||
}
|
||||
bOpenMode = true;
|
||||
break;
|
||||
|
||||
case "OpenStream":
|
||||
// 接收到的数据
|
||||
var iHeadLen = eventData.dataSize;
|
||||
var pHead = Module._malloc(iHeadLen + 4);
|
||||
if (pHead === null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var aHead = Module.HEAPU8.subarray(pHead, pHead + iHeadLen);
|
||||
aHead.set(eventData.data);
|
||||
res = Module._OpenStream(g_nPort,pHead, iHeadLen, eventData.bufPoolSize);
|
||||
postMessage({'function': "OpenStream", 'errorCode': res});
|
||||
if (res !== PLAYM4_OK)
|
||||
{
|
||||
//释放内存
|
||||
Module._free(pHead);
|
||||
pHead = null;
|
||||
return;
|
||||
}
|
||||
bOpenStream = true;
|
||||
break;
|
||||
case "Play":
|
||||
let resP = Module._Play(g_nPort);
|
||||
if (resP !== PLAYM4_OK)
|
||||
{
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "InputData":
|
||||
// 接收到的数据
|
||||
var iLen = eventData.dataSize;
|
||||
if (iLen > 0)
|
||||
{
|
||||
var pInputData = Module._malloc(iLen);
|
||||
if (pInputData === null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
var inputData = new Uint8Array(eventData.data);
|
||||
// var aInputData = Module.HEAPU8.subarray(pInputData, pInputData + iLen);
|
||||
// aInputData.set(inputData);
|
||||
Module.writeArrayToMemory(inputData, pInputData);
|
||||
inputData = null;
|
||||
res = Module._InputData(g_nPort,pInputData, iLen);
|
||||
if (res !== PLAYM4_OK)
|
||||
{
|
||||
let errorCode = Module._GetLastError(g_nPort);
|
||||
let sourceRemain = Module._GetSourceBufferRemain(g_nPort);
|
||||
postMessage({'function': "InputData", 'errorCode': errorCode,"sourceRemain":sourceRemain});
|
||||
}
|
||||
Module._free(pInputData);
|
||||
pInputData = null;
|
||||
}else{
|
||||
let sourceRemain = Module._GetSourceBufferRemain(g_nPort);
|
||||
if(sourceRemain == 0)
|
||||
{
|
||||
console.log("C buffer and JS buffer size is both 0");
|
||||
postMessage({'function': "InputData", 'errorCode':PLAYM4_NEED_MORE_DATA});
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/////////////////////
|
||||
if (funGetFrameData === null)
|
||||
{
|
||||
funGetFrameData = Module.cwrap('GetFrameData', 'number');
|
||||
}
|
||||
|
||||
while (bOpenMode && bOpenStream)
|
||||
{
|
||||
|
||||
var ret = getFrameData(funGetFrameData);
|
||||
// 直到获取视频帧或数据不足为止
|
||||
if (PLAYM4_VIDEO_FRAME === ret ||PLAYM4_NEED_MORE_DATA === ret || PLAYM4_ORDER_ERROR === ret || PLAYM4_NEED_NEET_LOOP ===ret)//PLAYM4_VIDEO_FRAME === ret ||
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case "SetSecretKey":
|
||||
var keyLen = eventData.nKeyLen;
|
||||
var pKeyData = Module._malloc(keyLen);
|
||||
if (pKeyData === null) {
|
||||
return;
|
||||
}
|
||||
var nKeySize = eventData.data.length
|
||||
var bufData = stringToBytes (eventData.data);
|
||||
var aKeyData = Module.HEAPU8.subarray(pKeyData, pKeyData + keyLen);
|
||||
aKeyData.set(new Uint8Array(bufData));
|
||||
|
||||
res = Module._SetSecretKey(g_nPort,eventData.nKeyType, pKeyData, keyLen);//, nKeySize
|
||||
if (res !== PLAYM4_OK) {
|
||||
postMessage({'function': "SetSecretKey", 'errorCode': res});
|
||||
Module._free(pKeyData);
|
||||
pKeyData = null;
|
||||
return;
|
||||
}
|
||||
|
||||
Module._free(pKeyData);
|
||||
pKeyData = null;
|
||||
break;
|
||||
|
||||
case "GetBMP":
|
||||
var nBMPWidth = eventData.width;
|
||||
var nBMPHeight = eventData.height;
|
||||
var pYUVData = eventData.data;
|
||||
var nYUVSize = nBMPWidth * nBMPHeight * 3 / 2;
|
||||
var oBMPCropRect = eventData.rect;
|
||||
|
||||
var pDataYUV = Module._malloc(nYUVSize);
|
||||
if (pDataYUV === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Module.writeArrayToMemory(new Uint8Array(pYUVData, 0, nYUVSize), pDataYUV);
|
||||
|
||||
// 分配BMP空间
|
||||
var nBmpSize = nBMPWidth * nBMPHeight * 4 + 60;
|
||||
var pBmpData = Module._malloc(nBmpSize);
|
||||
var pBmpSize = Module._malloc(4);
|
||||
if (pBmpData === null || pBmpSize === null) {
|
||||
Module._free(pDataYUV);
|
||||
pDataYUV = null;
|
||||
|
||||
if (pBmpData != null) {
|
||||
Module._free(pBmpData);
|
||||
pBmpData = null;
|
||||
}
|
||||
|
||||
if (pBmpSize != null) {
|
||||
Module._free(pBmpSize);
|
||||
pBmpSize = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
//Module._memset(pBmpSize, nBmpSize, 4); // 防止bmp截图出现输入数据过大的错误码
|
||||
Module.setValue(pBmpSize, nBmpSize, "i32");
|
||||
res = Module._GetBMP(g_nPort,pDataYUV, nYUVSize, pBmpData, pBmpSize,
|
||||
oBMPCropRect.left, oBMPCropRect.top, oBMPCropRect.right, oBMPCropRect.bottom);
|
||||
if (res !== PLAYM4_OK) {
|
||||
postMessage({'function': "GetBMP", 'errorCode': res});
|
||||
Module._free(pDataYUV);
|
||||
pDataYUV = null;
|
||||
Module._free(pBmpData);
|
||||
pBmpData = null;
|
||||
Module._free(pBmpSize);
|
||||
pBmpSize = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取BMP图片大小
|
||||
var nBmpDataSize = Module.getValue(pBmpSize, "i32");
|
||||
|
||||
// 获取BMP图片数据
|
||||
var aBmpData = new Uint8Array(nBmpDataSize);
|
||||
aBmpData.set(Module.HEAPU8.subarray(pBmpData, pBmpData + nBmpDataSize));
|
||||
|
||||
postMessage({'function': "GetBMP", 'data': aBmpData, 'errorCode': res}, [aBmpData.buffer]);
|
||||
aBmpData=null;
|
||||
if (pDataYUV != null) {
|
||||
Module._free(pDataYUV);
|
||||
pDataYUV = null;
|
||||
}
|
||||
if (pBmpData != null) {
|
||||
Module._free(pBmpData);
|
||||
pBmpData = null;
|
||||
}
|
||||
if (pBmpSize != null) {
|
||||
Module._free(pBmpSize);
|
||||
pBmpSize = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case "GetJPEG":
|
||||
var nJpegWidth = eventData.width;
|
||||
var nJpegHeight = eventData.height;
|
||||
var pYUVData1 = eventData.data;
|
||||
var nYUVSize1 = nJpegWidth * nJpegHeight * 3 / 2;
|
||||
var oJpegCropRect = eventData.rect;
|
||||
|
||||
var pDataYUV1 = Module._malloc(nYUVSize1);
|
||||
if (pDataYUV1 === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
Module.writeArrayToMemory(new Uint8Array(pYUVData1, 0, nYUVSize1), pDataYUV1);
|
||||
|
||||
// 分配JPEG空间
|
||||
var pJpegData = Module._malloc(nYUVSize1);
|
||||
var pJpegSize = Module._malloc(4);
|
||||
if (pJpegData === null || pJpegSize === null) {
|
||||
if (pJpegData != null) {
|
||||
Module._free(pJpegData);
|
||||
pJpegData = null;
|
||||
}
|
||||
|
||||
if (pJpegSize != null) {
|
||||
Module._free(pJpegSize);
|
||||
pJpegSize = null;
|
||||
}
|
||||
|
||||
if (pDataYUV1 != null) {
|
||||
Module._free(pDataYUV1);
|
||||
pDataYUV1 = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
Module.setValue(pJpegSize, nJpegWidth * nJpegHeight * 2, "i32"); // JPEG抓图,输入缓冲长度不小于当前帧YUV大小
|
||||
|
||||
res = Module._GetJPEG(g_nPort,pDataYUV1, nYUVSize1, pJpegData, pJpegSize,
|
||||
oJpegCropRect.left, oJpegCropRect.top, oJpegCropRect.right, oJpegCropRect.bottom);
|
||||
if (res !== PLAYM4_OK) {
|
||||
postMessage({'function': "GetJPEG", 'errorCode': res});
|
||||
if (pJpegData != null) {
|
||||
Module._free(pJpegData);
|
||||
pJpegData = null;
|
||||
}
|
||||
|
||||
if (pJpegSize != null) {
|
||||
Module._free(pJpegSize);
|
||||
pJpegSize = null;
|
||||
}
|
||||
|
||||
if (pDataYUV1 != null) {
|
||||
Module._free(pDataYUV1);
|
||||
pDataYUV1 = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取JPEG图片大小
|
||||
var nJpegSize = Module.getValue(pJpegSize, "i32");
|
||||
|
||||
// 获取JPEG图片数据
|
||||
var aJpegData = new Uint8Array(nJpegSize);
|
||||
aJpegData.set(Module.HEAPU8.subarray(pJpegData, pJpegData + nJpegSize));
|
||||
|
||||
postMessage({'function': "GetJPEG", 'data': aJpegData, 'errorCode': res}, [aJpegData.buffer]);
|
||||
|
||||
nJpegSize = null;
|
||||
aJpegData = null;
|
||||
|
||||
if (pDataYUV1 != null) {
|
||||
Module._free(pDataYUV1);
|
||||
pDataYUV1 = null;
|
||||
}
|
||||
if (pJpegData != null) {
|
||||
Module._free(pJpegData);
|
||||
pJpegData = null;
|
||||
}
|
||||
if (pJpegSize != null) {
|
||||
Module._free(pJpegSize);
|
||||
pJpegSize = null;
|
||||
}
|
||||
break;
|
||||
|
||||
case "SetDecodeFrameType":
|
||||
var nFrameType = eventData.data;
|
||||
res = Module._SetDecodeFrameType(g_nPort,nFrameType);
|
||||
if (res !== PLAYM4_OK) {
|
||||
postMessage({'function': "SetDecodeFrameType", 'errorCode': res});
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "CloseStream":
|
||||
//stop
|
||||
let resS = Module._Stop(g_nPort);
|
||||
if (resS !== PLAYM4_OK) {
|
||||
postMessage({'function': "Stop", 'errorCode': res});
|
||||
return;
|
||||
}
|
||||
//closeStream
|
||||
res = Module._CloseStream(g_nPort);
|
||||
if (res !== PLAYM4_OK) {
|
||||
postMessage({'function': "CloseStream", 'errorCode': res});
|
||||
return;
|
||||
}
|
||||
//freePort
|
||||
let resF = Module._FreePort(g_nPort);
|
||||
if (resF !== PLAYM4_OK) {
|
||||
postMessage({'function': "FreePort", 'errorCode': res});
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "PlaySound":
|
||||
let resPS = Module._PlaySound(g_nPort);
|
||||
if (resPS !== PLAYM4_OK) {
|
||||
console.log("PlaySound failed");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "StopSound":
|
||||
let resSS = Module._StopSound();
|
||||
if (resSS !== PLAYM4_OK) {
|
||||
console.log("StopSound failed");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "SetVolume":
|
||||
let resSV = Module._SetVolume(g_nPort,eventData.volume);
|
||||
if (resSV !== PLAYM4_OK) {
|
||||
console.log("Audio SetVolume failed");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "GetVolume":
|
||||
let volume = Module._GetVolume();
|
||||
if(volume>0)
|
||||
{
|
||||
postMessage({'function': "GetVolume", 'volume': volume});
|
||||
}
|
||||
else{
|
||||
console.log("Audio GetVolume failed");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "OnlyPlaySound":
|
||||
let resOPS = Module._OnlyPlaySound(g_nPort);
|
||||
if (resOPS !== PLAYM4_OK) {
|
||||
console.log("OnlyPlaySound failed");
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case "Pause" :
|
||||
let resPa = Module._Pause(g_nPort,eventData.bPlay);
|
||||
if (resPa !== PLAYM4_OK) {
|
||||
console.log("Pause failed");
|
||||
return;
|
||||
}
|
||||
case "PlayRate":
|
||||
Module._SetPlayRate(g_nPort,eventData.playRate);
|
||||
break;
|
||||
case "SetIFrameDecInterval":
|
||||
Module._SetIFrameDecInterval(g_nPort,eventData.data);
|
||||
break;
|
||||
case "SetLostFrameMode":
|
||||
Module._SetLostFrameMode(g_nPort,eventData.data);
|
||||
break;
|
||||
case "SetDemuxModel":
|
||||
Module._SetDemuxModel(g_nPort,eventData.nIdemuxType,eventData.bTrue);
|
||||
break;
|
||||
case "SkipErrorData":
|
||||
Module._SkipErrorData(g_nPort,eventData.bSkip);
|
||||
break;
|
||||
case "SetDecodeERC":
|
||||
Module._SetDecodeERC(g_nPort,eventData.nLevel);
|
||||
break;
|
||||
case "SetANRParam":
|
||||
Module._SetANRParam(g_nPort,eventData.nEnable,eventData.nANRLevel);
|
||||
break;
|
||||
case "SetResampleValue":
|
||||
Module._SetResampleValue(g_nPort,eventData.nEnable,eventData.resampleValue);
|
||||
break;
|
||||
case "GetLastError":
|
||||
let errorCode = Module._GetLastError(g_nPort);
|
||||
postMessage({'function': "GetLastError", 'errorCode': errorCode});
|
||||
break;
|
||||
case "SetGlobalBaseTime":
|
||||
Module._SetGlobalBaseTime(g_nPort,eventData.year,eventData.month,eventData.day,eventData.hour,eventData.min,eventData.sec,eventData.ms);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
function getOSDTime(oFrameInfo) {
|
||||
var iYear = oFrameInfo.year;
|
||||
var iMonth = oFrameInfo.month;
|
||||
var iDay = oFrameInfo.day;
|
||||
var iHour = oFrameInfo.hour;
|
||||
var iMinute = oFrameInfo.minute;
|
||||
var iSecond = oFrameInfo.second;
|
||||
|
||||
if (iMonth < 10) {
|
||||
iMonth = "0" + iMonth;
|
||||
}
|
||||
if (iDay < 10) {
|
||||
iDay = "0" + iDay;
|
||||
}
|
||||
if (iHour < 10) {
|
||||
iHour = "0" + iHour;
|
||||
}
|
||||
if (iMinute < 10) {
|
||||
iMinute = "0" + iMinute;
|
||||
}
|
||||
if (iSecond < 10) {
|
||||
iSecond = "0" + iSecond;
|
||||
}
|
||||
|
||||
return iYear + "-" + iMonth + "-" + iDay + " " + iHour + ":" + iMinute + ":" + iSecond;
|
||||
}
|
||||
// 获取帧数据
|
||||
function getFrameData(fun)
|
||||
{
|
||||
// function getFrameData() {
|
||||
// 获取帧数据
|
||||
// var res = Module._GetFrameData();
|
||||
var res = fun();
|
||||
if (res === PLAYM4_OK)
|
||||
{
|
||||
var oFrameInfo = Module._GetFrameInfo();
|
||||
switch (oFrameInfo.frameType)
|
||||
{
|
||||
case AUDIO_TYPE:
|
||||
var iSize = oFrameInfo.frameSize;
|
||||
if (0 === iSize)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
var pPCM = Module._GetFrameBuffer();
|
||||
// var audioBuf = new ArrayBuffer(iSize);
|
||||
var aPCMData = new Uint8Array(iSize);
|
||||
aPCMData.set(Module.HEAPU8.subarray(pPCM, pPCM + iSize));
|
||||
if(bWorkerPrintLog)
|
||||
{
|
||||
console.log("<<<Worker: audio media Info: nSise:"+ oFrameInfo.frameSize+",nSampleRate:"+oFrameInfo.samplesPerSec+',channel:'+oFrameInfo.channels+',bitsPerSample:'+oFrameInfo.bitsPerSample);
|
||||
}
|
||||
postMessage({
|
||||
'function': "GetFrameData", 'type': "audioType", 'data': aPCMData.buffer,
|
||||
'frameInfo': oFrameInfo, 'errorCode': res
|
||||
}, [aPCMData.buffer]);
|
||||
|
||||
oFrameInfo = null;
|
||||
pPCM = null;
|
||||
aPCMData = null;
|
||||
return PLAYM4_AUDIO_FRAME;
|
||||
|
||||
case VIDEO_TYPE:
|
||||
var szOSDTime = getOSDTime(oFrameInfo);
|
||||
|
||||
var iWidth = oFrameInfo.width;
|
||||
var iHeight = oFrameInfo.height;
|
||||
|
||||
var iYUVSize = iWidth * iHeight * 3 / 2;
|
||||
if (0 === iYUVSize)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
var pYUV = Module._GetFrameBuffer();
|
||||
|
||||
// 图像数据渲染后压回,若从主码流切到子码流,存在数组大小与图像大小不匹配现象
|
||||
var aYUVData = new Uint8Array(iYUVSize);
|
||||
aYUVData.set(Module.HEAPU8.subarray(pYUV, pYUV + iYUVSize));
|
||||
if(bWorkerPrintLog)
|
||||
{
|
||||
console.log("<<<Worker: video media Info: Width:"+ oFrameInfo.width+",Height:"+oFrameInfo.height+",timeStamp:"+oFrameInfo.timeStamp);
|
||||
}
|
||||
|
||||
postMessage({
|
||||
'function': "GetFrameData", 'type': "videoType", 'data': aYUVData.buffer,
|
||||
'dataLen': aYUVData.length,'osd': szOSDTime, 'frameInfo': oFrameInfo, 'errorCode': res
|
||||
}, [aYUVData.buffer]);
|
||||
|
||||
oFrameInfo = null;
|
||||
pYUV = null;
|
||||
aYUVData = null;
|
||||
return PLAYM4_VIDEO_FRAME;
|
||||
|
||||
case PRIVT_TYPE:
|
||||
postMessage({
|
||||
'function': "GetFrameData", 'type': "", 'data': null,
|
||||
'dataLen': -1, 'osd': 0, 'frameInfo': null, 'errorCode': PLAYM4_SYS_NOT_SUPPORT
|
||||
});
|
||||
return PLAYM4_SYS_NOT_SUPPORT;
|
||||
|
||||
default:
|
||||
postMessage({
|
||||
'function': "GetFrameData", 'type': "", 'data': null,
|
||||
'dataLen': -1, 'osd': 0, 'frameInfo': null, 'errorCode': PLAYM4_SYS_NOT_SUPPORT
|
||||
});
|
||||
return PLAYM4_SYS_NOT_SUPPORT;
|
||||
}
|
||||
}
|
||||
else {
|
||||
let errorCode = Module._GetLastError(g_nPort);
|
||||
//解码失败返回裸数据
|
||||
if(PLAYM4_DECODE_ERROR===errorCode)
|
||||
{
|
||||
var rawInfo=Module._GetRawDataInfo();
|
||||
var pRawData = Module._GetRawDataBuffer();
|
||||
var aRawData = new Uint8Array(rawInfo.isize);
|
||||
aRawData.set(Module.HEAPU8.subarray(pRawData, pRawData + rawInfo.isize));
|
||||
postMessage({
|
||||
'function': "GetRawData", 'type': "", 'data':aRawData.buffer,
|
||||
'rawDataLen': rawInfo.isize, 'osd': 0, 'frameInfo': null, 'errorCode': errorCode
|
||||
});
|
||||
rawInfo=null;
|
||||
pRawData=null;
|
||||
aRawData=null;
|
||||
}
|
||||
//需要更多数据
|
||||
if (PLAYM4_NEED_MORE_DATA === errorCode || PLAYM4_SYS_NOT_SUPPORT === errorCode || PLAYM4_NEED_NEET_LOOP === errorCode){
|
||||
postMessage({
|
||||
'function': "GetFrameData", 'type': "", 'data': null,
|
||||
'dataLen': -1, 'osd': 0, 'frameInfo': null, 'errorCode': errorCode
|
||||
});
|
||||
}
|
||||
return errorCode;
|
||||
}
|
||||
}
|
||||
|
||||
// 开始计算时间
|
||||
function startTime() {
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
// 结束计算时间
|
||||
function endTime() {
|
||||
return new Date().getTime();
|
||||
}
|
||||
|
||||
// 字母字符串转byte数组
|
||||
function stringToBytes ( str ) {
|
||||
var ch, st, re = [];
|
||||
for (var i = 0; i < str.length; i++ ) {
|
||||
ch = str.charCodeAt(i); // get char
|
||||
st = []; // set up "stack"
|
||||
do {
|
||||
st.push( ch & 0xFF ); // push byte to stack
|
||||
ch = ch >> 8; // shift value down by 1 byte
|
||||
}
|
||||
while ( ch );
|
||||
// add stack contents to result
|
||||
// done because chars have "wrong" endianness
|
||||
re = re.concat( st.reverse() );
|
||||
}
|
||||
// return an array of bytes
|
||||
return re;
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,398 @@
|
|||
"use strict";
|
||||
//顶点着色器
|
||||
//attribute修饰符用于声明由浏览器(javascript)传输给顶点着色器的变量值;
|
||||
// vertexPos即我们定义的顶点坐标;
|
||||
// gl_Position是一个内建的传出变量。
|
||||
var vertexYUVShader = [
|
||||
'attribute vec4 vertexPos;',
|
||||
'attribute vec2 texturePos;',
|
||||
'varying vec2 textureCoord;',
|
||||
|
||||
'void main()',
|
||||
'{',
|
||||
'gl_Position = vertexPos;',
|
||||
'textureCoord = texturePos;',
|
||||
'}'
|
||||
].join('\n');
|
||||
//像素着色器(yuv->rgb)
|
||||
var fragmentYUVShader = [
|
||||
'precision highp float;',
|
||||
'varying highp vec2 textureCoord;',
|
||||
'uniform sampler2D ySampler;',
|
||||
'uniform sampler2D uSampler;',
|
||||
'uniform sampler2D vSampler;',
|
||||
'const mat4 YUV2RGB = mat4',
|
||||
'(',
|
||||
'1.1643828125, 0, 1.59602734375, -.87078515625,',
|
||||
'1.1643828125, -.39176171875, -.81296875, .52959375,',
|
||||
'1.1643828125, 2.017234375, 0, -1.081390625,',
|
||||
'0, 0, 0, 1',
|
||||
');',
|
||||
|
||||
'void main(void) {',
|
||||
'highp float y = texture2D(ySampler, textureCoord).r;',
|
||||
'highp float u = texture2D(uSampler, textureCoord).r;',
|
||||
'highp float v = texture2D(vSampler, textureCoord).r;',
|
||||
'gl_FragColor = vec4(y, u, v, 1) * YUV2RGB;',
|
||||
'}'
|
||||
].join('\n');
|
||||
|
||||
(function (root, factory) {
|
||||
root.SuperRender = factory();
|
||||
}(this, function () {
|
||||
|
||||
function RenderManager(canvas) {
|
||||
|
||||
this.canvasElement = document.getElementById(canvas);
|
||||
|
||||
this.initContextGL();
|
||||
|
||||
if(this.contextGL) {
|
||||
this.YUVProgram = this.initProgram(vertexYUVShader, fragmentYUVShader);
|
||||
this.initBuffers();
|
||||
this.initTextures();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化WebGL上下文
|
||||
*/
|
||||
RenderManager.prototype.initContextGL = function() {
|
||||
|
||||
var canvas = this.canvasElement;
|
||||
|
||||
var gl = null;
|
||||
|
||||
try {
|
||||
gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
|
||||
} catch (e) {
|
||||
gl = null;
|
||||
}
|
||||
|
||||
if(!gl || typeof gl.getParameter !== "function") {
|
||||
gl = null;
|
||||
}
|
||||
|
||||
this.contextGL = gl;
|
||||
|
||||
console.log("WebGL1.0");
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化着色器程序
|
||||
* @param vertexShaderScript 顶点着色器脚本
|
||||
* @param fragmentShaderScript 片段着色器脚本
|
||||
*/
|
||||
RenderManager.prototype.initProgram = function(vertexShaderScript, fragmentShaderScript) {
|
||||
|
||||
var gl = this.contextGL;
|
||||
|
||||
var vertexShader = gl.createShader(gl.VERTEX_SHADER); //创建定点着色器
|
||||
gl.shaderSource(vertexShader, vertexShaderScript);
|
||||
gl.compileShader(vertexShader);
|
||||
if(!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
|
||||
console.log('Vertex shader failed to compile: ' + gl.getShaderInfoLog(vertexShader));
|
||||
}
|
||||
|
||||
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
|
||||
gl.shaderSource(fragmentShader, fragmentShaderScript);
|
||||
gl.compileShader(fragmentShader);
|
||||
if(!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
|
||||
console.log('Fragment shader failed to compile: ' + gl.getShaderInfoLog(fragmentShader));
|
||||
}
|
||||
|
||||
var program = gl.createProgram();
|
||||
gl.attachShader(program, vertexShader);
|
||||
gl.attachShader(program, fragmentShader);
|
||||
gl.linkProgram(program);
|
||||
if(!gl.getProgramParameter(program, gl.LINK_STATUS)) {
|
||||
console.log('Program failed to compile: ' + gl.getProgramInfoLog(program));
|
||||
}
|
||||
|
||||
gl.deleteShader(vertexShader);
|
||||
gl.deleteShader(fragmentShader);
|
||||
|
||||
return program;
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化数据缓存
|
||||
*/
|
||||
RenderManager.prototype.initBuffers = function() {
|
||||
|
||||
var gl = this.contextGL;
|
||||
|
||||
var vertexPosBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 1, -1, 1, 1, -1, -1, -1]), gl.STATIC_DRAW);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
|
||||
var texturePosBuffer = gl.createBuffer();
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
|
||||
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]), gl.DYNAMIC_DRAW);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
|
||||
this.vertexPosBuffer = vertexPosBuffer;
|
||||
this.texturePosBuffer = texturePosBuffer;
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建纹理
|
||||
*/
|
||||
RenderManager.prototype.initTexture = function() {
|
||||
|
||||
var gl = this.contextGL;
|
||||
|
||||
var textureRef = gl.createTexture();
|
||||
gl.bindTexture(gl.TEXTURE_2D, textureRef);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
||||
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
||||
gl.bindTexture(gl.TEXTURE_2D, null);
|
||||
|
||||
return textureRef;
|
||||
};
|
||||
|
||||
/**
|
||||
* 初始化YUV纹理
|
||||
*/
|
||||
RenderManager.prototype.initTextures = function() {
|
||||
|
||||
var gl = this.contextGL;
|
||||
|
||||
var program = this.YUVProgram;
|
||||
gl.useProgram(program);
|
||||
|
||||
var yTextureRef = this.initTexture();
|
||||
var ySamplerRef = gl.getUniformLocation(program, 'ySampler');
|
||||
gl.uniform1i(ySamplerRef, 0);
|
||||
this.yTextureRef = yTextureRef;
|
||||
|
||||
var uTextureRef = this.initTexture();
|
||||
var uSamplerRef = gl.getUniformLocation(program, 'uSampler');
|
||||
gl.uniform1i(uSamplerRef, 1);
|
||||
this.uTextureRef = uTextureRef;
|
||||
|
||||
var vTextureRef = this.initTexture();
|
||||
var vSamplerRef = gl.getUniformLocation(program, 'vSampler');
|
||||
gl.uniform1i(vSamplerRef, 2);
|
||||
this.vTextureRef = vTextureRef;
|
||||
|
||||
gl.useProgram(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* 显示帧数据
|
||||
* @param nWidth 宽度
|
||||
* @param nHeight 高度
|
||||
* @param nHeight 帧数据
|
||||
*/
|
||||
RenderManager.prototype.SR_DisplayFrameData = function(nWidth, nHeight, pData,dWidth,dHeight) {
|
||||
|
||||
if(nWidth <= 0 || nHeight <= 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var gl = this.contextGL;
|
||||
|
||||
if(null == pData)
|
||||
{
|
||||
gl.clearColor(0.0, 0.0, 0.0, 0.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
return;
|
||||
}
|
||||
|
||||
var canvas = this.canvasElement;
|
||||
|
||||
this.nWindowWidth = canvas.width;
|
||||
this.nWindowHeight = canvas.height;
|
||||
|
||||
var nWindowWidth = this.nWindowWidth;
|
||||
var nWindowHeight = this.nWindowHeight;
|
||||
|
||||
gl.clearColor(0.8, 0.8, 1.0, 1.0);
|
||||
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
|
||||
|
||||
gl.viewport(0, 0, nWindowWidth, nWindowHeight);
|
||||
|
||||
this.updateFrameData(nWidth, nHeight, pData,dWidth,dHeight);
|
||||
|
||||
var program = this.YUVProgram;
|
||||
gl.useProgram(program);
|
||||
|
||||
var vertexPosBuffer = this.vertexPosBuffer;
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, vertexPosBuffer);
|
||||
var vertexPosRef = gl.getAttribLocation(program, 'vertexPos');
|
||||
gl.enableVertexAttribArray(vertexPosRef);
|
||||
gl.vertexAttribPointer(vertexPosRef, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
|
||||
var texturePosBuffer = this.texturePosBuffer;
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
|
||||
var texturePosRef = gl.getAttribLocation(program, 'texturePos');
|
||||
gl.enableVertexAttribArray(texturePosRef);
|
||||
gl.vertexAttribPointer(texturePosRef, 2, gl.FLOAT, false, 0, 0);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
|
||||
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
||||
|
||||
gl.disableVertexAttribArray(vertexPosRef);
|
||||
gl.disableVertexAttribArray(texturePosRef);
|
||||
|
||||
gl.useProgram(null);
|
||||
};
|
||||
|
||||
/**
|
||||
* 上传YUV数据到纹理
|
||||
* @param nWidth 宽度
|
||||
* @param nHeight 高度
|
||||
* @param nHeight 帧数据
|
||||
*/
|
||||
RenderManager.prototype.updateFrameData = function(width, height, data,dWidth,dHeight) {
|
||||
|
||||
var gl = this.contextGL;
|
||||
|
||||
var yTextureRef = this.yTextureRef;
|
||||
var uTextureRef = this.uTextureRef;
|
||||
var vTextureRef = this.vTextureRef;
|
||||
|
||||
var i420Data = data;
|
||||
// debugger;
|
||||
if(width == dWidth && height == dHeight)
|
||||
{
|
||||
var yDataLength = width * height;
|
||||
var yData = i420Data.subarray(0, yDataLength);
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width, height, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, yData);
|
||||
|
||||
var cbDataLength = width/2 * height/2;
|
||||
var cbData = i420Data.subarray(width*height, width*height + cbDataLength);
|
||||
gl.activeTexture(gl.TEXTURE2);
|
||||
gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width/2, height/2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, cbData);
|
||||
|
||||
var crDataLength = cbDataLength;
|
||||
var crData = i420Data.subarray(width*height + width*height/4, width*height + width*height/4 + crDataLength);
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, width/2, height/2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, crData);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
// //裁剪宽
|
||||
var yDataLength = dWidth * dHeight;
|
||||
var yData=new Uint8Array(yDataLength) ;
|
||||
for(var i=0;i<dHeight;i++)
|
||||
{
|
||||
//var ySonData=new Uint8Array(dWidth) ;
|
||||
var ySonData = i420Data.subarray(i*width, i*width+dWidth);
|
||||
for (var j = 0; j < dWidth; j++) {
|
||||
yData[i*dWidth + j] = ySonData[j];
|
||||
}
|
||||
}
|
||||
gl.activeTexture(gl.TEXTURE0);
|
||||
gl.bindTexture(gl.TEXTURE_2D, yTextureRef);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, dWidth, dHeight, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, yData);
|
||||
yData=null;
|
||||
ySonData=null;
|
||||
|
||||
var cbDataLength = dWidth/2 * dHeight/2;
|
||||
var cbData =new Uint8Array(cbDataLength);
|
||||
//var cbSonData=new Uint8Array(dWidth/2) ;
|
||||
for(var i=0;i<dHeight/2;i++)
|
||||
{
|
||||
var cbSonData = i420Data.subarray(width*height+i*width/2, width*height+i*width/2+dWidth/2);
|
||||
for (var j = 0; j < dWidth/2; j++) {
|
||||
cbData[i*dWidth/2 + j] = cbSonData[j];
|
||||
}
|
||||
}
|
||||
gl.activeTexture(gl.TEXTURE2);
|
||||
gl.bindTexture(gl.TEXTURE_2D, vTextureRef);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, dWidth/2, dHeight/2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, cbData);
|
||||
cbData=null;
|
||||
cbSonData=null;
|
||||
|
||||
var crDataLength = cbDataLength;
|
||||
var crData = new Uint8Array(crDataLength);
|
||||
for(var i=0;i<dHeight/2;i++)
|
||||
{
|
||||
var crSonData = i420Data.subarray(width*height*5/4+i*width/2, width*height*5/4+i*width/2+dWidth/2);
|
||||
for (var j = 0; j < dWidth/2; j++) {
|
||||
crData[i*dWidth/2 + j] = crSonData[j];
|
||||
}
|
||||
}
|
||||
gl.activeTexture(gl.TEXTURE1);
|
||||
gl.bindTexture(gl.TEXTURE_2D, uTextureRef);
|
||||
gl.texImage2D(gl.TEXTURE_2D, 0, gl.LUMINANCE, dWidth/2, dHeight/2, 0, gl.LUMINANCE, gl.UNSIGNED_BYTE, crData);
|
||||
crData=null;
|
||||
crSonData=null;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置显示区域
|
||||
* @param stDisplayRect 显示区域
|
||||
*/
|
||||
RenderManager.prototype.SR_SetDisplayRect = function(stDisplayRect) {
|
||||
|
||||
var gl = this.contextGL;
|
||||
var oRect = this.canvasElement.getBoundingClientRect();//采用实际宽高,不然多窗口scale情况下放大异常
|
||||
var nWindowWidth = oRect.width;
|
||||
var nWindowHeight = oRect.height;
|
||||
// var nWindowWidth = this.nWindowWidth;
|
||||
// var nWindowHeight = this.nWindowHeight;
|
||||
//console.log("this.nWindowWidth:"+this.nWindowWidth+", this.nWindowHeight:"+this.nWindowHeight)
|
||||
var texturePosValues = null;
|
||||
|
||||
if(stDisplayRect && nWindowWidth > 0 && nWindowHeight > 0) {
|
||||
var fLeft = stDisplayRect.left / nWindowWidth;
|
||||
var fTop = stDisplayRect.top / nWindowHeight;
|
||||
var fRight = stDisplayRect.right / nWindowWidth;
|
||||
var fBottom = stDisplayRect.bottom / nWindowHeight;
|
||||
|
||||
texturePosValues = new Float32Array([fRight, fTop, fLeft, fTop, fRight, fBottom, fLeft, fBottom]);
|
||||
}
|
||||
else {
|
||||
texturePosValues = new Float32Array([1, 0, 0, 0, 1, 1, 0, 1]);
|
||||
}
|
||||
|
||||
var texturePosBuffer = this.texturePosBuffer;
|
||||
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, texturePosBuffer);
|
||||
gl.bufferSubData(gl.ARRAY_BUFFER, 0, texturePosValues);
|
||||
gl.bindBuffer(gl.ARRAY_BUFFER, null);
|
||||
};
|
||||
|
||||
/**
|
||||
* 释放显示资源
|
||||
*/
|
||||
RenderManager.prototype.SR_Destroy = function() {
|
||||
|
||||
var gl = this.contextGL;
|
||||
|
||||
var YUVProgram = this.YUVProgram;
|
||||
gl.deleteProgram(YUVProgram);
|
||||
|
||||
var vertexPosBuffer = this.vertexPosBuffer;
|
||||
var texturePosBuffer = this.texturePosBuffer;
|
||||
|
||||
gl.deleteBuffer(vertexPosBuffer);
|
||||
gl.deleteBuffer(texturePosBuffer);
|
||||
|
||||
var yTextureRef = this.yTextureRef;
|
||||
var uTextureRef = this.uTextureRef;
|
||||
var vTextureRef = this.vTextureRef;
|
||||
gl.deleteTexture(yTextureRef);
|
||||
gl.deleteTexture(uTextureRef);
|
||||
gl.deleteTexture(vTextureRef);
|
||||
gl.getExtension('WEBGL_lose_context').loseContext();
|
||||
};
|
||||
|
||||
return RenderManager;
|
||||
|
||||
}));
|
After Width: | Height: | Size: 106 KiB |
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
After Width: | Height: | Size: 1.5 KiB |
|
@ -0,0 +1,182 @@
|
|||
<template>
|
||||
<a-config-provider :locale="zhCN">
|
||||
<a-spin :spinning="loading" style="z-index: 1001" :tip="loadingMessage">
|
||||
<fullScreenContainer>
|
||||
<router-view></router-view>
|
||||
</fullScreenContainer>
|
||||
</a-spin>
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import fullScreenContainer from '@/components/fullScreenContainer.vue'
|
||||
import zhCN from 'ant-design-vue/es/locale/zh_CN'
|
||||
import { useLoadingStore } from '@/stores/modules/loadingStore'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
const loadingStore = useLoadingStore()
|
||||
const { loading, loadingMessage } = storeToRefs(loadingStore)
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
body {
|
||||
font-family: AliBaBaPuHuTi, serif;
|
||||
}
|
||||
|
||||
//地图弹框样式
|
||||
.mapPopInfo {
|
||||
width: 450px;
|
||||
}
|
||||
|
||||
.mapPopInfo-top {
|
||||
height: 33px;
|
||||
line-height: 33px;
|
||||
background: linear-gradient(to right, #0d6295, #0872b1, #0872b18c);
|
||||
padding: 0 15px;
|
||||
border: solid 2px #2d94b5;
|
||||
border-bottom: solid 1px #54bbda;
|
||||
}
|
||||
|
||||
.mapPopInfo-top > img {
|
||||
float: right;
|
||||
margin: 3px 0;
|
||||
}
|
||||
|
||||
.mapPopInfo-top > span > span {
|
||||
float: left;
|
||||
width: 330px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mapPopInfo-main {
|
||||
padding: 15px;
|
||||
background: #13213bab;
|
||||
box-shadow: inset 1px 1px 14px 2px #32689e;
|
||||
border: solid 2px #2d94b5;
|
||||
border-top: 0;
|
||||
}
|
||||
|
||||
.mapPopInfo-main > div > p {
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.mapPopInfo-main > div > p > span:nth-child(3) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.mapPopInfo-main > div > p > button {
|
||||
height: 26px;
|
||||
line-height: 26px;
|
||||
background: #2081c1;
|
||||
color: #fff;
|
||||
border: 1px solid #2081c1;
|
||||
box-shadow: 0 0 3px 3px #244b68;
|
||||
}
|
||||
|
||||
.amap-marker-content {
|
||||
white-space: normal !important;
|
||||
}
|
||||
|
||||
.amap-info-contentContainer.bottom-center {
|
||||
padding-bottom: 0 !important;
|
||||
}
|
||||
|
||||
.indexMarkerImg {
|
||||
width: 32px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.indexMarkerImg > img {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.indexMarkerImg > img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
left: 4px;
|
||||
top: 4px;
|
||||
z-index: 20;
|
||||
}
|
||||
|
||||
@keyframes jumper {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
5% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.markerClass {
|
||||
position: relative;
|
||||
left: 28px;
|
||||
top: 33px;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.markerClass > div {
|
||||
background-color: #f4090a;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 100%;
|
||||
margin: 2px;
|
||||
animation-fill-mode: both;
|
||||
position: absolute;
|
||||
left: -41px;
|
||||
top: -46px;
|
||||
opacity: 0;
|
||||
margin: 0;
|
||||
width: 75px;
|
||||
height: 75px;
|
||||
animation: jumper 3s 0s linear infinite;
|
||||
}
|
||||
|
||||
.markerClass > div:nth-child(2) {
|
||||
-webkit-animation-delay: 0.33333s;
|
||||
animation-delay: 0.33333s;
|
||||
}
|
||||
|
||||
.markerClass > div:nth-child(3) {
|
||||
-webkit-animation-delay: 0.66666s;
|
||||
animation-delay: 0.66666s;
|
||||
}
|
||||
|
||||
.riskMarkerImg {
|
||||
overflow: hidden;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.riskMarkerImg > span {
|
||||
float: left;
|
||||
}
|
||||
|
||||
.riskMarkerImg > img {
|
||||
float: left;
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
margin: 0 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.riskMarkerImg > span:last-child {
|
||||
width: 120px;
|
||||
height: 80px;
|
||||
background: #000;
|
||||
margin-left: 10px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,539 @@
|
|||
/* Logo 字体 */
|
||||
@font-face {
|
||||
font-family: "iconfont logo";
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
|
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
|
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
|
||||
}
|
||||
|
||||
.logo {
|
||||
font-family: "iconfont logo";
|
||||
font-size: 160px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
/* tabs */
|
||||
.nav-tabs {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.nav-tabs .nav-more {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 42px;
|
||||
line-height: 42px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#tabs {
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
#tabs li {
|
||||
cursor: pointer;
|
||||
width: 100px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
border-bottom: 2px solid transparent;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
margin-bottom: -1px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
|
||||
#tabs .active {
|
||||
border-bottom-color: #f00;
|
||||
color: #222;
|
||||
}
|
||||
|
||||
.tab-container .content {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* 页面布局 */
|
||||
.main {
|
||||
padding: 30px 100px;
|
||||
width: 960px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.main .logo {
|
||||
color: #333;
|
||||
text-align: left;
|
||||
margin-bottom: 30px;
|
||||
line-height: 1;
|
||||
height: 110px;
|
||||
margin-top: -50px;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.main .logo a {
|
||||
font-size: 160px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.helps {
|
||||
margin-top: 40px;
|
||||
}
|
||||
|
||||
.helps pre {
|
||||
padding: 20px;
|
||||
margin: 10px 0;
|
||||
border: solid 1px #e7e1cd;
|
||||
background-color: #fffdef;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.icon_lists {
|
||||
width: 100% !important;
|
||||
overflow: hidden;
|
||||
*zoom: 1;
|
||||
}
|
||||
|
||||
.icon_lists li {
|
||||
width: 100px;
|
||||
margin-bottom: 10px;
|
||||
margin-right: 20px;
|
||||
text-align: center;
|
||||
list-style: none !important;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.icon_lists li .code-name {
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.icon_lists .icon {
|
||||
display: block;
|
||||
height: 100px;
|
||||
line-height: 100px;
|
||||
font-size: 42px;
|
||||
margin: 10px auto;
|
||||
color: #333;
|
||||
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
-moz-transition: font-size 0.25s linear, width 0.25s linear;
|
||||
transition: font-size 0.25s linear, width 0.25s linear;
|
||||
}
|
||||
|
||||
.icon_lists .icon:hover {
|
||||
font-size: 100px;
|
||||
}
|
||||
|
||||
.icon_lists .svg-icon {
|
||||
/* 通过设置 font-size 来改变图标大小 */
|
||||
width: 1em;
|
||||
/* 图标和文字相邻时,垂直对齐 */
|
||||
vertical-align: -0.15em;
|
||||
/* 通过设置 color 来改变 SVG 的颜色/fill */
|
||||
fill: currentColor;
|
||||
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
|
||||
normalize.css 中也包含这行 */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.icon_lists li .name,
|
||||
.icon_lists li .code-name {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
/* markdown 样式 */
|
||||
.markdown {
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.highlight {
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.markdown img {
|
||||
vertical-align: middle;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
color: #404040;
|
||||
font-weight: 500;
|
||||
line-height: 40px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown h2,
|
||||
.markdown h3,
|
||||
.markdown h4,
|
||||
.markdown h5,
|
||||
.markdown h6 {
|
||||
color: #404040;
|
||||
margin: 1.6em 0 0.6em 0;
|
||||
font-weight: 500;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown h1 {
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.markdown h2 {
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.markdown h3 {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.markdown h4 {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.markdown h5 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown h6 {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.markdown hr {
|
||||
height: 1px;
|
||||
border: 0;
|
||||
background: #e9e9e9;
|
||||
margin: 16px 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
.markdown p {
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown>p,
|
||||
.markdown>blockquote,
|
||||
.markdown>.highlight,
|
||||
.markdown>ol,
|
||||
.markdown>ul {
|
||||
width: 80%;
|
||||
}
|
||||
|
||||
.markdown ul>li {
|
||||
list-style: circle;
|
||||
}
|
||||
|
||||
.markdown>ul li,
|
||||
.markdown blockquote ul>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown>ul li p,
|
||||
.markdown>ol li p {
|
||||
margin: 0.6em 0;
|
||||
}
|
||||
|
||||
.markdown ol>li {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
.markdown>ol li,
|
||||
.markdown blockquote ol>li {
|
||||
margin-left: 20px;
|
||||
padding-left: 4px;
|
||||
}
|
||||
|
||||
.markdown code {
|
||||
margin: 0 3px;
|
||||
padding: 0 5px;
|
||||
background: #eee;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.markdown strong,
|
||||
.markdown b {
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0px;
|
||||
empty-cells: show;
|
||||
border: 1px solid #e9e9e9;
|
||||
width: 95%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
white-space: nowrap;
|
||||
color: #333;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown>table th,
|
||||
.markdown>table td {
|
||||
border: 1px solid #e9e9e9;
|
||||
padding: 8px 16px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown>table th {
|
||||
background: #F7F7F7;
|
||||
}
|
||||
|
||||
.markdown blockquote {
|
||||
font-size: 90%;
|
||||
color: #999;
|
||||
border-left: 4px solid #e9e9e9;
|
||||
padding-left: 0.8em;
|
||||
margin: 1em 0;
|
||||
}
|
||||
|
||||
.markdown blockquote p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.markdown .anchor {
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.markdown .waiting {
|
||||
color: #ccc;
|
||||
}
|
||||
|
||||
.markdown h1:hover .anchor,
|
||||
.markdown h2:hover .anchor,
|
||||
.markdown h3:hover .anchor,
|
||||
.markdown h4:hover .anchor,
|
||||
.markdown h5:hover .anchor,
|
||||
.markdown h6:hover .anchor {
|
||||
opacity: 1;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.markdown>br,
|
||||
.markdown>p>br {
|
||||
clear: both;
|
||||
}
|
||||
|
||||
|
||||
.hljs {
|
||||
display: block;
|
||||
background: white;
|
||||
padding: 0.5em;
|
||||
color: #333333;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.hljs-comment,
|
||||
.hljs-meta {
|
||||
color: #969896;
|
||||
}
|
||||
|
||||
.hljs-string,
|
||||
.hljs-variable,
|
||||
.hljs-template-variable,
|
||||
.hljs-strong,
|
||||
.hljs-emphasis,
|
||||
.hljs-quote {
|
||||
color: #df5000;
|
||||
}
|
||||
|
||||
.hljs-keyword,
|
||||
.hljs-selector-tag,
|
||||
.hljs-type {
|
||||
color: #a71d5d;
|
||||
}
|
||||
|
||||
.hljs-literal,
|
||||
.hljs-symbol,
|
||||
.hljs-bullet,
|
||||
.hljs-attribute {
|
||||
color: #0086b3;
|
||||
}
|
||||
|
||||
.hljs-section,
|
||||
.hljs-name {
|
||||
color: #63a35c;
|
||||
}
|
||||
|
||||
.hljs-tag {
|
||||
color: #333333;
|
||||
}
|
||||
|
||||
.hljs-title,
|
||||
.hljs-attr,
|
||||
.hljs-selector-id,
|
||||
.hljs-selector-class,
|
||||
.hljs-selector-attr,
|
||||
.hljs-selector-pseudo {
|
||||
color: #795da3;
|
||||
}
|
||||
|
||||
.hljs-addition {
|
||||
color: #55a532;
|
||||
background-color: #eaffea;
|
||||
}
|
||||
|
||||
.hljs-deletion {
|
||||
color: #bd2c00;
|
||||
background-color: #ffecec;
|
||||
}
|
||||
|
||||
.hljs-link {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* 代码高亮 */
|
||||
/* PrismJS 1.15.0
|
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
|
||||
/**
|
||||
* prism.js default theme for JavaScript, CSS and HTML
|
||||
* Based on dabblet (http://dabblet.com)
|
||||
* @author Lea Verou
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: black;
|
||||
background: none;
|
||||
text-shadow: 0 1px white;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection,
|
||||
pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: #b3d4fc;
|
||||
}
|
||||
|
||||
@media print {
|
||||
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
padding: 1em;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
:not(pre)>code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
background: #f5f2f0;
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre)>code[class*="language-"] {
|
||||
padding: .1em;
|
||||
border-radius: .3em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: slategray;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string {
|
||||
color: #9a6e3a;
|
||||
background: hsla(0, 0%, 100%, .5);
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #DD4A68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
|
@ -0,0 +1,163 @@
|
|||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 4036849 */
|
||||
src: url('iconfont.woff2?t=1695805406735') format('woff2'),
|
||||
url('iconfont.woff?t=1695805406735') format('woff'),
|
||||
url('iconfont.ttf?t=1695805406735') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-mysql:before {
|
||||
content: "\e667";
|
||||
}
|
||||
|
||||
.icon-RabbitMQ:before {
|
||||
content: "\e6a0";
|
||||
}
|
||||
|
||||
.icon-duanluqi:before {
|
||||
content: "\e60a";
|
||||
}
|
||||
|
||||
.icon-baimingdan:before {
|
||||
content: "\e643";
|
||||
}
|
||||
|
||||
.icon-VPNwangguan:before {
|
||||
content: "\e7da";
|
||||
}
|
||||
|
||||
.icon-kaifazhezhongxin:before {
|
||||
content: "\e70f";
|
||||
}
|
||||
|
||||
.icon-kongzhitai:before {
|
||||
content: "\e651";
|
||||
}
|
||||
|
||||
.icon-baidu:before {
|
||||
content: "\e8cb";
|
||||
}
|
||||
|
||||
.icon-waibulianjie:before {
|
||||
content: "\e858";
|
||||
}
|
||||
|
||||
.icon-zidianguanli:before {
|
||||
content: "\e625";
|
||||
}
|
||||
|
||||
.icon-shujukaifajiaobenkaifa:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.icon-chanpin:before {
|
||||
content: "\e64f";
|
||||
}
|
||||
|
||||
.icon-xiaoshou:before {
|
||||
content: "\e624";
|
||||
}
|
||||
|
||||
.icon-ceshi:before {
|
||||
content: "\e853";
|
||||
}
|
||||
|
||||
.icon-zhuanshujingli:before {
|
||||
content: "\e883";
|
||||
}
|
||||
|
||||
.icon-gongsi:before {
|
||||
content: "\e679";
|
||||
}
|
||||
|
||||
.icon-xitongquanxian:before {
|
||||
content: "\e61e";
|
||||
}
|
||||
|
||||
.icon-rizhi:before {
|
||||
content: "\e647";
|
||||
}
|
||||
|
||||
.icon-yonghuguanli_huaban:before {
|
||||
content: "\e62d";
|
||||
}
|
||||
|
||||
.icon-dingshirenwu:before {
|
||||
content: "\e6a3";
|
||||
}
|
||||
|
||||
.icon-dashboard:before {
|
||||
content: "\e78b";
|
||||
}
|
||||
|
||||
.icon-caidan:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.icon-bumenguanli:before {
|
||||
content: "\e61d";
|
||||
}
|
||||
|
||||
.icon-jiaoseguanli:before {
|
||||
content: "\e621";
|
||||
}
|
||||
|
||||
.icon-xitong:before {
|
||||
content: "\e601";
|
||||
}
|
||||
|
||||
.icon-shouye:before {
|
||||
content: "\e8b9";
|
||||
}
|
||||
|
||||
.icon-guanyu:before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
||||
.icon-DVLINK_daping:before {
|
||||
content: "\e627";
|
||||
}
|
||||
|
||||
.icon-weixin:before {
|
||||
content: "\e656";
|
||||
}
|
||||
|
||||
.icon-QQ:before {
|
||||
content: "\e882";
|
||||
}
|
||||
|
||||
.icon-contentright:before {
|
||||
content: "\e67a";
|
||||
}
|
||||
|
||||
.icon-zhuti:before {
|
||||
content: "\e610";
|
||||
}
|
||||
|
||||
.icon-sousuo:before {
|
||||
content: "\e68a";
|
||||
}
|
||||
|
||||
.icon-xiaoxi:before {
|
||||
content: "\e8be";
|
||||
}
|
||||
|
||||
.icon-zhongyingwen:before {
|
||||
content: "\e605";
|
||||
}
|
||||
|
||||
.icon-fangda:before {
|
||||
content: "\e622";
|
||||
}
|
||||
|
||||
.icon-suoxiao:before {
|
||||
content: "\e62a";
|
||||
}
|
||||
|
|
@ -0,0 +1,268 @@
|
|||
{
|
||||
"id": "4036849",
|
||||
"name": "luozhun",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "3876471",
|
||||
"name": "mysql",
|
||||
"font_class": "mysql",
|
||||
"unicode": "e667",
|
||||
"unicode_decimal": 58983
|
||||
},
|
||||
{
|
||||
"icon_id": "3172487",
|
||||
"name": "RabbitMQ",
|
||||
"font_class": "RabbitMQ",
|
||||
"unicode": "e6a0",
|
||||
"unicode_decimal": 59040
|
||||
},
|
||||
{
|
||||
"icon_id": "18337193",
|
||||
"name": "断路器",
|
||||
"font_class": "duanluqi",
|
||||
"unicode": "e60a",
|
||||
"unicode_decimal": 58890
|
||||
},
|
||||
{
|
||||
"icon_id": "29328030",
|
||||
"name": "白名单",
|
||||
"font_class": "baimingdan",
|
||||
"unicode": "e643",
|
||||
"unicode_decimal": 58947
|
||||
},
|
||||
{
|
||||
"icon_id": "10055646",
|
||||
"name": "VPN网关",
|
||||
"font_class": "VPNwangguan",
|
||||
"unicode": "e7da",
|
||||
"unicode_decimal": 59354
|
||||
},
|
||||
{
|
||||
"icon_id": "1259944",
|
||||
"name": "开发者中心",
|
||||
"font_class": "kaifazhezhongxin",
|
||||
"unicode": "e70f",
|
||||
"unicode_decimal": 59151
|
||||
},
|
||||
{
|
||||
"icon_id": "12975229",
|
||||
"name": "控制台",
|
||||
"font_class": "kongzhitai",
|
||||
"unicode": "e651",
|
||||
"unicode_decimal": 58961
|
||||
},
|
||||
{
|
||||
"icon_id": "18166606",
|
||||
"name": "百度",
|
||||
"font_class": "baidu",
|
||||
"unicode": "e8cb",
|
||||
"unicode_decimal": 59595
|
||||
},
|
||||
{
|
||||
"icon_id": "34201231",
|
||||
"name": "外部链接 ",
|
||||
"font_class": "waibulianjie",
|
||||
"unicode": "e858",
|
||||
"unicode_decimal": 59480
|
||||
},
|
||||
{
|
||||
"icon_id": "5434087",
|
||||
"name": "字典管理",
|
||||
"font_class": "zidianguanli",
|
||||
"unicode": "e625",
|
||||
"unicode_decimal": 58917
|
||||
},
|
||||
{
|
||||
"icon_id": "4773266",
|
||||
"name": "数据开发—脚本开发",
|
||||
"font_class": "shujukaifajiaobenkaifa",
|
||||
"unicode": "e65c",
|
||||
"unicode_decimal": 58972
|
||||
},
|
||||
{
|
||||
"icon_id": "5121534",
|
||||
"name": "产品",
|
||||
"font_class": "chanpin",
|
||||
"unicode": "e64f",
|
||||
"unicode_decimal": 58959
|
||||
},
|
||||
{
|
||||
"icon_id": "11641886",
|
||||
"name": "销售",
|
||||
"font_class": "xiaoshou",
|
||||
"unicode": "e624",
|
||||
"unicode_decimal": 58916
|
||||
},
|
||||
{
|
||||
"icon_id": "16398952",
|
||||
"name": "测试",
|
||||
"font_class": "ceshi",
|
||||
"unicode": "e853",
|
||||
"unicode_decimal": 59475
|
||||
},
|
||||
{
|
||||
"icon_id": "34453374",
|
||||
"name": "专属经理",
|
||||
"font_class": "zhuanshujingli",
|
||||
"unicode": "e883",
|
||||
"unicode_decimal": 59523
|
||||
},
|
||||
{
|
||||
"icon_id": "9592764",
|
||||
"name": "公司",
|
||||
"font_class": "gongsi",
|
||||
"unicode": "e679",
|
||||
"unicode_decimal": 59001
|
||||
},
|
||||
{
|
||||
"icon_id": "8225386",
|
||||
"name": "系统权限",
|
||||
"font_class": "xitongquanxian",
|
||||
"unicode": "e61e",
|
||||
"unicode_decimal": 58910
|
||||
},
|
||||
{
|
||||
"icon_id": "6527123",
|
||||
"name": "日志",
|
||||
"font_class": "rizhi",
|
||||
"unicode": "e647",
|
||||
"unicode_decimal": 58951
|
||||
},
|
||||
{
|
||||
"icon_id": "12753449",
|
||||
"name": "用户管理",
|
||||
"font_class": "yonghuguanli_huaban",
|
||||
"unicode": "e62d",
|
||||
"unicode_decimal": 58925
|
||||
},
|
||||
{
|
||||
"icon_id": "20853327",
|
||||
"name": "定时任务",
|
||||
"font_class": "dingshirenwu",
|
||||
"unicode": "e6a3",
|
||||
"unicode_decimal": 59043
|
||||
},
|
||||
{
|
||||
"icon_id": "4765881",
|
||||
"name": "dashboard",
|
||||
"font_class": "dashboard",
|
||||
"unicode": "e78b",
|
||||
"unicode_decimal": 59275
|
||||
},
|
||||
{
|
||||
"icon_id": "5283349",
|
||||
"name": "菜单",
|
||||
"font_class": "caidan",
|
||||
"unicode": "e65d",
|
||||
"unicode_decimal": 58973
|
||||
},
|
||||
{
|
||||
"icon_id": "6627737",
|
||||
"name": "部门管理",
|
||||
"font_class": "bumenguanli",
|
||||
"unicode": "e61d",
|
||||
"unicode_decimal": 58909
|
||||
},
|
||||
{
|
||||
"icon_id": "7274106",
|
||||
"name": "角色管理",
|
||||
"font_class": "jiaoseguanli",
|
||||
"unicode": "e621",
|
||||
"unicode_decimal": 58913
|
||||
},
|
||||
{
|
||||
"icon_id": "1119109",
|
||||
"name": "系统",
|
||||
"font_class": "xitong",
|
||||
"unicode": "e601",
|
||||
"unicode_decimal": 58881
|
||||
},
|
||||
{
|
||||
"icon_id": "1727423",
|
||||
"name": "204首页",
|
||||
"font_class": "shouye",
|
||||
"unicode": "e8b9",
|
||||
"unicode_decimal": 59577
|
||||
},
|
||||
{
|
||||
"icon_id": "11641882",
|
||||
"name": "关于",
|
||||
"font_class": "guanyu",
|
||||
"unicode": "e623",
|
||||
"unicode_decimal": 58915
|
||||
},
|
||||
{
|
||||
"icon_id": "12769434",
|
||||
"name": "DVLINK_大屏",
|
||||
"font_class": "DVLINK_daping",
|
||||
"unicode": "e627",
|
||||
"unicode_decimal": 58919
|
||||
},
|
||||
{
|
||||
"icon_id": "318438",
|
||||
"name": "weixin",
|
||||
"font_class": "weixin",
|
||||
"unicode": "e656",
|
||||
"unicode_decimal": 58966
|
||||
},
|
||||
{
|
||||
"icon_id": "4936984",
|
||||
"name": "QQ",
|
||||
"font_class": "QQ",
|
||||
"unicode": "e882",
|
||||
"unicode_decimal": 59522
|
||||
},
|
||||
{
|
||||
"icon_id": "128654",
|
||||
"name": "content-right",
|
||||
"font_class": "contentright",
|
||||
"unicode": "e67a",
|
||||
"unicode_decimal": 59002
|
||||
},
|
||||
{
|
||||
"icon_id": "4608986",
|
||||
"name": "主题",
|
||||
"font_class": "zhuti",
|
||||
"unicode": "e610",
|
||||
"unicode_decimal": 58896
|
||||
},
|
||||
{
|
||||
"icon_id": "10452247",
|
||||
"name": "sousuo",
|
||||
"font_class": "sousuo",
|
||||
"unicode": "e68a",
|
||||
"unicode_decimal": 59018
|
||||
},
|
||||
{
|
||||
"icon_id": "11372726",
|
||||
"name": "消息中心",
|
||||
"font_class": "xiaoxi",
|
||||
"unicode": "e8be",
|
||||
"unicode_decimal": 59582
|
||||
},
|
||||
{
|
||||
"icon_id": "7533292",
|
||||
"name": "中英文",
|
||||
"font_class": "zhongyingwen",
|
||||
"unicode": "e605",
|
||||
"unicode_decimal": 58885
|
||||
},
|
||||
{
|
||||
"icon_id": "3278362",
|
||||
"name": "放大",
|
||||
"font_class": "fangda",
|
||||
"unicode": "e622",
|
||||
"unicode_decimal": 58914
|
||||
},
|
||||
{
|
||||
"icon_id": "5698509",
|
||||
"name": "全屏缩小",
|
||||
"font_class": "suoxiao",
|
||||
"unicode": "e62a",
|
||||
"unicode_decimal": 58922
|
||||
}
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 7.6 KiB |
After Width: | Height: | Size: 58 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 9.2 KiB |
After Width: | Height: | Size: 9.9 KiB |
After Width: | Height: | Size: 8.8 KiB |
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 14 KiB |
After Width: | Height: | Size: 9.7 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 8.0 KiB |
After Width: | Height: | Size: 6.7 KiB |
After Width: | Height: | Size: 2.3 MiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 36 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 74 KiB |
|
@ -0,0 +1,468 @@
|
|||
/* flex */
|
||||
.flex-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.flex-justify-between {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-align-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.w-f {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.h-f {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.f-r {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.f-l {
|
||||
float: left;
|
||||
}
|
||||
|
||||
/* clearfix */
|
||||
.clearfix::after {
|
||||
display: block;
|
||||
height: 0;
|
||||
overflow: hidden;
|
||||
clear: both;
|
||||
content: "";
|
||||
}
|
||||
|
||||
/* 文字单行省略号 */
|
||||
.sle {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* 文字多行省略号 */
|
||||
.mle {
|
||||
display: -webkit-box;
|
||||
overflow: hidden;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 2;
|
||||
}
|
||||
|
||||
/* 文字多了自动換行 */
|
||||
.break-word {
|
||||
word-break: break-all;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* fade-transform */
|
||||
.fade-transform-leave-active,
|
||||
.fade-transform-enter-active {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.fade-transform-enter-from {
|
||||
opacity: 0;
|
||||
transition: all 0.2s;
|
||||
transform: translateX(-30px);
|
||||
}
|
||||
|
||||
.fade-transform-leave-to {
|
||||
opacity: 0;
|
||||
transition: all 0.2s;
|
||||
transform: translateX(30px);
|
||||
}
|
||||
|
||||
/* breadcrumb-transform */
|
||||
.breadcrumb-enter-active {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.breadcrumb-enter-from,
|
||||
.breadcrumb-leave-active {
|
||||
opacity: 0;
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
/* 外边距、内边距全局样式 */
|
||||
@for $i from 0 through 40 {
|
||||
.mt#{$i} {
|
||||
margin-top: #{$i}px !important;
|
||||
}
|
||||
.mr#{$i} {
|
||||
margin-right: #{$i}px !important;
|
||||
}
|
||||
.mb#{$i} {
|
||||
margin-bottom: #{$i}px !important;
|
||||
}
|
||||
.ml#{$i} {
|
||||
margin-left: #{$i}px !important;
|
||||
}
|
||||
.pt#{$i} {
|
||||
padding-top: #{$i}px !important;
|
||||
}
|
||||
.pr#{$i} {
|
||||
padding-right: #{$i}px !important;
|
||||
}
|
||||
.pb#{$i} {
|
||||
padding-bottom: #{$i}px !important;
|
||||
}
|
||||
.pl#{$i} {
|
||||
padding-left: #{$i}px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* -- 内外边距 -- */
|
||||
|
||||
.margin-0 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.margin-xs {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.margin-sm {
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
.margin {
|
||||
margin: 15px;
|
||||
}
|
||||
|
||||
.margin-lg {
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.margin-xl {
|
||||
margin: 25px;
|
||||
}
|
||||
|
||||
.margin-top-xs {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.margin-top-sm {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.margin-top {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.margin-top-lg {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.margin-top-xl {
|
||||
margin-top: 25px;
|
||||
}
|
||||
|
||||
.margin-right-xs {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.margin-right-sm {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.margin-right {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.margin-right-lg {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.margin-right-xl {
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.margin-bottom-xs {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.margin-bottom-sm {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.margin-bottom {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.margin-bottom-lg {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.margin-bottom-xl {
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.margin-left-xs {
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
.margin-left-sm {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.margin-left {
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
.margin-left-lg {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.margin-left-xl {
|
||||
margin-left: 25px;
|
||||
}
|
||||
|
||||
.margin-lr-xs {
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.margin-lr-sm {
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.margin-lr {
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.margin-lr-lg {
|
||||
margin-left: 20px;
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
.margin-lr-xl {
|
||||
margin-left: 25px;
|
||||
margin-right: 25px;
|
||||
}
|
||||
|
||||
.margin-tb-xs {
|
||||
margin-top: 5px;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.margin-tb-sm {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.margin-tb {
|
||||
margin-top: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.margin-tb-lg {
|
||||
margin-top: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.margin-tb-xl {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 25px;
|
||||
}
|
||||
|
||||
.padding-0 {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.padding-xs {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.padding-sm {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.padding {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.padding-lg {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.padding-xl {
|
||||
padding: 25px;
|
||||
}
|
||||
|
||||
.padding-top-xs {
|
||||
padding-top: 5px;
|
||||
}
|
||||
|
||||
.padding-top-sm {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.padding-top {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
.padding-top-lg {
|
||||
padding-top: 20px;
|
||||
}
|
||||
|
||||
.padding-top-xl {
|
||||
padding-top: 25px;
|
||||
}
|
||||
|
||||
.padding-right-xs {
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.padding-right-sm {
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.padding-right {
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.padding-right-lg {
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.padding-right-xl {
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
.padding-bottom-xs {
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.padding-bottom-sm {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.padding-bottom {
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.padding-bottom-lg {
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.padding-bottom-xl {
|
||||
padding-bottom: 25px;
|
||||
}
|
||||
|
||||
.padding-left-xs {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.padding-left-sm {
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
.padding-left {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.padding-left-lg {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.padding-left-xl {
|
||||
padding-left: 25px;
|
||||
}
|
||||
|
||||
.padding-lr-xs {
|
||||
padding-left: 5px;
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.padding-lr-sm {
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
|
||||
.padding-lr {
|
||||
padding-left: 15px;
|
||||
padding-right: 15px;
|
||||
}
|
||||
|
||||
.padding-lr-lg {
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
}
|
||||
|
||||
.padding-lr-xl {
|
||||
padding-left: 25px;
|
||||
padding-right: 25px;
|
||||
}
|
||||
|
||||
.padding-tb-xs {
|
||||
padding-top: 5px;
|
||||
padding-bottom: 5px;
|
||||
}
|
||||
|
||||
.padding-tb-sm {
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.padding-tb {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
|
||||
.padding-tb-lg {
|
||||
padding-top: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.padding-tb-xl {
|
||||
padding-top: 25px;
|
||||
padding-bottom: 25px;
|
||||
}
|
||||
|
||||
/*高德地图去水印*/
|
||||
.amap-logo {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.amap-copyright {
|
||||
display: none !important;
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
/*高德地图去水印 结束*/
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width: 5px;
|
||||
height: 12px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
/*浏览器滚动条样式*/
|
||||
::-webkit-scrollbar-thumb {
|
||||
display: block;
|
||||
min-height: 12px;
|
||||
min-width: 8px;
|
||||
border-radius: 6px;
|
||||
background-color: rgb(217, 217, 217);
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
display: block;
|
||||
min-height: 12px;
|
||||
min-width: 8px;
|
||||
border-radius: 6px;
|
||||
background-color: rgb(159, 159, 159);
|
||||
}
|
||||
|
||||
/*浏览器滚动条样式 结束*/
|
|
@ -0,0 +1,4 @@
|
|||
@font-face {
|
||||
font-family: AliBaBaPuHuTi;
|
||||
src: url('../font/AlibabaPuHuiTi-2-65-Medium.ttf');
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
@mixin wh($w, $h) {
|
||||
width: $w;
|
||||
height: $h;
|
||||
}
|
|
@ -0,0 +1,146 @@
|
|||
/* 扩展ant design pro按钮组件颜色 */
|
||||
$--my-antd-important: !important;
|
||||
|
||||
.button-color-dust {
|
||||
color: #ffffff;
|
||||
background-color: #F5222D;
|
||||
border-color: #F5222D;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #ff4d4f $--my-antd-important;
|
||||
border-color: #ff4d4f $--my-antd-important;
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #cf1322 $--my-antd-important;
|
||||
border-color: #cf1322 $--my-antd-important;
|
||||
}
|
||||
}
|
||||
|
||||
.button-color-volcano {
|
||||
color: #ffffff;
|
||||
background-color: #FA541C;
|
||||
border-color: #FA541C;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #ff7a45 $--my-antd-important;
|
||||
border-color: #ff7a45 $--my-antd-important;
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #d4380d $--my-antd-important;
|
||||
border-color: #d4380d $--my-antd-important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-warn {
|
||||
color: #ffffff;
|
||||
background-color: #FAAD14;
|
||||
border-color: #FAAD14;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #ffc53d $--my-antd-important;
|
||||
border-color: #ffc53d $--my-antd-important;
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #d48806 $--my-antd-important;
|
||||
border-color: #d48806 $--my-antd-important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
color: #ffffff;
|
||||
background-color: #52C41A;
|
||||
border-color: #52C41A;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #73d13d $--my-antd-important;
|
||||
border-color: #73d13d $--my-antd-important;
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #389e0d $--my-antd-important;
|
||||
border-color: #389e0d $--my-antd-important;
|
||||
}
|
||||
}
|
||||
|
||||
.button-color-cyan {
|
||||
color: #ffffff;
|
||||
background-color: #13C2C2;
|
||||
border-color: #13C2C2;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #36cfc9 $--my-antd-important;
|
||||
border-color: #36cfc9 $--my-antd-important;
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #08979c $--my-antd-important;
|
||||
border-color: #08979c $--my-antd-important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-daybreak {
|
||||
color: #ffffff;
|
||||
background-color: #1890FF;
|
||||
border-color: #1890FF;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #096dd9 $--my-antd-important;
|
||||
border-color: #096dd9 $--my-antd-important;
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #40a9ff $--my-antd-important;
|
||||
border-color: #40a9ff $--my-antd-important;
|
||||
}
|
||||
}
|
||||
|
||||
.button-color-geekblue {
|
||||
color: #ffffff;
|
||||
background-color: #2F54EB;
|
||||
border-color: #2F54EB;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #1d39c4 $--my-antd-important;
|
||||
border-color: #1d39c4 $--my-antd-important;
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #597ef7 $--my-antd-important;
|
||||
border-color: #597ef7 $--my-antd-important;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-purple {
|
||||
color: #ffffff;
|
||||
background-color: #722ED1;
|
||||
border-color: #722ED1;
|
||||
|
||||
&:hover, &:focus {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #9254de $--my-antd-important;
|
||||
border-color: #9254de $--my-antd-important;
|
||||
}
|
||||
|
||||
&:active, &.active {
|
||||
color: #ffffff $--my-antd-important;
|
||||
background-color: #531dab $--my-antd-important;
|
||||
border-color: #531dab $--my-antd-important;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
/* http://meyerweb.com/eric/tools/css/reset/ */
|
||||
/* v1.0 | 20080212 */
|
||||
|
||||
html, body, div, span, applet, object, iframe,
|
||||
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
|
||||
a, abbr, acronym, address, big, cite, code,
|
||||
del, dfn, em, font, img, ins, kbd, q, s, samp,
|
||||
small, strike, strong, sub, sup, tt, var,
|
||||
b, u, i, center,
|
||||
dl, dt, dd, ol, ul, li,
|
||||
fieldset, form, label, legend,
|
||||
table, caption, tbody, tfoot, thead, tr, th, td {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
outline: 0;
|
||||
font-size: 100%;
|
||||
vertical-align: baseline;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
blockquote, q {
|
||||
quotes: none;
|
||||
}
|
||||
|
||||
blockquote:before, blockquote:after,
|
||||
q:before, q:after {
|
||||
content: none;
|
||||
}
|
||||
|
||||
/* remember to define focus styles! */
|
||||
:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
/* remember to highlight inserts somehow! */
|
||||
ins {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
del {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* tables still need 'cellspacing="0"' in the markup */
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
border-spacing: 0;
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
/* 存放css变量 */
|
||||
$primary-color: red;
|
|
@ -0,0 +1,6 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
|
||||
class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198">
|
||||
<path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path>
|
||||
<path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path>
|
||||
<path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path>
|
||||
</svg>
|
After Width: | Height: | Size: 517 B |
|
@ -0,0 +1,107 @@
|
|||
import axios, { AxiosError, AxiosInstance, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from "axios";
|
||||
import { message } from 'ant-design-vue';
|
||||
import { useUserStore } from "@/stores/modules/userStore";
|
||||
import { useLoadingStore } from "@/stores/modules/loadingStore";
|
||||
import { useRouter } from "vue-router";
|
||||
const router = useRouter();
|
||||
import { LOGIN_ROUTER } from "@/configs";
|
||||
axios.defaults.withCredentials = false;
|
||||
export interface JsonResult<T> {
|
||||
code: number;
|
||||
msg: string;
|
||||
data?: T;
|
||||
|
||||
}
|
||||
|
||||
export interface CustomAxiosRequestConfig extends AxiosRequestConfig {
|
||||
//是否需要全屏禁用
|
||||
loading?: boolean,
|
||||
loadingMessage?: string
|
||||
}
|
||||
|
||||
export interface CustomInternalAxiosRequestConfig extends InternalAxiosRequestConfig {
|
||||
//是否需要全屏禁用
|
||||
loading?: boolean,
|
||||
loadingMessage?: string
|
||||
}
|
||||
|
||||
const axiosConfig: AxiosRequestConfig = {
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
timeout: import.meta.env.VITE_APP_ENV === 'production' ? 10000 : 60000,
|
||||
timeoutErrorMessage: "请求超时......"
|
||||
};
|
||||
|
||||
class RequestHttp {
|
||||
service: AxiosInstance;
|
||||
public constructor(config: AxiosRequestConfig) {
|
||||
this.service = axios.create(config);
|
||||
//1.添加请求拦截
|
||||
this.service.interceptors.request.use((config: CustomInternalAxiosRequestConfig) => {
|
||||
if (config.loading) {
|
||||
useLoadingStore().showLoading(config.loadingMessage);
|
||||
}
|
||||
//默认带上用户token
|
||||
config.headers.set('Authorization', useUserStore().getToken)
|
||||
return config;
|
||||
}, (error: AxiosError): Promise<string> => {
|
||||
useLoadingStore().hideLoading();
|
||||
message.error(error.message).then(() => {
|
||||
})
|
||||
return Promise.reject(error);
|
||||
})
|
||||
//2.添加响应拦截
|
||||
this.service.interceptors.response.use(async (response: AxiosResponse): Promise<any> => {
|
||||
useLoadingStore().hideLoading();
|
||||
const jsonResult: JsonResult<unknown> = response.data;
|
||||
if (jsonResult.code !== 0) {
|
||||
//一些特定的错误需要重新登录
|
||||
if ([-101].includes(jsonResult.code)) {
|
||||
//跳转登录页
|
||||
await useUserStore().resetUserInfo();
|
||||
await message.error(jsonResult.msg)
|
||||
// alert('??')
|
||||
location.reload()
|
||||
// router.push({
|
||||
// path: LOGIN_ROUTER.path
|
||||
// })
|
||||
|
||||
|
||||
} else {
|
||||
await message.error(jsonResult.msg)
|
||||
}
|
||||
return Promise.reject(jsonResult);
|
||||
}
|
||||
return Promise.resolve(jsonResult);
|
||||
}, async (error: AxiosError): Promise<string> => {
|
||||
useLoadingStore().hideLoading();
|
||||
await message.error(error.msg)
|
||||
return Promise.reject(error);
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 常用请求方法封装
|
||||
*/
|
||||
get<T>(url: string, params?: object, _object: CustomAxiosRequestConfig = {}): Promise<JsonResult<T>> {
|
||||
return this.service.get(url, { params, ..._object });
|
||||
}
|
||||
|
||||
post<T>(url: string, params?: object | object[], _object: CustomAxiosRequestConfig = {}): Promise<JsonResult<T>> {
|
||||
return this.service.post(url, params, _object);
|
||||
}
|
||||
|
||||
put<T>(url: string, params?: object | object[], _object: CustomAxiosRequestConfig = {}): Promise<JsonResult<T>> {
|
||||
return this.service.put(url, params, _object);
|
||||
}
|
||||
|
||||
delete<T>(url: string, params?: object, _object: CustomAxiosRequestConfig = {}): Promise<JsonResult<T>> {
|
||||
return this.service.delete(url, { params, ..._object });
|
||||
}
|
||||
|
||||
download(url: string, params?: object, _object: CustomAxiosRequestConfig = {}): Promise<BlobPart> {
|
||||
return this.service.post(url, params, { ..._object, responseType: "blob" });
|
||||
}
|
||||
}
|
||||
|
||||
const api = new RequestHttp(axiosConfig);
|
||||
export default api
|
|
@ -0,0 +1,76 @@
|
|||
<template>
|
||||
<codemirror
|
||||
:model-value="value"
|
||||
@update:modelValue="$emit('update:value', $event)"
|
||||
:autofocus="autofocus"
|
||||
:disabled="disabled"
|
||||
:indent-with-tab="indentWithTab"
|
||||
:tab-size="tabSize"
|
||||
:placeholder="placeholder"
|
||||
:lineWrapping="true"
|
||||
:style="style"
|
||||
:phrases="phrases"
|
||||
:auto-destroy="autoDestroy"
|
||||
:selection="selection"
|
||||
:extensions="extensions"
|
||||
@ready="handleReady"
|
||||
@change="$emit('change', $event)"
|
||||
@focus="$emit('focus', $event)"
|
||||
@blur="$emit('blur', $event)"
|
||||
>
|
||||
</codemirror>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {Codemirror} from 'vue-codemirror'
|
||||
import {Extension, EditorSelection} from "@codemirror/state";
|
||||
import {LanguageSupport} from "@codemirror/language";
|
||||
import {oneDark} from '@codemirror/theme-one-dark'
|
||||
import {computed, shallowRef} from "vue";
|
||||
|
||||
//see https://github.com/surmon-china/vue-codemirror
|
||||
|
||||
const view = shallowRef()
|
||||
const handleReady = (payload: {
|
||||
view: import("@codemirror/view").EditorView;
|
||||
state: import("@codemirror/state").EditorState;
|
||||
container: HTMLDivElement;
|
||||
}) => {
|
||||
view.value = payload.view
|
||||
}
|
||||
|
||||
defineEmits(['update:value', 'ready', 'change', 'focus', 'blur'])
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
value: string;
|
||||
autofocus?: boolean,
|
||||
disabled?: boolean,
|
||||
indentWithTab?: boolean
|
||||
tabSize?: number,
|
||||
placeholder?: string
|
||||
style?: object
|
||||
phrases?: object
|
||||
autoDestroy?: boolean,
|
||||
selection?: EditorSelection
|
||||
lang?: LanguageSupport[]
|
||||
theme?: Extension
|
||||
}>(), {
|
||||
lang: () => {
|
||||
return [];
|
||||
},
|
||||
theme: () => {
|
||||
return oneDark;
|
||||
},
|
||||
tabSize: 2,
|
||||
placeholder: '请输入文字',
|
||||
})
|
||||
|
||||
const extensions = computed(() => {
|
||||
return [...props.lang, props.theme]
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
|
@ -0,0 +1,88 @@
|
|||
// el_drawer_transition.js
|
||||
<template>
|
||||
<el-drawer-transition>
|
||||
<slot />
|
||||
</el-drawer-transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
addClass,
|
||||
removeClass
|
||||
} from 'element-ui/lib/utils/dom'
|
||||
|
||||
export default {
|
||||
name: 'EkDrawerTransition',
|
||||
components: {
|
||||
'el-drawer-transition': {
|
||||
functional: true,
|
||||
render(createElement, context) {
|
||||
const data = {
|
||||
on: {
|
||||
beforeEnter(el) {
|
||||
addClass(el, 'drawer-transition')
|
||||
if (!el.dataset) el.dataset = {}
|
||||
|
||||
el.dataset.oldPaddingLeft = el.style.paddingLeft
|
||||
el.dataset.oldPaddingRight = el.style.paddingRight
|
||||
|
||||
el.style.width = '0'
|
||||
el.style.paddingLeft = 0
|
||||
el.style.paddingRight = 0
|
||||
},
|
||||
enter(el) {
|
||||
el.dataset.oldOverflow = el.style.overflow
|
||||
if (el.scrollWidth !== 0) {
|
||||
el.style.width = el.scrollWidth + 'px'
|
||||
el.style.paddingLeft = el.dataset.oldPaddingLeft
|
||||
el.style.paddingRight = el.dataset.oldPaddingRight
|
||||
} else {
|
||||
el.style.width = ''
|
||||
el.style.paddingLeft = el.dataset.oldPaddingLeft
|
||||
el.style.paddingRight = el.dataset.oldPaddingRight
|
||||
}
|
||||
el.style.overflow = 'hidden'
|
||||
},
|
||||
afterEnter(el) {
|
||||
removeClass(el, 'drawer-transition')
|
||||
el.style.width = ''
|
||||
el.style.overflow = el.dataset.oldOverflow
|
||||
},
|
||||
beforeLeave(el) {
|
||||
if (!el.dataset) el.dataset = {}
|
||||
el.dataset.oldPaddingLeft = el.style.paddingLeft
|
||||
el.dataset.oldPaddingRight = el.style.paddingRight
|
||||
el.dataset.oldOverflow = el.style.overflow
|
||||
|
||||
el.style.width = el.scrollWidth + 'px'
|
||||
el.style.overflow = 'hidden'
|
||||
},
|
||||
leave(el) {
|
||||
if (el.scrollWidth !== 0) {
|
||||
addClass(el, 'drawer-transition')
|
||||
el.style.width = 0
|
||||
el.style.paddingLeft = 0
|
||||
el.style.paddingRight = 0
|
||||
}
|
||||
},
|
||||
afterLeave(el) {
|
||||
removeClass(el, 'drawer-transition')
|
||||
el.style.width = ''
|
||||
el.style.overflow = el.dataset.oldOverflow
|
||||
el.style.paddingLeft = el.dataset.oldPaddingLeft
|
||||
el.style.paddingRight = el.dataset.oldPaddingRight
|
||||
}
|
||||
}
|
||||
}
|
||||
return createElement('transition', data, context.children)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.drawer-transition {
|
||||
transition: 0.3s width ease-in-out, 0.3s padding-left ease-in-out, 0.3s padding-right ease-in-out;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,54 @@
|
|||
<template>
|
||||
<div class="not-container">
|
||||
<img src="@/assets/images/404.png" class="not-img" alt="404"/>
|
||||
<div class="not-detail">
|
||||
<h2>404</h2>
|
||||
<h4>抱歉,您访问的页面不存在~🤷♂️🤷♀️</h4>
|
||||
<a-button type="primary" @click="router.push(INDEX_ROUTER.path)">返回首页</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useRouter} from "vue-router";
|
||||
import {INDEX_ROUTER} from "@/configs";
|
||||
|
||||
const router = useRouter();
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.not-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
.not-img {
|
||||
margin-right: 120px;
|
||||
}
|
||||
|
||||
.not-detail {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
h2,
|
||||
h4 {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 60px;
|
||||
color: red;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin: 30px 0 20px;
|
||||
font-size: 19px;
|
||||
font-weight: normal;
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
|
@ -0,0 +1,189 @@
|
|||
<template>
|
||||
<a-form
|
||||
ref="formProRef"
|
||||
:model="modelValue"
|
||||
v-bind="formProps?.attrs ?? {}"
|
||||
v-on="formProps?.events ?? {}"
|
||||
>
|
||||
<a-row :gutter="gutter">
|
||||
<a-col
|
||||
v-for="(item,field) in formProps?.formItems"
|
||||
:key="field"
|
||||
v-bind="getResponsive(item)"
|
||||
>
|
||||
<a-form-item
|
||||
:name="field"
|
||||
v-bind="item.attrs ?? {}"
|
||||
>
|
||||
<template #label>
|
||||
{{ item.label ?? item.attrs?.label }}
|
||||
<template v-if="item.remarkRender">
|
||||
<a-popover :title="item.label ?? item.attrs?.label" :content="item.remarkRender()">
|
||||
<QuestionCircleOutlined class="margin-left-xs" style="color: red"/>
|
||||
</a-popover>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 自定义的图标选择器-->
|
||||
<SelectIcon
|
||||
v-if="item.type === 'select-icon'"
|
||||
style="width: 100%"
|
||||
v-model:value="modelValue[field]"
|
||||
v-bind="item.component?.attrs ?? {}"
|
||||
/>
|
||||
<!-- ant design 其他组件 -->
|
||||
<a-input
|
||||
v-else-if="item.type==='input'"
|
||||
style="width: 100%"
|
||||
v-model:value="modelValue[field]"
|
||||
v-bind="item.component?.attrs?{...item.component?.attrs,...placeholder(item)}:{...placeholder(item)}"
|
||||
:allowClear="item.component?.attrs?.allowClear ?? true"
|
||||
v-on="item.component?.events ?? {}"
|
||||
/>
|
||||
<a-input-password
|
||||
v-else-if="item.type==='input-password'"
|
||||
style="width: 100%"
|
||||
v-model:value="modelValue[field]"
|
||||
v-bind="item.component?.attrs?{...item.component?.attrs,...placeholder(item)}:{...placeholder(item)}"
|
||||
:allowClear="item.component?.attrs?.allowClear ?? true"
|
||||
v-on="item.component?.events ?? {}"
|
||||
/>
|
||||
<a-textarea
|
||||
v-else-if="item.type==='input-textarea'"
|
||||
style="width: 100%"
|
||||
v-model:value="modelValue[field]"
|
||||
v-bind="item.component?.attrs?{...item.component?.attrs,...placeholder(item)}:{...placeholder(item)}"
|
||||
:allowClear="item.component?.attrs?.allowClear ?? true"
|
||||
v-on="item.component?.events ?? {}"
|
||||
/>
|
||||
<a-input-number
|
||||
v-else-if="item.type==='input-number'"
|
||||
style="width: 100%"
|
||||
v-model:value="modelValue[field]"
|
||||
v-bind="item.component?.attrs?{...item.component?.attrs,...placeholder(item)}:{...placeholder(item)}"
|
||||
:allowClear="item.component?.attrs?.allowClear ?? true"
|
||||
v-on="item.component?.events ?? {}"
|
||||
/>
|
||||
<a-radio-group
|
||||
v-else-if="item.type==='radio-group'"
|
||||
style="width: 100%"
|
||||
v-model:value="modelValue[field]"
|
||||
v-bind="item.component?.attrs?{...item.component?.attrs,...placeholder(item)}:{...placeholder(item)}"
|
||||
v-on="item.component?.events ?? {}"
|
||||
:options="item.options"
|
||||
/>
|
||||
<a-checkbox-group
|
||||
v-else-if="item.type==='checkbox-group'"
|
||||
style="width: 100%"
|
||||
v-model:value="modelValue[field]"
|
||||
v-bind="item.component?.attrs?{...item.component?.attrs,...placeholder(item)}:{...placeholder(item)}"
|
||||
v-on="item.component?.events ?? {}"
|
||||
:options="item.options"
|
||||
/>
|
||||
<a-select
|
||||
v-else-if="item.type==='select'"
|
||||
style="width: 100%"
|
||||
v-model:value="modelValue[field]"
|
||||
v-bind="item.component?.attrs?{...item.component?.attrs,...placeholder(item)}:{...placeholder(item)}"
|
||||
:allowClear="item.component?.attrs?.allowClear ?? true"
|
||||
v-on="item.component?.events ?? {}"
|
||||
:options="item.options"
|
||||
/>
|
||||
<a-tree-select
|
||||
v-else-if="item.type==='tree-select'"
|
||||
style="width: 100%"
|
||||
v-model:value="modelValue[field]"
|
||||
v-bind="item.component?.attrs?{...item.component?.attrs,...placeholder(item)}:{...placeholder(item)}"
|
||||
v-on="item.component?.events ?? {}"
|
||||
:tree-data="item.options"
|
||||
/>
|
||||
<a-cascader
|
||||
v-else-if="item.type ==='cascader'"
|
||||
style="width: 100%"
|
||||
v-model:value="modelValue[field]"
|
||||
v-bind="item.component?.attrs?{...item.component?.attrs,...placeholder(item)}:{...placeholder(item)}"
|
||||
v-on="item.component?.events ?? {}"
|
||||
:options="item.options"/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<slot name="formOperation"></slot>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {FormProItemProps, FormProProps} from "@/components/form/types/FormPro";
|
||||
import {defineAsyncComponent, ref, UnwrapRef} from "vue";
|
||||
import {FormInstance} from "ant-design-vue";
|
||||
import {FormExpose} from "ant-design-vue/es/form/Form";
|
||||
import {InternalNamePath, NamePath, ValidateOptions} from "ant-design-vue/es/form/interface";
|
||||
import {QuestionCircleOutlined} from '@ant-design/icons-vue'
|
||||
|
||||
//异步加载组件
|
||||
const SelectIcon: any = defineAsyncComponent(() => import("@/components/selecticon/SelectIcon.vue"));
|
||||
|
||||
/**
|
||||
* modelValue: 表单绑定的对象
|
||||
* formProps: 表单配置项
|
||||
* grid: 栅格布局
|
||||
* gutter: 栅格布局间隔
|
||||
*/
|
||||
const props = withDefaults(defineProps<{
|
||||
modelValue: Record<string, any>,
|
||||
formProps?: FormProProps | UnwrapRef<FormProProps>,
|
||||
grid?: Grid
|
||||
gutter?: number;
|
||||
}>(), {
|
||||
grid: () => {
|
||||
return {
|
||||
span: 24
|
||||
}
|
||||
},
|
||||
gutter: 10
|
||||
})
|
||||
|
||||
const formProRef = ref<FormInstance>(null)
|
||||
|
||||
const getResponsive = (item: FormProItemProps | UnwrapRef<FormProItemProps>) => {
|
||||
//span优先级高于响应式设置
|
||||
if (item.grid) return item.grid.span ? {span: item.grid.span} : {...item.grid};
|
||||
return {...props.grid}
|
||||
};
|
||||
|
||||
const placeholder = (item: FormProItemProps | UnwrapRef<FormProItemProps>) => {
|
||||
if (["datetimerange", "daterange", "monthrange"].includes(item.component?.attrs?.type)) {
|
||||
return {rangeSeparator: "至", startPlaceholder: "开始时间", endPlaceholder: "结束时间"};
|
||||
}
|
||||
return {
|
||||
placeholder: item.component?.attrs?.placeholder ?? (item.type.startsWith('input') ? `请输入${item.label || item.attrs?.label}` : `请选择${item.label || item.attrs?.label}`)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 暴露表单的属性
|
||||
*/
|
||||
defineExpose<FormExpose>({
|
||||
validate: (nameList?: NamePath[] | string, options?: ValidateOptions) => {
|
||||
return formProRef.value.validate(nameList, options)
|
||||
},
|
||||
resetFields: (name?: NamePath) => {
|
||||
formProRef.value.resetFields(name);
|
||||
},
|
||||
clearValidate: () => {
|
||||
formProRef.value.clearValidate();
|
||||
},
|
||||
getFieldsValue: (nameList?: InternalNamePath[] | true) => {
|
||||
return formProRef.value.getFieldsValue(nameList);
|
||||
},
|
||||
scrollToField: (name: NamePath, options?: {}) => {
|
||||
formProRef.value.scrollToField(name, options);
|
||||
},
|
||||
validateFields: (nameList?: NamePath[] | string, options?: ValidateOptions) => {
|
||||
return formProRef.value.validateFields(nameList, options);
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
|
@ -0,0 +1,38 @@
|
|||
import {FormItemProps, FormProps} from "ant-design-vue";
|
||||
import {FormExpose} from "ant-design-vue/es/form/Form";
|
||||
import {Ref, VNode} from "vue";
|
||||
|
||||
export type FormProItemType = 'input'
|
||||
| 'input-password'
|
||||
| 'input-textarea'
|
||||
| 'input-number'
|
||||
| 'radio-group'
|
||||
| 'select'
|
||||
| 'tree-select'
|
||||
| 'cascader'
|
||||
| 'checkbox-group'
|
||||
| 'select-icon'
|
||||
|
||||
export interface FormProItemProps {
|
||||
type: FormProItemType,
|
||||
//优先级高于 attrs.label
|
||||
label?: string;
|
||||
grid?: Grid,
|
||||
attrs?: FormItemProps
|
||||
component?: {
|
||||
attrs?: {
|
||||
allowClear?: boolean,
|
||||
placeholder?: string,
|
||||
[key: string]: any,
|
||||
}
|
||||
events?: Record<string, Function>
|
||||
},
|
||||
options?: SelectNode<any>[] | TreeNode<any>[] | Ref<SelectNode<any>[]> | Ref<TreeNode<any>[]>,
|
||||
remarkRender?: () => VNode | string
|
||||
}
|
||||
|
||||
export interface FormProProps {
|
||||
attrs?: FormProps,
|
||||
events?: FormExpose,
|
||||
formItems?: Record<string, FormProItemProps>
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<template>
|
||||
<div id="dv-full-screen-container" ref="currentDom">
|
||||
<template v-if="state.ready">
|
||||
<slot></slot>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {useAutoResize} from "./useAutoResize";
|
||||
import {ref} from "vue";
|
||||
|
||||
const currentDom = ref<HTMLElement>(null)
|
||||
const state = useAutoResize(currentDom)
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
#dv-full-screen-container {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
overflow: hidden;
|
||||
transform-origin: left top;
|
||||
z-index: 999;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,20 @@
|
|||
<template>
|
||||
<i :class="`${fontClass}`" :title="title" :style="{fontSize:`${size}px`}"/>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
interface IconFontProps {
|
||||
fontClass: string,
|
||||
size?: number,
|
||||
title?: string
|
||||
}
|
||||
|
||||
withDefaults(defineProps<IconFontProps>(), {
|
||||
size: 25,
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div class="border">
|
||||
<span class="number">
|
||||
<transition-group>
|
||||
<i v-for="(num,index) in numbers" :key="index+'num'" :style="[setPosition(num), setTransition()]"></i>
|
||||
</transition-group>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'DigitalScroll',
|
||||
props: {
|
||||
number: {
|
||||
type: Number,
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
showNumber: '',
|
||||
numbers: []
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
number (number) {
|
||||
this.showNumber = this.number + ''
|
||||
this.numbers = []
|
||||
for (var i = 0; i < this.showNumber.length; i++) {
|
||||
this.numbers.push(parseInt(this.showNumber.charAt(i)))
|
||||
}
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.showNumber = this.number + ''
|
||||
this.numbers = []
|
||||
for (var i = 0; i < this.showNumber.length; i++) {
|
||||
this.numbers.push(parseInt(this.showNumber.charAt(i)))
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
setPosition () {
|
||||
return function (value) {
|
||||
return {backgroundPosition: '0 ' + value * -58 + 'px'}
|
||||
}
|
||||
},
|
||||
setTransition () {
|
||||
return function () {
|
||||
return {transition: 'background-position 1.5s ease 0s'}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.border{
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.border .number i{
|
||||
width: 45px;
|
||||
height: 47px;
|
||||
display: inline-block;
|
||||
/* animation: bounce-in .5s; */
|
||||
/* background: url(../../assets/number.png) no-repeat; */
|
||||
background-position: 0 0;
|
||||
}
|
||||
.v-enter-active, .v-leave-active{
|
||||
transition: background-position 2s ease 2s;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<a-select
|
||||
:value="defaultIcon"
|
||||
:allowClear="allowClear"
|
||||
show-search
|
||||
:filter-option="filterOption"
|
||||
:placeholder="placeholder"
|
||||
@change="selectIcon"
|
||||
>
|
||||
<a-select-option v-for="item in iconList" :value="item.value" :label="item.label">
|
||||
<div class="icon-item">
|
||||
<span>{{ item.label }}</span>
|
||||
<icon-font :font-class="item.value" :size="iconSize"/>
|
||||
</div>
|
||||
</a-select-option>
|
||||
</a-select>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import IconFont from "@/components/iconfont/IconFont.vue";
|
||||
import iconJson from '@/assets/iconfont/iconfont.json'
|
||||
import {computed} from "vue";
|
||||
|
||||
|
||||
interface SelectIconOptions {
|
||||
value?: string;
|
||||
iconSize?: number,
|
||||
allowClear?: boolean;
|
||||
filterable?: boolean,
|
||||
placeholder?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<SelectIconOptions>(), {
|
||||
iconSize: 20,
|
||||
allowClear: true,
|
||||
filterable: true,
|
||||
placeholder: "请选择图标"
|
||||
});
|
||||
|
||||
const defaultIcon = computed(() => {
|
||||
return props.value
|
||||
})
|
||||
|
||||
const emit = defineEmits(["update:value"]);
|
||||
const selectIcon = (iconName: string) => {
|
||||
emit("update:value", iconName);
|
||||
};
|
||||
|
||||
const iconList: SelectNode<string>[] = iconJson.glyphs.map((e: { name: string, font_class: string }) => {
|
||||
return {
|
||||
value: `iconfont ${iconJson.css_prefix_text}${e.font_class}`,
|
||||
label: e.name
|
||||
}
|
||||
});
|
||||
|
||||
const filterOption = (input: string, option: SelectNode<string>) => {
|
||||
return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
|
||||
};
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.icon-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,176 @@
|
|||
<template>
|
||||
<div class="table-pro-content">
|
||||
<div class="card padding" v-if="searchFormProps">
|
||||
<form-pro
|
||||
ref="searchFormRef"
|
||||
:form-props="searchFormProps"
|
||||
v-model="searchParams"
|
||||
:grid="{xs: 24,sm: 12,md: 8,lg: 6,xl: 4}"
|
||||
>
|
||||
<template v-slot:formOperation>
|
||||
<a-space class="f-r margin-right">
|
||||
<a-button type="primary" @click="search">
|
||||
<search-outlined/>
|
||||
搜索
|
||||
</a-button>
|
||||
<a-button danger @click="()=>searchFormRef.resetFields()">
|
||||
<rollback-outlined/>
|
||||
重置
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</form-pro>
|
||||
</div>
|
||||
<div class="card padding margin-top-xs">
|
||||
<div class="flex-justify-between">
|
||||
<slot name="tableHeader" :selectKeys="selectKeys" :selectRows="selectRows"></slot>
|
||||
<div></div>
|
||||
<a-space>
|
||||
<template v-if="!searchFormProps">
|
||||
<a-tooltip>
|
||||
<template #title>刷新数据</template>
|
||||
<a-button shape="circle" @click="requestGetTableData">
|
||||
<ReloadOutlined/>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="isPrinter">
|
||||
<a-tooltip>
|
||||
<template #title>打印数据</template>
|
||||
<a-button shape="circle">
|
||||
<PrinterOutlined/>
|
||||
</a-button>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-table
|
||||
class="margin-top"
|
||||
ref="tableProRef"
|
||||
:row-key="rowKey"
|
||||
v-bind="tableProps?.attrs ?? {}"
|
||||
v-on="tableProps?.events ?? {}"
|
||||
:row-selection="isSelection ? selectionProps ? selectionProps : defaultSelectProps :null"
|
||||
:data-source="dataSource"
|
||||
:loading="loading"
|
||||
:pagination="false"
|
||||
/>
|
||||
<a-pagination
|
||||
v-if="isPagination"
|
||||
class="f-r margin-top margin-right"
|
||||
v-model:current="pageParams.current"
|
||||
v-model:page-size="pageParams.size"
|
||||
:total="pageParams.total"
|
||||
v-bind="paginationProps ?? {}"
|
||||
@change="handleCurrentChange"
|
||||
@showSizeChange="handleSizeChange"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {FormProProps} from "@/components/form/types/FormPro";
|
||||
import {FormExpose} from "ant-design-vue/es/form/Form";
|
||||
import FormPro from "@/components/form/FormPro.vue";
|
||||
import {onMounted, ref, UnwrapRef} from "vue";
|
||||
import {
|
||||
PageResult, TableProExpose,
|
||||
TableProPaginationProps,
|
||||
TableProProps,
|
||||
TableProRowSelect
|
||||
} from "@/components/table/types/TablePro";
|
||||
import {JsonResult} from "@/axios";
|
||||
import {useTable} from "@/hooks/useTable";
|
||||
import {Key} from "ant-design-vue/es/table/interface";
|
||||
import {SearchOutlined, RollbackOutlined, ReloadOutlined, PrinterOutlined} from '@ant-design/icons-vue';
|
||||
|
||||
const selectKeys = ref<Key[]>([])
|
||||
const selectRows = ref<any[]>([])
|
||||
const defaultSelectProps: TableProRowSelect = {
|
||||
type: "checkbox",
|
||||
selectedRowKeys: selectKeys as any,
|
||||
preserveSelectedRowKeys: true,
|
||||
onSelect: (record, selected) => {
|
||||
if (selected) {
|
||||
selectKeys.value.push(record[props.rowKey])
|
||||
selectRows.value.push(record)
|
||||
} else {
|
||||
selectKeys.value.splice(selectKeys.value.findIndex(x => x === record[props.rowKey]));
|
||||
selectRows.value.splice(selectRows.value.findIndex(r => r[props.rowKey] === record[props.rowKey]))
|
||||
}
|
||||
},
|
||||
onChange: (selectedRowKeys, selectedRows) => {
|
||||
selectKeys.value = selectedRowKeys;
|
||||
selectRows.value = selectedRows;
|
||||
},
|
||||
}
|
||||
|
||||
const clearSelect = () => {
|
||||
selectKeys.value = [];
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
rowKey?: Key
|
||||
searchFormProps?: FormProProps | UnwrapRef<FormProProps>,
|
||||
defaultSearchParams?: Record<string, any>,
|
||||
tableProps?: TableProProps | UnwrapRef<TableProProps>,
|
||||
requestAuto?: boolean,
|
||||
requestApi: <T>(params?: Record<string, any>) => Promise<JsonResult<T[] | PageResult<T>>>,
|
||||
requestError?: (errorMsg: any) => void,
|
||||
dataCallback?: <T>(data: T) => T,
|
||||
isPagination?: boolean,
|
||||
paginationProps?: TableProPaginationProps | UnwrapRef<TableProPaginationProps>,
|
||||
isSelection?: boolean
|
||||
selectionProps?: TableProRowSelect | UnwrapRef<TableProRowSelect>,
|
||||
isPrinter?: boolean
|
||||
}>(), {
|
||||
rowKey: 'snowFlakeId',
|
||||
requestAuto: true,
|
||||
isPagination: true,
|
||||
isSelection: false
|
||||
})
|
||||
|
||||
const searchFormRef = ref<FormExpose>(null);
|
||||
const tableProRef = ref<any>(null);
|
||||
|
||||
const searchParams = ref<Record<string, any>>({
|
||||
...props.defaultSearchParams
|
||||
})
|
||||
|
||||
const {
|
||||
loading,
|
||||
dataSource,
|
||||
pageParams,
|
||||
search,
|
||||
requestGetTableData,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
resetState
|
||||
} = useTable(props.requestApi, searchFormRef, searchParams, props.isPagination, props.dataCallback, props.requestError);
|
||||
|
||||
onMounted(() => {
|
||||
props.requestAuto && requestGetTableData(true)
|
||||
})
|
||||
|
||||
defineExpose<TableProExpose>({
|
||||
requestGetTableData,
|
||||
clearSelect,
|
||||
resetTable: () => {
|
||||
searchFormRef.value?.resetFields()
|
||||
resetState();
|
||||
}
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.card {
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e4e7ed;
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0 12px rgba(0, 0, 0, 0.05);
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,25 @@
|
|||
import {PaginationProps, TableProps} from "ant-design-vue";
|
||||
import {TableRowSelection} from "ant-design-vue/lib/table/interface";
|
||||
import {Page} from "@/hooks/useTable";
|
||||
|
||||
export interface TableProProps {
|
||||
attrs?: Partial<Omit<TableProps<any>, "dataSource" | "pagination" | "loading" | "rowSelection">>,
|
||||
events?: Record<string, Function>,
|
||||
}
|
||||
|
||||
export type TableProPaginationProps = Partial<Omit<PaginationProps, "current" | "pageSize" | "total">>;
|
||||
export type TableProRowSelect = TableRowSelection;
|
||||
|
||||
export interface TableProExpose {
|
||||
requestGetTableData: (isInit?: boolean) => Promise<void>,
|
||||
clearSelect: () => void,
|
||||
resetTable: () => void
|
||||
}
|
||||
|
||||
|
||||
export interface PageResult<T> {
|
||||
current: string,
|
||||
records: T[],
|
||||
size: string,
|
||||
total: string
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
import {onMounted, onUnmounted, Ref, ref} from "vue";
|
||||
import {debounce} from "lodash-es";
|
||||
import {observerDomResize} from "@/utils/index";
|
||||
|
||||
export const useAutoResize = (dom: Ref<HTMLElement>) => {
|
||||
|
||||
let domObserver: MutationObserver = null;
|
||||
const state = ref<{
|
||||
width: number,
|
||||
height: number,
|
||||
allWidth: number,
|
||||
scale: 0,
|
||||
ready: boolean
|
||||
}>({
|
||||
width: 0,
|
||||
height: 0,
|
||||
allWidth: 0,
|
||||
scale: 0,
|
||||
ready: false
|
||||
});
|
||||
|
||||
/**
|
||||
* 设置比例尺
|
||||
*/
|
||||
const setAppScale = () => dom.value.style.transform = `scale(${document.body.clientWidth / state.value.allWidth})`
|
||||
|
||||
/**
|
||||
* 初始化宽高
|
||||
*/
|
||||
const initWH = (resize = true) => {
|
||||
state.value.width = dom.value.clientWidth || 0
|
||||
state.value.height = dom.value.clientHeight || 0
|
||||
resize && setAppScale()
|
||||
}
|
||||
|
||||
/**
|
||||
* 加了节流
|
||||
*/
|
||||
const debounceInitWHFun = debounce(initWH, 100)
|
||||
|
||||
/**
|
||||
* 加载完成初始化数据
|
||||
*/
|
||||
onMounted(() => {
|
||||
initWH(false)
|
||||
|
||||
domObserver = observerDomResize(dom.value, debounceInitWHFun)
|
||||
window.addEventListener('resize', debounceInitWHFun as any)
|
||||
|
||||
const {width, height} = screen
|
||||
state.value.allWidth = width
|
||||
dom.value.style.width = `${width}px`
|
||||
dom.value.style.height = `${height}px`
|
||||
|
||||
setAppScale()
|
||||
|
||||
state.value.ready = true
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (!domObserver) return
|
||||
domObserver.disconnect()
|
||||
domObserver.takeRecords()
|
||||
domObserver = null
|
||||
window.removeEventListener('resize', debounceInitWHFun as any)
|
||||
})
|
||||
|
||||
return state
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
export const MENU_TYPE: SelectNode<number>[] = [
|
||||
{
|
||||
value: 0,
|
||||
label: "目录"
|
||||
}, {
|
||||
value: 1,
|
||||
label: "菜单"
|
||||
}
|
||||
]
|
||||
|
||||
export const IS_ENABLE: SelectNode<number>[] = [
|
||||
{
|
||||
value: 0,
|
||||
label: '启用',
|
||||
}, {
|
||||
value: 1,
|
||||
label: '禁用'
|
||||
}
|
||||
]
|
||||
|
||||
export const IS_OR_NOT: SelectNode<number>[] = [
|
||||
{
|
||||
value: 0,
|
||||
label: '是',
|
||||
}, {
|
||||
value: 1,
|
||||
label: '否'
|
||||
}
|
||||
]
|
||||
|
||||
export const CLIENT_TYPE: SelectNode<string>[] = [
|
||||
{
|
||||
value: 'management',
|
||||
label: '管理端'
|
||||
}, {
|
||||
value: 'cloud_control',
|
||||
label: '云控端'
|
||||
}
|
||||
]
|
||||
|
||||
export const SEX: SelectNode<number>[] = [
|
||||
{
|
||||
value: 0,
|
||||
label: '男'
|
||||
}, {
|
||||
value: 1,
|
||||
label: '女'
|
||||
}, {
|
||||
value: 2,
|
||||
label: '隐藏'
|
||||
}
|
||||
]
|
|
@ -0,0 +1,17 @@
|
|||
export const CLIENT_TYPE = "MANAGEMENT";
|
||||
export const INDEX_ROUTER = {
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
meta: {
|
||||
title: '',
|
||||
icon: 'iconfont icon-shouye',
|
||||
},
|
||||
}
|
||||
export const LOGIN_ROUTER = {
|
||||
path: '/login',
|
||||
name: 'login',
|
||||
meta: {
|
||||
title: '登录',
|
||||
},
|
||||
}
|
||||
export const ROUTER_WHITE_LIST: string[] = [LOGIN_ROUTER.path];
|
|
@ -0,0 +1,40 @@
|
|||
import {Directive, DirectiveBinding} from "@vue/runtime-core";
|
||||
import {message} from "ant-design-vue";
|
||||
|
||||
interface ElType extends HTMLElement {
|
||||
copyData: string | number;
|
||||
}
|
||||
|
||||
/**
|
||||
* copy复制指令
|
||||
* <span v-copy>复制内容</span>
|
||||
* <span v-copy="'复制内容'">复制内容</span>
|
||||
*/
|
||||
const vCopy: Directive = {
|
||||
mounted(el: ElType, binding: DirectiveBinding<string | number>) {
|
||||
el.copyData = binding.value || el.innerText
|
||||
el.addEventListener('click', handlerCopy)
|
||||
},
|
||||
updated(el: ElType, binding: DirectiveBinding) {
|
||||
//元素更新的时候,重新赋值
|
||||
el.copyData = binding.value || el.innerText
|
||||
},
|
||||
beforeUnmount(el: ElType) {
|
||||
// 指令与元素解绑的时候,移除事件绑定
|
||||
el.removeEventListener('click', handlerCopy)
|
||||
},
|
||||
}
|
||||
|
||||
function handlerCopy(this: ElType) {
|
||||
if (!this.copyData) {
|
||||
message.warning('暂无复制内容')
|
||||
return
|
||||
}
|
||||
navigator.clipboard.writeText(`${this.copyData}`).then(() => {
|
||||
message.success(`复制 【${this.copyData}】成功!`)
|
||||
}).catch((err) => {
|
||||
message.success(err)
|
||||
})
|
||||
}
|
||||
|
||||
export default vCopy
|
|
@ -0,0 +1,79 @@
|
|||
declare const __APP_ENV: ImportMetaEnv;
|
||||
|
||||
declare interface AppInfo {
|
||||
name: string;
|
||||
appName: string,
|
||||
version: string;
|
||||
description: string
|
||||
lastBuildTime: Date;
|
||||
}
|
||||
|
||||
/* __APP_INFO__ */
|
||||
declare const __APP_INFO: AppInfo;
|
||||
|
||||
/**
|
||||
* 选择
|
||||
*/
|
||||
interface SelectNode<T> {
|
||||
value: T,
|
||||
label: string,
|
||||
orderIndex?: number,
|
||||
extData?: Record<string, any>
|
||||
}
|
||||
|
||||
/**
|
||||
* 树
|
||||
*/
|
||||
interface TreeNode<T> {
|
||||
value: T,
|
||||
parentValue: T,
|
||||
label: string,
|
||||
orderIndex?: number,
|
||||
extData?: Record<string, any>,
|
||||
children?: TreeNode<T>[]
|
||||
}
|
||||
|
||||
interface RouterVo {
|
||||
routerName: string;
|
||||
path: string;
|
||||
redirect: string;
|
||||
type: number;
|
||||
meta: RouterMetaVo;
|
||||
children?: RouterVo[];
|
||||
}
|
||||
|
||||
interface RouterMetaVo {
|
||||
snowFlakeId: number;
|
||||
icon: string;
|
||||
title: string;
|
||||
link: string;
|
||||
isFull: boolean;
|
||||
isKeepAlive: boolean;
|
||||
}
|
||||
|
||||
declare interface Breadcrumb {
|
||||
path: string,
|
||||
label: string,
|
||||
icon?: string
|
||||
}
|
||||
|
||||
declare interface Grid {
|
||||
//栅格占据的列数
|
||||
span?: number;
|
||||
//栅格左侧的间隔格数
|
||||
offset?: number;
|
||||
//栅格向右移动格数
|
||||
push?: number;
|
||||
//栅格向左移动格数
|
||||
pull?: number;
|
||||
//<768px 响应式栅格数或者栅格属性对象
|
||||
xs?: number;
|
||||
//≥768px 响应式栅格数或者栅格属性对象
|
||||
sm?: number;
|
||||
//≥992px 响应式栅格数或者栅格属性对象
|
||||
md?: number;
|
||||
//≥1200px 响应式栅格数或者栅格属性对象
|
||||
lg?: number;
|
||||
//≥1920px 响应式栅格数或者栅格属性对象
|
||||
xl?: number;
|
||||
}
|
|
@ -0,0 +1,148 @@
|
|||
import {ref, Ref} from "vue";
|
||||
import {FormExpose} from "ant-design-vue/es/form/Form";
|
||||
import {JsonResult} from "@/axios";
|
||||
import {PageResult} from "@/components/table/types/TablePro";
|
||||
|
||||
/**
|
||||
* 分页对象
|
||||
*/
|
||||
export interface Page {
|
||||
current: number,
|
||||
size: number,
|
||||
total: number
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页参数
|
||||
*/
|
||||
export interface PageParams {
|
||||
params: Record<string, any>,
|
||||
page: Omit<Page, 'total'>
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param api 查询方法
|
||||
* @param searchFormRef 表单校验
|
||||
* @param searchParams 查询的参数
|
||||
* @param isPageTable 是否分页
|
||||
* @param dataCallBack 查询到数据后的回调
|
||||
* @param requestError 查询出错回调
|
||||
*/
|
||||
export const useTable = (api: <R>(params?: Record<string, any>) => Promise<JsonResult<R[] | PageResult<R>>>,
|
||||
searchFormRef: Ref<FormExpose>,
|
||||
searchParams: Ref<Record<string, any>>,
|
||||
isPageTable: boolean = true,
|
||||
dataCallBack?: (data: Record<string, any>[]) => Record<string, any>[],
|
||||
requestError?: (errorMsg: any) => void) => {
|
||||
|
||||
const dataSource = ref<Record<string, any>[]>([])
|
||||
const loading = ref<boolean>(false);
|
||||
const pageParams = ref<Page>({
|
||||
current: 1,
|
||||
size: 10,
|
||||
total: 0
|
||||
})
|
||||
|
||||
/**
|
||||
* 获取表格数据
|
||||
*/
|
||||
const requestGetTableData = async (isInit: boolean = false) => {
|
||||
try {
|
||||
//校验表单
|
||||
!isInit && await searchFormRef.value?.validate();
|
||||
//组装参数
|
||||
const totalSearchParams: PageParams | Record<string, any> = isPageTable ? {
|
||||
params: searchParams.value,
|
||||
page: {
|
||||
current: pageParams.value.current,
|
||||
size: pageParams.value.size
|
||||
}
|
||||
} : searchParams.value;
|
||||
|
||||
loading.value = true;
|
||||
|
||||
const resp = await api<Record<string, any>>(totalSearchParams);
|
||||
let tableData: Record<string, any>[];
|
||||
|
||||
if (isPageTable) {
|
||||
const {current, records, size, total} = resp.data as PageResult<Record<string, any>>;
|
||||
isPageTable && updatePageParams({
|
||||
current: parseInt(current),
|
||||
size: parseInt(size),
|
||||
total: parseInt(total)
|
||||
});
|
||||
tableData = records;
|
||||
} else {
|
||||
tableData = resp.data as Record<string, any>[]
|
||||
}
|
||||
dataCallBack && (tableData = dataCallBack(tableData));
|
||||
dataSource.value = tableData;
|
||||
} catch (error) {
|
||||
requestError && requestError(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新分页信息
|
||||
*/
|
||||
const updatePageParams = (ps: Page) => {
|
||||
Object.assign(pageParams.value, ps);
|
||||
};
|
||||
|
||||
/**
|
||||
* 重置表格状态 包括 dataSource loading pageParams
|
||||
*/
|
||||
const resetState = () => {
|
||||
dataSource.value = [];
|
||||
loading.value = false;
|
||||
pageParams.value = {
|
||||
current: 1,
|
||||
size: 10,
|
||||
total: 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 表格数据查询 与 requestGetTableData 区别是会将页面重置为1
|
||||
* 如果只做刷新数据请用 requestGetTableData
|
||||
*/
|
||||
const search = async () => {
|
||||
pageParams.value.current = 1;
|
||||
await requestGetTableData();
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 每页条数改变
|
||||
* @param current 当前页
|
||||
* @param {Number} size 页显示数量
|
||||
*/
|
||||
const handleSizeChange = async (current: number, size: number) => {
|
||||
pageParams.value.current = 1;
|
||||
pageParams.value.size = size;
|
||||
await requestGetTableData();
|
||||
};
|
||||
|
||||
/**
|
||||
* @description 当前页改变
|
||||
* @param current 当前页
|
||||
*/
|
||||
const handleCurrentChange = async (current: number) => {
|
||||
pageParams.value.current = current;
|
||||
await requestGetTableData();
|
||||
};
|
||||
|
||||
return {
|
||||
dataSource,
|
||||
loading,
|
||||
pageParams,
|
||||
requestGetTableData,
|
||||
search,
|
||||
handleSizeChange,
|
||||
handleCurrentChange,
|
||||
resetState
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
import { createApp } from 'vue'
|
||||
import App from '@/App.vue'
|
||||
import { initAMapApiLoader } from '@vuemap/vue-amap';
|
||||
import DataVVue3 from '@kjgl77/datav-vue3'
|
||||
import '@vuemap/vue-amap/dist/style.css'
|
||||
initAMapApiLoader({
|
||||
key: 'YOUR_KEY'
|
||||
})
|
||||
import dayjs from 'dayjs';
|
||||
import lodasha from 'lodash';
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import zhCn from 'element-plus/dist/locale/zh-cn.mjs';
|
||||
|
||||
// 高德地图typescript支持
|
||||
import "@amap/amap-jsapi-types";
|
||||
// vue Router
|
||||
import router from "@/router";
|
||||
// pinia store
|
||||
import pinia from "@/stores";
|
||||
|
||||
//浏览器重置样式
|
||||
import '@/assets/scss/reset.scss'
|
||||
//公共样式
|
||||
import '@/assets/scss/common.scss'
|
||||
//ant design 扩展样式
|
||||
import '@/assets/scss/myAntD.scss'
|
||||
// 阿里巴巴普惠体
|
||||
import '@/assets/scss/font.scss'
|
||||
//动画样式库
|
||||
import 'animate.css';
|
||||
// iconfont css
|
||||
import "@/assets/iconfont/iconfont.css";
|
||||
|
||||
//自定义指令
|
||||
import vCopy from "@/directives/copy";
|
||||
const vueApp = createApp(App);
|
||||
vueApp.config.globalProperties.day=dayjs//全局挂载
|
||||
vueApp.config.globalProperties.lodash=lodasha//全局挂载
|
||||
|
||||
vueApp.directive('copy', vCopy)
|
||||
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||
vueApp.component(key, component)
|
||||
}
|
||||
|
||||
vueApp
|
||||
.use(router)
|
||||
.use(DataVVue3)
|
||||
.use(pinia)
|
||||
.use(ElementPlus, {
|
||||
locale: zhCn,
|
||||
})
|
||||
.use(zhCn)
|
||||
.mount('#app')
|
|
@ -0,0 +1,66 @@
|
|||
import {createRouter, createWebHistory,createWebHashHistory} from "vue-router";
|
||||
import {staticRouter, errorRouter} from "@/router/modules/staticRouters";
|
||||
import {LOGIN_ROUTER, ROUTER_WHITE_LIST} from "@/configs";
|
||||
import {useUserStore} from "@/stores/modules/userStore";
|
||||
import {message, Modal} from "ant-design-vue";
|
||||
import {start, close} from '@/utils/nporgress'
|
||||
|
||||
/**
|
||||
* createWebHistory & createWebHashHistory
|
||||
* createWebHistory: 路径不带#号,但需要后端进行配置(如nginx配置:try_files $uri $uri/ /index.html last;)
|
||||
* createWebHashHistory: 路径带#号 这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理,影响SEO
|
||||
*/
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes: [...staticRouter, ...errorRouter],
|
||||
strict: false,
|
||||
scrollBehavior: () => ({left: 0, top: 0})
|
||||
});
|
||||
|
||||
/**
|
||||
* 前置路由守卫
|
||||
*/
|
||||
router.beforeEach(async (to, from, next) => {
|
||||
//开启进度条
|
||||
start();
|
||||
Modal.destroyAll();
|
||||
//判断是不是访问登录页
|
||||
const userStore = useUserStore();
|
||||
// if (to.path.toLocaleLowerCase() === LOGIN_ROUTER.path && userStore.getToken) {
|
||||
// //如果已登录 且访问login页面 直接返回当前页面
|
||||
// await message.warn('当前已登录,请先退出账号');
|
||||
// return next(from.fullPath)
|
||||
// }
|
||||
//判断访问路径是不是白名单
|
||||
if (ROUTER_WHITE_LIST.includes(to.path)) {
|
||||
return next();
|
||||
}
|
||||
//不在白名单内需要查看是否携带token 没有token需要返回登录页进行登录
|
||||
if (!userStore.token) {
|
||||
await message.warn('未找到token,请重新登陆!')
|
||||
return next(LOGIN_ROUTER.path);
|
||||
}
|
||||
|
||||
//放行
|
||||
return next();
|
||||
})
|
||||
|
||||
/**
|
||||
* 路由跳转错误
|
||||
*/
|
||||
router.onError(error => {
|
||||
console.error("路由错误", error.message);
|
||||
close();
|
||||
});
|
||||
|
||||
/**
|
||||
* 后置路由守卫
|
||||
* */
|
||||
router.afterEach((to) => {
|
||||
//动态设置标题
|
||||
const title: string = import.meta.env.VITE_APP_NAME;
|
||||
document.title = to.meta.title ? `${to.meta.title} - ${title}` : title;
|
||||
close()
|
||||
});
|
||||
|
||||
export default router;
|
|
@ -0,0 +1,83 @@
|
|||
import { RouteRecordRaw } from "vue-router";
|
||||
import { INDEX_ROUTER, LOGIN_ROUTER } from "@/configs";
|
||||
|
||||
/**
|
||||
* staticRouter (静态路由)
|
||||
*/
|
||||
export const staticRouter : RouteRecordRaw[] = [
|
||||
{
|
||||
path: "/",
|
||||
redirect: INDEX_ROUTER.path,
|
||||
},
|
||||
{
|
||||
...LOGIN_ROUTER,
|
||||
component: () => import("@/views/login.vue"),
|
||||
},
|
||||
{
|
||||
path:'/index',
|
||||
component: () => import('@/views/index.vue'),
|
||||
redirect: '/index/video',
|
||||
children: [
|
||||
{
|
||||
path: '/index/video',
|
||||
component: () => import('@/views/page/video.vue'),
|
||||
},
|
||||
{
|
||||
path: '/index/indexmin',
|
||||
component: () => import('@/views/page/indexMim.vue'),
|
||||
},
|
||||
{
|
||||
path: '/index/index',
|
||||
component: () => import('@/views/page/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/index/histordata',
|
||||
component: () => import('@/views/page/historicalData.vue'),
|
||||
},
|
||||
{
|
||||
path: '/index/analysis',
|
||||
component: () => import('@/views/page/analysis.vue'),
|
||||
},
|
||||
{
|
||||
path: '/index/command',
|
||||
component: () => import('@/views/page/commandDispatch.vue'),
|
||||
},
|
||||
{
|
||||
path: '/index/information',
|
||||
component: () => import('@/views/page/information.vue'),
|
||||
},
|
||||
{
|
||||
path: '/index/communitymanage',
|
||||
component: () => import('@/views/page/communitymanage.vue'),
|
||||
},
|
||||
{
|
||||
path: '/index/hkplay',
|
||||
component: () => import('@/views/page/hkplay.vue'),
|
||||
},
|
||||
{
|
||||
path: '/index/system',
|
||||
component: () => import('@/views/page/system.vue'),
|
||||
},
|
||||
]
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* errorRouter (错误页面路由)
|
||||
*/
|
||||
export const errorRouter = [
|
||||
{
|
||||
path: "/404",
|
||||
name: "404",
|
||||
component: () => import("@/components/errorMessage/404.vue"),
|
||||
},
|
||||
{
|
||||
path: "/500",
|
||||
name: "500",
|
||||
component: () => import("@/components/errorMessage/500.vue"),
|
||||
},
|
||||
{
|
||||
path: "/:pathMatch(.*)*",
|
||||
component: () => import("@/components/errorMessage/404.vue"),
|
||||
},
|
||||
];
|
|
@ -0,0 +1,8 @@
|
|||
import {createPinia} from "pinia";
|
||||
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||
|
||||
//创建store实例 持久化插件
|
||||
const pinia = createPinia();
|
||||
pinia.use(piniaPluginPersistedstate);
|
||||
|
||||
export default pinia;
|
|
@ -0,0 +1,29 @@
|
|||
import {defineStore} from "pinia";
|
||||
|
||||
|
||||
export interface LoadingStore {
|
||||
loading: boolean,
|
||||
loadingMessage: string
|
||||
}
|
||||
|
||||
export const useLoadingStore = defineStore({
|
||||
id: "loadingStore",
|
||||
state: (): LoadingStore => {
|
||||
return {
|
||||
loading: false,
|
||||
loadingMessage: '加载中...'
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
showLoading(loadingMessage?: string) {
|
||||
if (loadingMessage && loadingMessage.length > 0) {
|
||||
this.loadingMessage = loadingMessage;
|
||||
}
|
||||
this.loading = true;
|
||||
},
|
||||
hideLoading() {
|
||||
this.loading = false;
|
||||
this.loadingMessage = '加载中...'
|
||||
}
|
||||
}
|
||||
})
|
|
@ -0,0 +1,64 @@
|
|||
import {defineStore} from "pinia";
|
||||
import router from "@/router";
|
||||
import {useUserStore} from "@/stores/modules/userStore";
|
||||
|
||||
export interface TabsState {
|
||||
tabList: TabsMenuProps[];
|
||||
}
|
||||
|
||||
export interface TabsMenuProps {
|
||||
icon: string;
|
||||
title: string;
|
||||
name: string;
|
||||
path: string;
|
||||
isKeepAlive: boolean,
|
||||
closable: boolean
|
||||
}
|
||||
|
||||
export const useTabsStore = defineStore({
|
||||
id: "tabs-store",
|
||||
state: (): TabsState => ({
|
||||
tabList: []
|
||||
}),
|
||||
actions: {
|
||||
// 新增tab
|
||||
async addTab(tabItem: TabsMenuProps) {
|
||||
if (this.tabList.every(item => item.path !== tabItem.path)) {
|
||||
this.tabList.push(tabItem);
|
||||
}
|
||||
},
|
||||
// 关闭单个tab
|
||||
async closeTab(tabPath: string, isCurrent: boolean = true) {
|
||||
const tabsList = this.tabList;
|
||||
if (isCurrent) {
|
||||
tabsList.forEach((item, index) => {
|
||||
if (item.path !== tabPath) return;
|
||||
const nextTab = tabsList[index + 1] || tabsList[index - 1];
|
||||
if (!nextTab) return;
|
||||
router.push(nextTab.path);
|
||||
});
|
||||
}
|
||||
this.tabList = tabsList.filter(item => item.path !== tabPath);
|
||||
},
|
||||
// 关闭多个tab
|
||||
async closeMultipleTab(tabPath?: string) {
|
||||
this.tabList = this.tabList.filter(item => {
|
||||
return item.path === tabPath || !item.closable;
|
||||
});
|
||||
},
|
||||
resetTab() {
|
||||
this.tabList = [];
|
||||
},
|
||||
changeTabKeepAlive(tabPath: string) {
|
||||
this.tabList.forEach(e => {
|
||||
if (e.path === tabPath) {
|
||||
e.isKeepAlive = !e.isKeepAlive;
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
persist: {
|
||||
key: 'tabs-store',
|
||||
storage: window.localStorage,
|
||||
}
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
import { defineStore } from "pinia";
|
||||
import jwtDecode from "jwt-decode";
|
||||
import api from "@/axios";
|
||||
import { useTabsStore } from "@/stores/modules/tabsStore";
|
||||
|
||||
export interface UserInfo {
|
||||
snowFlakeId: number;
|
||||
name: string,
|
||||
nickName: string
|
||||
headImage: string;
|
||||
telephone: string;
|
||||
sex: number;
|
||||
email: string;
|
||||
extData: {
|
||||
roleName: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface UserStore {
|
||||
token: string;
|
||||
userInfo: UserInfo;
|
||||
authMenuList: RouterVo[];
|
||||
}
|
||||
|
||||
export const useUserStore = defineStore({
|
||||
id: "userStore",
|
||||
state: (): UserStore => {
|
||||
return {
|
||||
sokentdata: undefined,
|
||||
token: undefined,
|
||||
userInfo: undefined,
|
||||
authMenuList: [],
|
||||
};
|
||||
},
|
||||
actions: {
|
||||
async initAuthRouterList() {
|
||||
const resp = await api.get<RouterVo[]>('/user/get/auth/menus')
|
||||
this.authMenuList = resp.data;
|
||||
},
|
||||
saveUserInfo(token: string): void {
|
||||
|
||||
//保存用户token
|
||||
this.token = token.session;
|
||||
console.log("🚀 ~ saveUserInfo ~ this:", this)
|
||||
//保存用户信息
|
||||
// this.userInfo = jwtDecode<UserInfo>(token);
|
||||
this.userInfo = token;
|
||||
},
|
||||
setwebsoketdata(data) {
|
||||
this.sokentdata = data;
|
||||
},
|
||||
async resetUserInfo() {
|
||||
this.token = undefined;
|
||||
this.userInfo = undefined;
|
||||
this.authMenuList = [];
|
||||
useTabsStore().resetTab();
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
getsoketData: (state): string => state.sokentdata,
|
||||
getToken: (state): string => state.token,
|
||||
getUserInfo: (state): UserInfo => state.userInfo,
|
||||
getBreadcrumbList: state => getAllBreadcrumbList(state.authMenuList),
|
||||
getFlatRouterList: state => getFlatRouterList(state.authMenuList),
|
||||
},
|
||||
persist: {
|
||||
key: "user-stores",
|
||||
storage: window.localStorage,
|
||||
paths: ["token", "userInfo", 'keepAliveNames'],
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 获取所有的面包屑
|
||||
*/
|
||||
const getAllBreadcrumbList = (menuList: RouterVo[], parent: Breadcrumb[] = [], result: { [key: string]: Breadcrumb[] } = {}) => {
|
||||
for (const item of menuList) {
|
||||
result[item.path] = [...parent, { path: item.path, label: item.meta.title, icon: item.meta.icon }];
|
||||
if (item.children) getAllBreadcrumbList(item.children, result[item.path], result);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* 扁平化路由
|
||||
* @param menuList
|
||||
*/
|
||||
const getFlatRouterList = (menuList: RouterVo[]): RouterVo[] => {
|
||||
let newMenuList: RouterVo[] = JSON.parse(JSON.stringify(menuList));
|
||||
return newMenuList.flatMap(item => [item, ...(item.children ? getFlatRouterList(item.children) : [])]);
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
// import AMapLoader from '@amap/amap-jsapi-loader';
|
||||
|
||||
// export type MapPluginsType = 'AMap.Geolocation' | ''
|
||||
// export const mapLoader = (plugins: MapPluginsType[] = []): Promise<any> => {
|
||||
// return new Promise((resolve, reject) => {
|
||||
// AMapLoader.load({
|
||||
// key: import.meta.env.VITE_APP_GAODE_KEY,
|
||||
// version: import.meta.env.VITE_APP_GAODE_VERSION,
|
||||
// plugins: [...plugins]
|
||||
// }).then(AMap => {
|
||||
// resolve(AMap)
|
||||
// }).catch(err => {
|
||||
// console.log(err);
|
||||
// reject(err)
|
||||
// })
|
||||
// })
|
||||
// }
|
|
@ -0,0 +1,54 @@
|
|||
import CryptoJS from "crypto-js";
|
||||
|
||||
const SECRET_KEY = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_APP_CRYPTO_JS_SECRET_KEY);
|
||||
const SECRET_IV = CryptoJS.enc.Utf8.parse(import.meta.env.VITE_APP_CRYPTO_JS_SECRET_IV);
|
||||
|
||||
/**
|
||||
* 加密方法
|
||||
* @param str 需要加密的字符串
|
||||
*/
|
||||
const encryptStr = (str: string): string => {
|
||||
const dataHex = CryptoJS.enc.Utf8.parse(str);
|
||||
const encrypted = CryptoJS.AES.encrypt(dataHex, SECRET_KEY, {
|
||||
iv: SECRET_IV,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
})
|
||||
return encrypted.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密方法
|
||||
* @param encryptStr 加密后的字符串
|
||||
*/
|
||||
const decryptStr = (encryptStr: string): string => {
|
||||
if (!encryptStr) {
|
||||
return null;
|
||||
}
|
||||
const decrypt = CryptoJS.AES.decrypt(encryptStr, SECRET_KEY, {
|
||||
iv: SECRET_IV,
|
||||
mode: CryptoJS.mode.CBC,
|
||||
padding: CryptoJS.pad.Pkcs7
|
||||
});
|
||||
return CryptoJS.enc.Utf8.stringify(decrypt);
|
||||
}
|
||||
|
||||
/**
|
||||
* 加密json数据
|
||||
* @param data json
|
||||
*/
|
||||
const encryptJSON = (data: unknown): string => {
|
||||
return encryptStr(JSON.stringify(data));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密json数据
|
||||
* @param encryptStr 加密后的字符串
|
||||
*/
|
||||
const decryptJSON = <T>(encryptStr: string): T => {
|
||||
return JSON.parse(decryptStr(encryptStr));
|
||||
}
|
||||
|
||||
export default {
|
||||
encryptStr, decryptStr, encryptJSON, decryptJSON
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
export const getCurrentTime = (): string => {
|
||||
const nowDate = new Date();
|
||||
const month: number = nowDate.getMonth() + 1;
|
||||
const date: number = nowDate.getDate();
|
||||
const hours: number = nowDate.getHours();
|
||||
const minutes: number = nowDate.getMinutes();
|
||||
const seconds: number = nowDate.getSeconds();
|
||||
|
||||
return `${nowDate.getFullYear()}
|
||||
-${month < 10 ? '0' + month : month}
|
||||
-${date < 10 ? '0' + date : date}
|
||||
-${hours < 10 ? '0' + hours : hours}
|
||||
-${minutes < 10 ? '0' + minutes : minutes}
|
||||
-${seconds < 10 ? '0' + seconds : seconds}`;
|
||||
}
|
||||
|
||||
export const getWeekDate = (): string => ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"][new Date().getDay()]
|
|
@ -0,0 +1,7 @@
|
|||
export function observerDomResize(dom: HTMLElement, callback: Function): MutationObserver {
|
||||
//@ts-ignore
|
||||
const MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver
|
||||
const observer = new MutationObserver(callback)
|
||||
observer.observe(dom, {attributes: true, attributeFilter: ['style'], attributeOldValue: true})
|
||||
return observer
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import NProgress from 'nprogress'
|
||||
import 'nprogress/nprogress.css'
|
||||
|
||||
NProgress.configure({
|
||||
// 动画方式
|
||||
easing: 'ease',
|
||||
// 递增进度条的速度
|
||||
speed: 1000,
|
||||
// 是否显示加载ico
|
||||
showSpinner: true,
|
||||
// 自动递增间隔
|
||||
trickleSpeed: 200,
|
||||
// 更改启动时使用的最小百分比
|
||||
minimum: 0.3,
|
||||
//指定进度条的父容器
|
||||
parent: 'body',
|
||||
})
|
||||
|
||||
// 打开进度条
|
||||
|
||||
export const start = () => {
|
||||
NProgress.start()
|
||||
}
|
||||
|
||||
// 关闭进度条
|
||||
export const close = () => {
|
||||
NProgress.done()
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
export enum WsMsgType {
|
||||
SIMPLE_MESSAGE = 'SIMPLE_MESSAGE',
|
||||
HEARTBEAT = 'HEARTBEAT'
|
||||
}
|
||||
|
||||
export interface WsMsgDTO<T> {
|
||||
msgType: WsMsgType,
|
||||
data: T,
|
||||
date: Date
|
||||
}
|
||||
|
||||
/**
|
||||
* 组装ws url
|
||||
*/
|
||||
export const wsUrl = (url: string) => {
|
||||
return `wss:${location.host}${import.meta.env.VITE_APP_WS_API}${url}`;
|
||||
}
|
||||
|
||||
export const wsPingMsg: WsMsgDTO<string> = {
|
||||
msgType: WsMsgType.HEARTBEAT,
|
||||
data: 'ping',
|
||||
date: new Date()
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
<template>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -0,0 +1,236 @@
|
|||
<template>
|
||||
<a-modal :title="`${logName} 日志详情记录`" :open="visible" width="80%" @ok="closeLogDetailModal"
|
||||
@cancel="closeLogDetailModal" :centered="true">
|
||||
<table-pro ref="tableRef" :request-api="reqApi" :default-search-params="{logType:0}"
|
||||
:search-form-props="searchFormProps" :table-props="tableProps" :request-auto="false">
|
||||
<template #tableHeader>
|
||||
<a-button @click="exception">模拟异常信息</a-button>
|
||||
</template>
|
||||
</table-pro>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import {
|
||||
Modal
|
||||
} from "ant-design-vue";
|
||||
import TablePro from "@/components/table/TablePro.vue";
|
||||
import api from "@/axios";
|
||||
import {
|
||||
nextTick,
|
||||
ref,
|
||||
watch
|
||||
} from "vue";
|
||||
import {
|
||||
TableProExpose,
|
||||
TableProProps
|
||||
} from "@/components/table/types/TablePro";
|
||||
import {
|
||||
PageParams
|
||||
} from "@/hooks/useTable";
|
||||
import {
|
||||
FormProProps
|
||||
} from "@/components/form/types/FormPro";
|
||||
import CodemirrorPro from "@/components/codemirror/CodemirrorPro.vue";
|
||||
import {
|
||||
json
|
||||
} from "@codemirror/lang-json";
|
||||
import {
|
||||
js_beautify
|
||||
} from 'js-beautify'
|
||||
|
||||
const exception = () => {
|
||||
api.get('/log/exception', {
|
||||
name: '罗准'
|
||||
})
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps < {
|
||||
visible: boolean,
|
||||
logId: string,
|
||||
logName: string
|
||||
} > (), {
|
||||
visible: false
|
||||
})
|
||||
|
||||
watch(() => props.visible, n => {
|
||||
if (n) {
|
||||
nextTick(() => {
|
||||
tableRef.value.requestGetTableData();
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(["update:visible", 'update:logId', 'update:logName']);
|
||||
const closeLogDetailModal = () => {
|
||||
//重置表格参数
|
||||
tableRef.value.resetTable();
|
||||
emit("update:visible", false);
|
||||
emit("update:logId", '');
|
||||
emit("update:logName", '');
|
||||
};
|
||||
|
||||
const tableRef = ref < TableProExpose > (null)
|
||||
const searchFormProps: FormProProps = {
|
||||
formItems: {
|
||||
executeUserName: {
|
||||
type: "input",
|
||||
label: '执行人'
|
||||
},
|
||||
timeConsuming: {
|
||||
type: "input-number",
|
||||
label: '耗时(大于)'
|
||||
},
|
||||
logType: {
|
||||
type: "select",
|
||||
label: '日志类型',
|
||||
component: {
|
||||
attrs: {
|
||||
allowClear: false
|
||||
}
|
||||
},
|
||||
options: [{
|
||||
value: 0,
|
||||
label: '全部'
|
||||
}, {
|
||||
value: 1,
|
||||
label: '成功'
|
||||
}, {
|
||||
value: 2,
|
||||
label: '异常'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
const tableProps: TableProProps = {
|
||||
attrs: {
|
||||
size: "small",
|
||||
bordered: true,
|
||||
rowClassName: (record) => {
|
||||
return (record.exceptionMsg && record.exceptionMsg.length > 0 ? 'table-warn' : null)
|
||||
},
|
||||
columns: [{
|
||||
title: '执行人',
|
||||
dataIndex: 'executeUserName'
|
||||
}, {
|
||||
title: '请求参数',
|
||||
dataIndex: 'reqParams',
|
||||
ellipsis: true,
|
||||
customRender: ({
|
||||
record
|
||||
}) => {
|
||||
return ( <
|
||||
div style = "cursor:pointer;"
|
||||
onDblclick = {
|
||||
() => {
|
||||
Modal.info({
|
||||
title: '请求参数',
|
||||
width: '40%',
|
||||
centered: true,
|
||||
content: < CodemirrorPro
|
||||
value = {
|
||||
js_beautify(record.reqParams)
|
||||
}
|
||||
lang = {
|
||||
[json()]
|
||||
}
|
||||
disabled = {
|
||||
true
|
||||
}
|
||||
/>
|
||||
})
|
||||
}
|
||||
} > {
|
||||
record.reqParams
|
||||
} < /div>
|
||||
)
|
||||
}
|
||||
}, {
|
||||
title: '响应结果',
|
||||
dataIndex: 'respResult',
|
||||
ellipsis: true,
|
||||
customRender: ({
|
||||
record
|
||||
}) => {
|
||||
return ( <
|
||||
div onDblclick = {
|
||||
() => {
|
||||
Modal.info({
|
||||
title: '响应结果',
|
||||
width: '40%',
|
||||
centered: true,
|
||||
content: < CodemirrorPro
|
||||
value = {
|
||||
js_beautify(record.respResult)
|
||||
}
|
||||
lang = {
|
||||
[json()]
|
||||
}
|
||||
disabled = {
|
||||
true
|
||||
}
|
||||
/>
|
||||
})
|
||||
}
|
||||
} > {
|
||||
record.respResult
|
||||
} < /div>
|
||||
)
|
||||
}
|
||||
}, {
|
||||
title: '记录时间',
|
||||
dataIndex: 'recordTime'
|
||||
}, {
|
||||
title: '接口耗时(ms)',
|
||||
dataIndex: 'timeConsuming'
|
||||
}, {
|
||||
title: '异常信息',
|
||||
dataIndex: 'exceptionMsg',
|
||||
ellipsis: true,
|
||||
customRender: ({
|
||||
record
|
||||
}) => {
|
||||
return ( <
|
||||
div style = "cursor:pointer;"
|
||||
onDblclick = {
|
||||
() => {
|
||||
Modal.info({
|
||||
title: '异常信息',
|
||||
width: '50%',
|
||||
centered: true,
|
||||
content: < CodemirrorPro
|
||||
value = {
|
||||
js_beautify(record.exceptionMsg)
|
||||
}
|
||||
disabled = {
|
||||
true
|
||||
}
|
||||
/>
|
||||
})
|
||||
}
|
||||
} > {
|
||||
record.exceptionMsg
|
||||
} < /div>
|
||||
)
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
const reqApi = (params: PageParams) => {
|
||||
//重新组装查询参数
|
||||
return api.post('/log/detail/pager', {
|
||||
page: params.page,
|
||||
params: {
|
||||
...params.params,
|
||||
logId: props.logId
|
||||
}
|
||||
}
|
||||
as PageParams)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.table-warn) td {
|
||||
background-color: #fdf6ec;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,197 @@
|
|||
<template>
|
||||
<a-layout style="height: 100%">
|
||||
<a-layout-sider>
|
||||
<a-menu style="height: 100%" theme="light">
|
||||
<a-menu-item v-for="item in databases" :key="item" @click="getTablesByDataBaseName(item)">
|
||||
{{ item }}
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</a-layout-sider>
|
||||
<a-layout-content class="padding" style="overflow: auto">
|
||||
<table-pro ref="tableRef" row-key="tableName" :request-api="reqApi" :request-auto="false"
|
||||
:table-props="tableProps" :search-form-props="searchFormProps"/>
|
||||
<a-modal title="代码模板选择" v-model:open="tempSelectVisible" @ok="submit" @cancel="closeTempSelectModal">
|
||||
<a-checkbox-group v-model:value="tempParams.templates" :options="tempOptions"/>
|
||||
</a-modal>
|
||||
</a-layout-content>
|
||||
</a-layout>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import api from "@/axios";
|
||||
import {onMounted, ref} from "vue";
|
||||
import {ColumnsType} from "ant-design-vue/lib/table";
|
||||
import {Modal} from "ant-design-vue";
|
||||
import CodemirrorPro from "@/components/codemirror/CodemirrorPro.vue";
|
||||
import {sql} from "@codemirror/lang-sql";
|
||||
import TablePro from "@/components/table/TablePro.vue";
|
||||
import {TableProExpose, TableProProps} from "@/components/table/types/TablePro";
|
||||
import {FormProProps} from "@/components/form/types/FormPro";
|
||||
|
||||
const tempParams = ref<{
|
||||
databaseName: string,
|
||||
tableName: string,
|
||||
templates: string[]
|
||||
}>({
|
||||
databaseName: '',
|
||||
tableName: '',
|
||||
templates: []
|
||||
});
|
||||
const tempOptions: SelectNode<string>[] = [
|
||||
{
|
||||
value: 'entity',
|
||||
label: 'entity'
|
||||
}, {
|
||||
value: 'mapper',
|
||||
label: 'mapper'
|
||||
}, {
|
||||
value: 'service',
|
||||
label: 'service'
|
||||
}, {
|
||||
value: 'serviceImpl',
|
||||
label: 'serviceImpl'
|
||||
},
|
||||
]
|
||||
|
||||
const tempSelectVisible = ref(false)
|
||||
const submit = async () => {
|
||||
await api.download('/generate/database', tempParams.value, {loading: true})
|
||||
closeTempSelectModal();
|
||||
}
|
||||
const closeTempSelectModal = () => {
|
||||
tempParams.value = {
|
||||
templates: [],
|
||||
databaseName: '',
|
||||
tableName: ''
|
||||
}
|
||||
tempSelectVisible.value = false;
|
||||
}
|
||||
|
||||
const databaseName = ref('');
|
||||
const tableRef = ref<TableProExpose>(null)
|
||||
const reqApi = (params: Record<string, any>) => {
|
||||
return api.post<Record<string, any>[]>('/database/list/table/byDatabaseName', {
|
||||
page: params.page,
|
||||
params: {
|
||||
...params.params,
|
||||
databaseName: databaseName.value
|
||||
}
|
||||
})
|
||||
}
|
||||
const searchFormProps: FormProProps = {
|
||||
formItems: {
|
||||
tableName: {
|
||||
type: 'input',
|
||||
label: '表名'
|
||||
}
|
||||
}
|
||||
}
|
||||
const tableProps: TableProProps = {
|
||||
attrs: {
|
||||
columns: [
|
||||
{
|
||||
title: '表名',
|
||||
dataIndex: 'tableName'
|
||||
}, {
|
||||
title: '存储引擎',
|
||||
dataIndex: 'engine'
|
||||
}, {
|
||||
title: '总数据行',
|
||||
dataIndex: 'tableRows'
|
||||
}, {
|
||||
title: '注释',
|
||||
dataIndex: 'tableComment'
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime'
|
||||
}, {
|
||||
title: '修改时间',
|
||||
dataIndex: 'updateTime'
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'opt',
|
||||
customRender: ({record}) => {
|
||||
return (
|
||||
<a-space>
|
||||
<a-button class="btn-success" onclick={() => {
|
||||
tempParams.value = {
|
||||
templates: [],
|
||||
databaseName: record.tableSchema,
|
||||
tableName: record.tableName
|
||||
}
|
||||
tempSelectVisible.value = true;
|
||||
}}>代码生成
|
||||
</a-button>
|
||||
<a-button type="primary" onclick={async () => {
|
||||
const resp = await api.get<Record<string, any>[]>('/database/get/columns/byTableName', {
|
||||
databaseName: record.tableSchema,
|
||||
tableName: record.tableName
|
||||
})
|
||||
Modal.info({
|
||||
title: '表结构',
|
||||
width: '60%',
|
||||
centered: true,
|
||||
content: <a-table row-key="COLUMN_NAME" columns={tableStructureColumns} data-source={resp.data}/>
|
||||
})
|
||||
}}>表结构
|
||||
</a-button>
|
||||
<a-button onclick={async () => {
|
||||
const resp = await api.get<string>('/database/show/createTable/byName', {
|
||||
databaseName: record.tableSchema,
|
||||
tableName: record.tableName
|
||||
})
|
||||
Modal.info({
|
||||
title: '建表语句',
|
||||
width: '60%',
|
||||
centered: true,
|
||||
content: <CodemirrorPro
|
||||
value={resp.data}
|
||||
lang={[sql()]}
|
||||
disabled={true}
|
||||
/>
|
||||
})
|
||||
}}>建表语句
|
||||
</a-button>
|
||||
</a-space>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const tableStructureColumns: ColumnsType = [
|
||||
{
|
||||
title: "字段名",
|
||||
dataIndex: 'columnName'
|
||||
}, {
|
||||
title: '数据类型',
|
||||
dataIndex: 'columnType'
|
||||
}, {
|
||||
title: '可为空',
|
||||
dataIndex: 'isNullable'
|
||||
}, {
|
||||
title: '注释',
|
||||
dataIndex: 'columnComment'
|
||||
}
|
||||
]
|
||||
const databases = ref<string[]>([])
|
||||
const getDataBases = async () => {
|
||||
const resp = await api.get<string[]>('/database/list')
|
||||
databases.value = resp.data
|
||||
}
|
||||
|
||||
const getTablesByDataBaseName = async (name: string) => {
|
||||
databaseName.value = name;
|
||||
await tableRef.value.requestGetTableData()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataBases();
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
|
@ -0,0 +1,9 @@
|
|||
<template>123
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
|
@ -0,0 +1,228 @@
|
|||
<template>
|
||||
<div class="log-content">
|
||||
<table-pro
|
||||
ref="tableRef"
|
||||
:request-api="reqApi"
|
||||
:table-props="tableProps"
|
||||
:search-form-props="formProps"
|
||||
:default-search-params="{isEnable:null}">
|
||||
<template #tableHeader>
|
||||
<a-space>
|
||||
<a-button @click="addLog" class="btn-success">添加日志拦截</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</table-pro>
|
||||
<a-modal
|
||||
:title="logModalTile"
|
||||
v-model:open="logModalVisible"
|
||||
@ok="submit"
|
||||
@cancel="closeLogModal"
|
||||
>
|
||||
<form-pro ref="logModalFormRef" v-model="logModalFormParams" :form-props="logModalFormProps"/>
|
||||
</a-modal>
|
||||
<log-detail v-model:visible="logDetailModalVisible" v-model:log-name="logName" v-model:log-id="logId"/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import TablePro from "@/components/table/TablePro.vue";
|
||||
import api from "@/axios";
|
||||
import {ref, createVNode} from "vue";
|
||||
import {TableProExpose, TableProProps} from "@/components/table/types/TablePro";
|
||||
import {FormProProps} from "@/components/form/types/FormPro";
|
||||
import {IS_ENABLE} from "@/configs/emnus";
|
||||
import FormPro from "@/components/form/FormPro.vue";
|
||||
import {FormExpose} from "ant-design-vue/es/form/Form";
|
||||
import {message} from "ant-design-vue";
|
||||
import {Modal} from "ant-design-vue";
|
||||
import {ExclamationCircleOutlined} from "@ant-design/icons-vue";
|
||||
import LogDetail from "@/views/dev/components/LogDetail.vue";
|
||||
|
||||
const logDetailModalVisible = ref<boolean>(false)
|
||||
const logId = ref<string>('')
|
||||
const logName = ref<string>('')
|
||||
|
||||
const formProps: FormProProps = {
|
||||
formItems: {
|
||||
name: {
|
||||
type: "input",
|
||||
label: '日志名称'
|
||||
},
|
||||
classPath: {
|
||||
type: "input",
|
||||
label: "类路径"
|
||||
},
|
||||
methodName: {
|
||||
type: "input",
|
||||
label: "方法名"
|
||||
},
|
||||
isEnable: {
|
||||
type: "select",
|
||||
label: "是否启用",
|
||||
options: [{value: null, label: '全部'}, ...IS_ENABLE],
|
||||
component: {
|
||||
attrs: {
|
||||
allowClear: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
const tableRef = ref<TableProExpose>(null);
|
||||
const tableProps: TableProProps = {
|
||||
attrs: {
|
||||
columns: [
|
||||
{
|
||||
title: '日志名称',
|
||||
dataIndex: 'name'
|
||||
}, {
|
||||
title: '类路径',
|
||||
dataIndex: 'classPath'
|
||||
}, {
|
||||
title: '方法名',
|
||||
dataIndex: 'methodName'
|
||||
}, {
|
||||
title: '是否启用',
|
||||
dataIndex: 'isEnable',
|
||||
customRender: ({record}) => {
|
||||
for (let i = 0; i < IS_ENABLE.length; i++) {
|
||||
if (IS_ENABLE[i].value === record.isEnable) {
|
||||
return <a-tag color={record.isEnable === 0 ? 'success' : 'error'}>{IS_ENABLE[i].label}</a-tag>
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
title: '备注',
|
||||
dataIndex: 'remark'
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime'
|
||||
}, {
|
||||
title: '创建人',
|
||||
dataIndex: 'createUserName'
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
customRender: ({record}) => {
|
||||
return (
|
||||
<a-space>
|
||||
<a-button class="btn-warn" onclick={() => {
|
||||
logModalTile.value = `${record.name} 信息编辑`
|
||||
logModalFormParams.value = {
|
||||
snowFlakeId: record.snowFlakeId,
|
||||
name: record.name,
|
||||
classPath: record.classPath,
|
||||
methodName: record.methodName,
|
||||
isEnable: record.isEnable,
|
||||
remark: record.remark
|
||||
}
|
||||
logModalVisible.value = true;
|
||||
}}>编辑
|
||||
</a-button>
|
||||
<a-button class="button-color-geekblue" onclick={() => {
|
||||
logId.value = record.snowFlakeId;
|
||||
logName.value = record.name;
|
||||
logDetailModalVisible.value = true;
|
||||
}}>查看日志
|
||||
</a-button>
|
||||
<a-button danger onclick={() => {
|
||||
Modal.confirm({
|
||||
title: '确认删除该日志拦截嘛?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: createVNode('div', {style: 'color:red;'}, '此操作将删除该日志拦截,且无法找回!'),
|
||||
onOk: async () => {
|
||||
const resp = await api.delete<boolean>('/log/delete/byId', {logId: record.snowFlakeId}, {loading: true})
|
||||
message.success(resp.message);
|
||||
await tableRef.value.requestGetTableData();
|
||||
},
|
||||
onCancel() {
|
||||
console.log("取消操作")
|
||||
},
|
||||
})
|
||||
}}>删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
const reqApi = (params: Record<string, any>) => {
|
||||
return api.post('/log/pager', params)
|
||||
}
|
||||
|
||||
const logModalTile = ref('')
|
||||
const logModalVisible = ref(false)
|
||||
const addLog = () => {
|
||||
logModalTile.value = "添加日志拦截"
|
||||
logModalVisible.value = true;
|
||||
}
|
||||
const submit = async () => {
|
||||
await logModalFormRef.value.validate()
|
||||
await api.get('/log/have/classMethod', {
|
||||
classPath: logModalFormParams.value.classPath,
|
||||
methodName: logModalFormParams.value.methodName
|
||||
}, {loading: true})
|
||||
const resp = await api.post('/log/save/or/update', logModalFormParams.value, {loading: true})
|
||||
message.success(resp.message)
|
||||
closeLogModal();
|
||||
tableRef.value.requestGetTableData();
|
||||
}
|
||||
const closeLogModal = () => {
|
||||
logModalFormParams.value = {
|
||||
classPath: '',
|
||||
methodName: '',
|
||||
isEnable: 0
|
||||
}
|
||||
logModalTile.value = ''
|
||||
logModalFormRef.value.clearValidate();
|
||||
logModalVisible.value = false;
|
||||
}
|
||||
|
||||
const logModalFormRef = ref<FormExpose>(null)
|
||||
const logModalFormParams = ref<{
|
||||
snowFlakeId?: string,
|
||||
name?: string,
|
||||
classPath: string,
|
||||
methodName: string,
|
||||
isEnable: number,
|
||||
remark?: string
|
||||
}>({
|
||||
classPath: '',
|
||||
methodName: '',
|
||||
isEnable: 0
|
||||
})
|
||||
const logModalFormProps: FormProProps = {
|
||||
formItems: {
|
||||
name: {
|
||||
type: 'input',
|
||||
label: '日志名称'
|
||||
}, classPath: {
|
||||
type: 'input',
|
||||
label: '类路径',
|
||||
attrs: {
|
||||
rules: {required: true, message: '类路径为必填', trigger: "blur"}
|
||||
}
|
||||
}, methodName: {
|
||||
type: 'input',
|
||||
label: '方法名',
|
||||
attrs: {
|
||||
rules: {required: true, message: '方法名为必填', trigger: "blur"}
|
||||
}
|
||||
}, isEnable: {
|
||||
type: 'radio-group',
|
||||
label: '是否启用',
|
||||
options: IS_ENABLE
|
||||
}, remark: {
|
||||
type: "input-textarea",
|
||||
label: "备注"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
</style>
|
|
@ -0,0 +1,270 @@
|
|||
<template>
|
||||
<div class="menu-content">
|
||||
<table-pro ref="tableRef" :request-api="reqApi" :table-props="tableProps" :is-pagination="false">
|
||||
<template #tableHeader>
|
||||
<a-space>
|
||||
<a-button class="btn-success" @click="addTopMenu">添加顶级菜单</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</table-pro>
|
||||
<a-modal
|
||||
:title="menuModal.title"
|
||||
v-model:open="menuModal.visible"
|
||||
@ok="submit"
|
||||
@cancel="closeModel"
|
||||
>
|
||||
<form-pro ref="menuFormRef" v-model="menuParams" :form-props="menuFormProps"/>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import {createVNode, ref} from "vue";
|
||||
import {FormInstance, message, Modal} from "ant-design-vue";
|
||||
import api from "@/axios";
|
||||
import IconFont from "@/components/iconfont/IconFont.vue";
|
||||
import {ExclamationCircleOutlined} from '@ant-design/icons-vue';
|
||||
import {FormProProps} from "@/components/form/types/FormPro";
|
||||
import FormPro from "@/components/form/FormPro.vue";
|
||||
import {IS_ENABLE, IS_OR_NOT, MENU_TYPE} from "@/configs/emnus";
|
||||
import TablePro from "@/components/table/TablePro.vue";
|
||||
import {TableProExpose, TableProProps} from "@/components/table/types/TablePro";
|
||||
|
||||
const menuFormRef = ref<FormInstance>(null)
|
||||
const menuParams = ref<{
|
||||
snowFlakeId?: string,
|
||||
parentId?: string,
|
||||
title?: string,
|
||||
routerName?: string,
|
||||
path: string,
|
||||
type: number,
|
||||
icon?: string,
|
||||
redirect?: string,
|
||||
link?: string,
|
||||
isEnable?: number,
|
||||
isFull?: number,
|
||||
isKeepAlive?: number,
|
||||
orderIndex: number
|
||||
}>({
|
||||
path: '',
|
||||
type: 0,
|
||||
isEnable: 0,
|
||||
isFull: 1,
|
||||
isKeepAlive: 1,
|
||||
orderIndex: 0
|
||||
})
|
||||
const menuFormProps: FormProProps = {
|
||||
attrs: {
|
||||
labelCol: {style: {width: '100px'}},
|
||||
wrapperCol: {span: 18},
|
||||
labelAlign: "left"
|
||||
},
|
||||
formItems: {
|
||||
title: {
|
||||
type: "input",
|
||||
label: '标题',
|
||||
attrs: {
|
||||
rules: {required: true, message: '请输入标题', trigger: 'change'}
|
||||
}
|
||||
},
|
||||
routerName: {
|
||||
type: "input",
|
||||
label: '路由名字',
|
||||
attrs: {
|
||||
rules: {required: true, message: '请输入路由名称', trigger: 'change'}
|
||||
}
|
||||
},
|
||||
path: {
|
||||
type: "input",
|
||||
label: '菜单路径',
|
||||
attrs: {
|
||||
rules: {required: true, message: '请输入菜单路径', trigger: 'change'}
|
||||
}
|
||||
},
|
||||
type: {
|
||||
type: "radio-group",
|
||||
label: '类型',
|
||||
options: MENU_TYPE
|
||||
},
|
||||
icon: {
|
||||
type: "select-icon",
|
||||
label: '图标',
|
||||
},
|
||||
redirect: {
|
||||
type: "input",
|
||||
label: '重定向路径',
|
||||
},
|
||||
link: {
|
||||
type: "input",
|
||||
label: '外部链接',
|
||||
},
|
||||
isEnable: {
|
||||
type: "radio-group",
|
||||
label: '是否启用',
|
||||
options: IS_ENABLE
|
||||
},
|
||||
isFull: {
|
||||
type: "radio-group",
|
||||
label: '是否全屏',
|
||||
options: IS_OR_NOT
|
||||
},
|
||||
isKeepAlive: {
|
||||
type: "radio-group",
|
||||
label: '是否缓存',
|
||||
options: IS_OR_NOT
|
||||
},
|
||||
orderIndex: {
|
||||
type: "input-number",
|
||||
label: '排序',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const menuModal = ref({
|
||||
title: "添加菜单",
|
||||
visible: false
|
||||
})
|
||||
|
||||
const addTopMenu = () => {
|
||||
menuModal.value = {
|
||||
title: '顶级菜单新增',
|
||||
visible: true
|
||||
}
|
||||
}
|
||||
const addChildrenMenu = (item: any) => {
|
||||
menuParams.value.parentId = item.snowFlakeId;
|
||||
menuModal.value = {
|
||||
title: `${item.title} 子级菜单新增`,
|
||||
visible: true
|
||||
}
|
||||
}
|
||||
const updateMenu = (item: any) => {
|
||||
menuParams.value = {
|
||||
snowFlakeId: item.snowFlakeId,
|
||||
title: item.title,
|
||||
routerName: item.routerName,
|
||||
path: item.path,
|
||||
redirect: item.redirect,
|
||||
type: item.type,
|
||||
icon: item.icon,
|
||||
link: item.link,
|
||||
isFull: item.isFull,
|
||||
isKeepAlive: item.isKeepAlive,
|
||||
isEnable: item.isEnable,
|
||||
orderIndex: item.orderIndex
|
||||
}
|
||||
menuModal.value = {
|
||||
title: `${item.title} 修改`,
|
||||
visible: true
|
||||
}
|
||||
}
|
||||
const closeModel = () => {
|
||||
menuParams.value = {
|
||||
path: null,
|
||||
type: 0,
|
||||
isEnable: 0,
|
||||
isFull: 1,
|
||||
isKeepAlive: 1,
|
||||
orderIndex: 0
|
||||
}
|
||||
menuFormRef.value.clearValidate();
|
||||
menuModal.value.visible = false;
|
||||
}
|
||||
const deleteMenuById = (menuId: string) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除该菜单嘛?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: createVNode('div', {style: 'color:red;'}, '此操作将删除菜单,且无法找回!'),
|
||||
onOk: async () => {
|
||||
const resp = await api.delete<boolean>('/menu/delete/byId', {menuId}, {loading: true})
|
||||
message.success(resp.message);
|
||||
await tableRef.value.requestGetTableData();
|
||||
},
|
||||
onCancel() {
|
||||
console.log("取消操作")
|
||||
},
|
||||
});
|
||||
}
|
||||
const submit = async () => {
|
||||
await menuFormRef.value.validate()
|
||||
const resp = await api.post<boolean>("/menu/save/or/update", {
|
||||
...menuParams.value
|
||||
}, {loading: true, loadingMessage: "请求中..."})
|
||||
message.success(resp.message);
|
||||
closeModel();
|
||||
await tableRef.value.requestGetTableData();
|
||||
}
|
||||
|
||||
const tableRef = ref<TableProExpose>(null)
|
||||
const tableProps: TableProProps = {
|
||||
attrs: {
|
||||
columns: [
|
||||
{
|
||||
title: '标题',
|
||||
dataIndex: 'title',
|
||||
}, {
|
||||
title: '路由名字',
|
||||
dataIndex: 'routerName',
|
||||
}, {
|
||||
title: '图标',
|
||||
dataIndex: 'icon',
|
||||
customRender: ({text}) => {
|
||||
return (<IconFont fontClass={text}></IconFont>)
|
||||
|
||||
}
|
||||
}, {
|
||||
title: '路径',
|
||||
dataIndex: 'path',
|
||||
}, {
|
||||
title: '类型',
|
||||
dataIndex: 'type',
|
||||
customRender: ({text}) => {
|
||||
for (let i = 0; i < MENU_TYPE.length; i++) {
|
||||
if (MENU_TYPE[i].value === text) {
|
||||
return MENU_TYPE[i].label
|
||||
}
|
||||
}
|
||||
return "暂无";
|
||||
}
|
||||
}, {
|
||||
title: '是否启用',
|
||||
dataIndex: 'isEnable',
|
||||
customRender: ({record}) => {
|
||||
for (let i = 0; i < IS_ENABLE.length; i++) {
|
||||
if (IS_ENABLE[i].value === record.isEnable) {
|
||||
return <a-tag color={record.isEnable === 0 ? 'success' : 'error'}>{IS_ENABLE[i].label}</a-tag>
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
customRender: ({record}) => {
|
||||
return (
|
||||
<a-space>
|
||||
{record.type === 0 ? <a-button class="btn-success" onclick={() => {
|
||||
addChildrenMenu(record);
|
||||
}}>添加子节点 </a-button> : ''}
|
||||
<a-button class="btn-warn" onclick={() => {
|
||||
updateMenu(record);
|
||||
}}>修改
|
||||
</a-button>
|
||||
<a-button danger onclick={() => {
|
||||
deleteMenuById(record.snowFlakeId)
|
||||
}}>删除
|
||||
</a-button>
|
||||
</a-space>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
const reqApi = async (params: Record<string, any>) => {
|
||||
return await api.get<any[]>('/menu/list/tree', params)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|
|
@ -0,0 +1,302 @@
|
|||
<template>
|
||||
<div class="role-content">
|
||||
<table-pro ref="tableRef" :request-api="reqApi" :default-search-params="{isEnable:null}"
|
||||
:search-form-props="searchFormProps" :table-props="tableProps">
|
||||
<template #tableHeader>
|
||||
<a-space>
|
||||
<a-button class="btn-success" @click="addRole">添加角色</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</table-pro>
|
||||
<a-modal
|
||||
:title="title"
|
||||
v-model:open="modalVisible"
|
||||
@ok="submit"
|
||||
@cancel="closeModel"
|
||||
>
|
||||
<form-pro ref="roleFormRef" v-model="roleParams" :form-props="roleFormProps"/>
|
||||
</a-modal>
|
||||
<a-modal
|
||||
:title="authModalTitle"
|
||||
v-model:open="authModalVisible"
|
||||
@ok="roleAuthMenusSubmit"
|
||||
@cancel="closeAuthModal"
|
||||
>
|
||||
<a-tree
|
||||
ref="treeRef"
|
||||
checkable
|
||||
v-model:checked-keys="checkMenuIds"
|
||||
:tree-data="roleAuthMenuTreeNodes"
|
||||
defaultExpandAll
|
||||
:field-names="{title:'label',key:'value',children:'children'}"
|
||||
>
|
||||
<template #title="{label,extData}">
|
||||
<label>{{ label }}</label>
|
||||
<icon-font class="margin-left" :font-class="extData.icon" :size="15"/>
|
||||
</template>
|
||||
</a-tree>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import {message, Modal} from "ant-design-vue";
|
||||
import {createVNode, onMounted, ref} from "vue";
|
||||
import api from "@/axios";
|
||||
import {ExclamationCircleOutlined} from "@ant-design/icons-vue";
|
||||
import IconFont from "@/components/iconfont/IconFont.vue";
|
||||
import {intersection} from 'lodash-es'
|
||||
import FormPro from "@/components/form/FormPro.vue";
|
||||
import {FormProProps} from "@/components/form/types/FormPro";
|
||||
import {CLIENT_TYPE, IS_ENABLE} from "@/configs/emnus";
|
||||
import {FormExpose} from "ant-design-vue/es/form/Form";
|
||||
import TablePro from "@/components/table/TablePro.vue";
|
||||
import {TableProExpose, TableProProps} from "@/components/table/types/TablePro";
|
||||
|
||||
const checkMenuIds = ref<string[]>([])
|
||||
const authModalTitle = ref('');
|
||||
const authModalVisible = ref(false)
|
||||
let authModalRoleId: string = '';
|
||||
const treeRef = ref<{
|
||||
checkedKeys: string[],
|
||||
halfCheckedKeys: string[]
|
||||
}>(null)
|
||||
const roleAuthMenusSubmit = async () => {
|
||||
const resp = await api.post<boolean>('/role/auth/menus', {
|
||||
roleId: authModalRoleId,
|
||||
menuIds: treeRef.value.checkedKeys.concat(treeRef.value.halfCheckedKeys)
|
||||
})
|
||||
message.success(resp.message)
|
||||
closeAuthModal()
|
||||
}
|
||||
const closeAuthModal = () => {
|
||||
authModalRoleId = '';
|
||||
authModalTitle.value = '';
|
||||
checkMenuIds.value = [];
|
||||
authModalVisible.value = false;
|
||||
}
|
||||
const roleAuthMenuTreeNodes = ref<TreeNode<string>[]>([])
|
||||
const getRoleAuthMenuTree = async () => {
|
||||
const resp = await api.get<TreeNode<string>[]>('/menu/list/role/auth/menu/tree')
|
||||
roleAuthMenuTreeNodes.value = resp.data;
|
||||
}
|
||||
|
||||
const roleFormRef = ref<FormExpose>(null)
|
||||
const roleParams = ref<{
|
||||
snowFlakeId?: string,
|
||||
name?: string,
|
||||
clientType?: string,
|
||||
remark?: string,
|
||||
isEnable?: number
|
||||
}>({
|
||||
clientType: 'management',
|
||||
isEnable: 0
|
||||
})
|
||||
const roleFormProps: FormProProps = {
|
||||
attrs: {
|
||||
labelCol: {style: {width: '100px'}},
|
||||
wrapperCol: {span: 18},
|
||||
labelAlign: "left"
|
||||
}, formItems: {
|
||||
name: {
|
||||
type: "input",
|
||||
label: '角色名字',
|
||||
attrs: {
|
||||
rules: {required: true, message: '请输入角色名称', trigger: 'change'}
|
||||
}
|
||||
},
|
||||
clientType: {
|
||||
type: "radio-group",
|
||||
label: '客户端',
|
||||
options: CLIENT_TYPE
|
||||
},
|
||||
remark: {
|
||||
type: "input-textarea",
|
||||
label: '备注'
|
||||
},
|
||||
isEnable: {
|
||||
type: "input",
|
||||
label: '是否启用',
|
||||
options: IS_ENABLE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const addRole = () => {
|
||||
title.value = "角色新增"
|
||||
modalVisible.value = true;
|
||||
}
|
||||
const updateRole = (role: any) => {
|
||||
title.value = `${role.name} 信息编辑`
|
||||
roleParams.value = {
|
||||
snowFlakeId: role.snowFlakeId,
|
||||
name: role.name,
|
||||
clientType: role.clientType,
|
||||
remark: role.remark,
|
||||
isEnable: role.isEnable
|
||||
}
|
||||
modalVisible.value = true;
|
||||
}
|
||||
const deleteRole = async (roleId: string) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除该角色嘛?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: createVNode('div', {style: 'color:red;'}, '此操作将删除角色,且无法找回!'),
|
||||
onOk: async () => {
|
||||
const resp = await api.delete('/role/delete/byId', {roleId}, {loading: true})
|
||||
message.success(resp.message);
|
||||
await tableRef.value.requestGetTableData();
|
||||
},
|
||||
onCancel() {
|
||||
console.log("取消操作")
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const title = ref("")
|
||||
const modalVisible = ref(false);
|
||||
const closeModel = () => {
|
||||
roleFormRef.value.clearValidate();
|
||||
roleParams.value = {
|
||||
clientType: 'management',
|
||||
isEnable: 0
|
||||
}
|
||||
modalVisible.value = false;
|
||||
}
|
||||
const submit = async () => {
|
||||
await roleFormRef.value.validate()
|
||||
const resp = await api.post<boolean>('/role/save/or/update', {
|
||||
...roleParams.value
|
||||
}, {loading: true, loadingMessage: '请求中...'})
|
||||
message.success(resp.message)
|
||||
closeModel();
|
||||
await tableRef.value.requestGetTableData();
|
||||
}
|
||||
|
||||
const tableRef = ref<TableProExpose>(null)
|
||||
const reqApi = (params: Record<string, any>) => {
|
||||
return api.post('/role/pager', params)
|
||||
}
|
||||
const searchFormProps: FormProProps = {
|
||||
formItems: {
|
||||
name: {
|
||||
type: "input",
|
||||
label: '角色名'
|
||||
},
|
||||
clientType: {
|
||||
type: "select",
|
||||
label: "客户端",
|
||||
options: [
|
||||
{
|
||||
value: null,
|
||||
label: '全部'
|
||||
}, ...CLIENT_TYPE
|
||||
]
|
||||
},
|
||||
isEnable: {
|
||||
type: "select",
|
||||
label: "是否启用",
|
||||
options: [
|
||||
{
|
||||
value: null,
|
||||
label: "全部"
|
||||
}, ...IS_ENABLE
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
const tableProps: TableProProps = {
|
||||
attrs: {
|
||||
columns: [
|
||||
{
|
||||
title: '角色名字',
|
||||
dataIndex: 'name'
|
||||
}, {
|
||||
title: '所属客户端',
|
||||
dataIndex: 'clientType',
|
||||
customRender: ({text}) => {
|
||||
for (let i = 0; i < CLIENT_TYPE.length; i++) {
|
||||
if (CLIENT_TYPE[i].value === text) {
|
||||
return CLIENT_TYPE[i].label;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
title: '备注',
|
||||
dataIndex: 'remark'
|
||||
}, {
|
||||
title: '是否启用',
|
||||
dataIndex: 'isEnable',
|
||||
customRender: ({text}) => {
|
||||
for (let i = 0; i < IS_ENABLE.length; i++) {
|
||||
if (IS_ENABLE[i].value === text) {
|
||||
return IS_ENABLE[i].label;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
title: '创建时间',
|
||||
dataIndex: 'createTime'
|
||||
}, {
|
||||
title: '操作',
|
||||
dataIndex: 'operation',
|
||||
customRender: ({record}) => {
|
||||
return (
|
||||
<div>
|
||||
{record.snowFlakeId === '1' ? '' : <a-space>
|
||||
<a-button class="btn-warn" onclick={() => {
|
||||
updateRole(record);
|
||||
}}>编辑
|
||||
</a-button>
|
||||
<a-button class="btn-purple" onclick={async () => {
|
||||
if (record.clientType != "management") {
|
||||
message.warn('此页面权限只支持管理端')
|
||||
return
|
||||
}
|
||||
authModalTitle.value = `${record.name} 角色权限编辑`
|
||||
authModalRoleId = record.snowFlakeId;
|
||||
//查询角色下的menuID 注意:包含父节点id,需要进行处理
|
||||
const resp = await api.get<string[]>('/role/get/auth/menuIds', {
|
||||
roleId: authModalRoleId
|
||||
})
|
||||
const leafIds: string[] = [];
|
||||
//获取整个tree的叶子节点id
|
||||
const deepList = (data: TreeNode<string>[]) => {
|
||||
data.forEach(e => {
|
||||
if (e.children && e.children.length > 0) {
|
||||
deepList(e.children);
|
||||
} else {
|
||||
leafIds.push(e.value);
|
||||
}
|
||||
})
|
||||
}
|
||||
deepList(roleAuthMenuTreeNodes.value);
|
||||
//根据得到的叶子节点跟角色的menuIds取交集
|
||||
checkMenuIds.value = intersection(leafIds, resp.data)
|
||||
authModalVisible.value = true;
|
||||
}}>权限
|
||||
</a-button>
|
||||
<a-button danger onclick={() => {
|
||||
deleteRole(record.snowFlakeId);
|
||||
}}>删除
|
||||
</a-button>
|
||||
</a-space>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getRoleAuthMenuTree()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.role-content {
|
||||
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,312 @@
|
|||
<template>
|
||||
<div class="user-content">
|
||||
<table-pro ref="tableRef" :table-props="tableProps" :request-api="reqApi"
|
||||
:default-search-params="{sex:null,isEnable:null}"
|
||||
:search-form-props="searchFormProps">
|
||||
<template #tableHeader>
|
||||
<a-space>
|
||||
<a-button class="btn-success" @click="addUser">添加用户</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</table-pro>
|
||||
<a-modal
|
||||
v-model:open="userModalVisible"
|
||||
:title="userModalTitle"
|
||||
@ok="submit"
|
||||
@cancel="closeUserModal"
|
||||
>
|
||||
<form-pro ref="userFormRef" v-model="userFormParams" :form-props="userFormProps"/>
|
||||
</a-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import {createVNode, ref} from "vue";
|
||||
import {FormInstance, message, Modal} from "ant-design-vue";
|
||||
import api from "@/axios";
|
||||
import {uuid} from "vue-uuid";
|
||||
import {ExclamationCircleOutlined} from "@ant-design/icons-vue";
|
||||
import {FormProProps} from "@/components/form/types/FormPro";
|
||||
import FormPro from "@/components/form/FormPro.vue";
|
||||
import {CLIENT_TYPE, IS_ENABLE, SEX} from "@/configs/emnus";
|
||||
import TablePro from "@/components/table/TablePro.vue";
|
||||
import {TableProExpose, TableProProps} from "@/components/table/types/TablePro";
|
||||
|
||||
const roleSelectOptions = ref<SelectNode<string>[]>([])
|
||||
|
||||
const addUser = async () => {
|
||||
await getRoleByClientType();
|
||||
userModalTitle.value = '新增用户'
|
||||
userModalVisible.value = true;
|
||||
}
|
||||
const userFormRef = ref<FormInstance>(null)
|
||||
const userFormParams = ref<{
|
||||
snowFlakeId?: string,
|
||||
name: string,
|
||||
userName: string,
|
||||
nickName?: string,
|
||||
telephone?: string,
|
||||
clientType?: 'management' | 'cloud_control',
|
||||
sex: number,
|
||||
email?: string,
|
||||
info?: string,
|
||||
isEnable: number,
|
||||
roleId?: string
|
||||
}>({
|
||||
name: '',
|
||||
sex: 0,
|
||||
isEnable: 0,
|
||||
userName: uuid.v1(),
|
||||
clientType: "management"
|
||||
})
|
||||
const userFormProps = ref<FormProProps>({
|
||||
attrs: {
|
||||
labelCol: {style: {width: '100px'}},
|
||||
wrapperCol: {span: 18},
|
||||
labelAlign: "left"
|
||||
},
|
||||
formItems: {
|
||||
name: {
|
||||
type: "input",
|
||||
label: "用户名",
|
||||
remarkRender: () => {
|
||||
return <div>123</div>
|
||||
}
|
||||
},
|
||||
userName: {
|
||||
type: "input",
|
||||
label: "账号"
|
||||
},
|
||||
nickName: {
|
||||
type: "input",
|
||||
label: "昵称"
|
||||
},
|
||||
telephone: {
|
||||
type: "input",
|
||||
label: "手机号"
|
||||
},
|
||||
sex: {
|
||||
type: "radio-group",
|
||||
label: "性别",
|
||||
options: SEX
|
||||
},
|
||||
email: {
|
||||
type: "input",
|
||||
label: "邮箱",
|
||||
},
|
||||
isEnable: {
|
||||
type: "radio-group",
|
||||
label: "是否启用",
|
||||
options: IS_ENABLE
|
||||
},
|
||||
clientType: {
|
||||
type: "radio-group",
|
||||
label: "客户端",
|
||||
options: CLIENT_TYPE,
|
||||
component: {
|
||||
events: {
|
||||
change: () => {
|
||||
userFormParams.value.roleId = null;
|
||||
getRoleByClientType()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
roleId: {
|
||||
type: "select",
|
||||
label: "角色",
|
||||
options: roleSelectOptions,
|
||||
attrs: {
|
||||
rules: {required: true, message: '角色为必选', trigger: 'change'}
|
||||
}
|
||||
},
|
||||
info: {
|
||||
type: "input-textarea",
|
||||
label: "介绍"
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const getRoleByClientType = async () => {
|
||||
const resp = await api.get<SelectNode<string>[]>('/role/get/byClientType', {
|
||||
clientType: userFormParams.value.clientType
|
||||
})
|
||||
roleSelectOptions.value = resp.data;
|
||||
}
|
||||
|
||||
const userModalTitle = ref<string>('')
|
||||
const userModalVisible = ref(false)
|
||||
|
||||
const submit = async () => {
|
||||
await userFormRef.value.validate();
|
||||
const resp = await api.post<boolean>('/user/save/or/update', {
|
||||
...userFormParams.value
|
||||
}, {loading: true})
|
||||
message.success(resp.message);
|
||||
closeUserModal();
|
||||
await tableRef.value.requestGetTableData();
|
||||
}
|
||||
const closeUserModal = () => {
|
||||
userFormRef.value.clearValidate();
|
||||
userModalTitle.value = ''
|
||||
userModalVisible.value = false;
|
||||
userFormParams.value = {
|
||||
name: '',
|
||||
sex: 0,
|
||||
isEnable: 0,
|
||||
userName: uuid.v1(),
|
||||
clientType: "management"
|
||||
}
|
||||
}
|
||||
|
||||
const tableRef = ref<TableProExpose>(null)
|
||||
const reqApi = (params: Record<string, any>) => {
|
||||
return api.post('/user/pager', params)
|
||||
}
|
||||
const searchFormProps: FormProProps = {
|
||||
formItems: {
|
||||
name: {
|
||||
type: "input",
|
||||
label: "用户名"
|
||||
},
|
||||
telephone: {
|
||||
type: "input",
|
||||
label: "手机号"
|
||||
},
|
||||
sex: {
|
||||
type: "select",
|
||||
label: '性别',
|
||||
options: [
|
||||
{
|
||||
value: null,
|
||||
label: "全部"
|
||||
}, ...SEX
|
||||
]
|
||||
},
|
||||
email: {
|
||||
type: "input",
|
||||
label: "邮箱"
|
||||
},
|
||||
isEnable: {
|
||||
type: "select",
|
||||
label: "是否启用",
|
||||
options: [
|
||||
{
|
||||
value: null,
|
||||
label: '全部'
|
||||
}, ...IS_ENABLE
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
const tableProps: TableProProps = {
|
||||
attrs: {
|
||||
columns: [
|
||||
{
|
||||
dataIndex: 'name',
|
||||
title: '用户名'
|
||||
}, {
|
||||
dataIndex: 'userName',
|
||||
title: '账号'
|
||||
}, {
|
||||
dataIndex: 'telephone',
|
||||
title: '手机号'
|
||||
}, {
|
||||
dataIndex: 'sex',
|
||||
title: '性别',
|
||||
customRender: ({record}) => {
|
||||
for (let i = 0; i < SEX.length; i++) {
|
||||
if (SEX[i].value === record.sex) {
|
||||
return <a-tag
|
||||
color={record.sex == 0 ? 'success' : record.sex === 1 ? 'magenta' : ''}>{SEX[i].label}</a-tag>
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
dataIndex: 'isEnable',
|
||||
title: '是否启用',
|
||||
customRender: ({record}) => {
|
||||
for (let i = 0; i < IS_ENABLE.length; i++) {
|
||||
if (IS_ENABLE[i].value === record.isEnable) {
|
||||
return <a-tag color={record.isEnable === 0 ? 'success' : 'error'}>{IS_ENABLE[i].label}</a-tag>
|
||||
}
|
||||
}
|
||||
}
|
||||
}, {
|
||||
dataIndex: 'roleName',
|
||||
title: '角色名字'
|
||||
}, {
|
||||
dataIndex: 'operation',
|
||||
title: '操作',
|
||||
customRender: ({record}) => {
|
||||
return (
|
||||
<div>
|
||||
{record.snowFlakeId === '1' ? '' : <a-space>
|
||||
<a-button class="btn-warn" onclick={async () => {
|
||||
userModalTitle.value = `${record.name} 用户信息编辑`
|
||||
userFormParams.value = {
|
||||
snowFlakeId: record.snowFlakeId,
|
||||
name: record.name,
|
||||
userName: record.userName,
|
||||
nickName: record.nickName,
|
||||
telephone: record.telephone,
|
||||
clientType: record.clientType,
|
||||
sex: record.sex,
|
||||
email: record.email,
|
||||
info: record.info,
|
||||
isEnable: record.isEnable,
|
||||
roleId: record.roleId
|
||||
}
|
||||
await getRoleByClientType();
|
||||
userModalVisible.value = true;
|
||||
}}>编辑
|
||||
</a-button>
|
||||
<a-button danger onclick={() => {
|
||||
Modal.confirm({
|
||||
title: '确认删除该用户嘛?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: createVNode('div', {style: 'color:red;'}, '此操作将删除用户,且无法找回!'),
|
||||
onOk: async () => {
|
||||
const resp = await api.delete<boolean>('/user/delete/byId', {
|
||||
userId: record.snowFlakeId
|
||||
}, {loading: true})
|
||||
message.success(resp.message);
|
||||
await tableRef.value.requestGetTableData();
|
||||
},
|
||||
onCancel() {
|
||||
console.log("取消操作")
|
||||
},
|
||||
});
|
||||
}}>删除
|
||||
</a-button>
|
||||
<a-button onclick={() => {
|
||||
Modal.confirm({
|
||||
title: '确认重置用户密码?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
content: createVNode('div', {style: 'color:red;'}, '此操作将重置用户密码!'),
|
||||
onOk: async () => {
|
||||
const resp = await api.get<boolean>('/user/reset/password', {
|
||||
userId: record.snowFlakeId
|
||||
}, {loading: true})
|
||||
message.success(resp.message);
|
||||
await tableRef.value.requestGetTableData()
|
||||
},
|
||||
onCancel() {
|
||||
console.log("取消操作")
|
||||
},
|
||||
});
|
||||
}}>重置密码
|
||||
</a-button>
|
||||
</a-space>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
</style>
|