初始化

This commit is contained in:
libingtao 2024-07-22 11:11:43 +08:00
parent e29c1afd39
commit e596818270
164 changed files with 258753 additions and 3 deletions

19
.env.development Normal file
View File

@ -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

19
.env.production Normal file
View File

@ -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

50
.gitignore vendored Normal file
View File

@ -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

View File

@ -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结尾
```
- 项目严重不推荐在开发过程中产生的红色错误或者黄色警告 如有 请自行解决
- 代码规范整洁

4842
analyze.html Normal file

File diff suppressed because one or more lines are too long

40
components.d.ts vendored Normal file
View File

@ -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']
}
}

313
h5player.min.js vendored Normal file

File diff suppressed because one or more lines are too long

19
index.html Normal file
View File

@ -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>

2
jquery-3.4.1.min.js vendored Normal file

File diff suppressed because one or more lines are too long

4666
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

77
package.json Normal file
View File

@ -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"
}
}

View File

@ -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

View File

@ -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;
}
})();

1
playctrl1simd/Decoder.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -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;
}));

BIN
public/gw.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 106 KiB

1
public/vite.svg Normal file
View File

@ -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

182
src/App.vue Normal file
View File

@ -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>

Binary file not shown.

View File

@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@ -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";
}

File diff suppressed because one or more lines are too long

View File

@ -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
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
src/assets/images/404.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
src/assets/images/nav.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

BIN
src/assets/images/title.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

468
src/assets/scss/common.scss Normal file
View File

@ -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);
}
/*浏览器滚动条样式 结束*/

View File

@ -0,0 +1,4 @@
@font-face {
font-family: AliBaBaPuHuTi;
src: url('../font/AlibabaPuHuiTi-2-65-Medium.ttf');
}

View File

@ -0,0 +1,4 @@
@mixin wh($w, $h) {
width: $w;
height: $h;
}

146
src/assets/scss/myAntD.scss Normal file
View File

@ -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;
}
}

View File

@ -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;
}

View File

@ -0,0 +1,2 @@
/* 存放css变量 */
$primary-color: red;

6
src/assets/vue.svg Normal file
View File

@ -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

107
src/axios/index.ts Normal file
View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -0,0 +1,11 @@
<script setup lang="ts">
</script>
<template>
</template>
<style scoped>
</style>

View File

@ -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>

View File

@ -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>
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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
}

View File

@ -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
}

52
src/configs/emnus.ts Normal file
View File

@ -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: '隐藏'
}
]

17
src/configs/index.ts Normal file
View File

@ -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];

40
src/directives/copy.ts Normal file
View File

@ -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

79
src/global.d.ts vendored Normal file
View File

@ -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;
}

148
src/hooks/useTable.ts Normal file
View File

@ -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
};
}

55
src/main.ts Normal file
View File

@ -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')

66
src/router/index.ts Normal file
View File

@ -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;

View File

@ -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"),
},
];

8
src/stores/index.ts Normal file
View File

@ -0,0 +1,8 @@
import {createPinia} from "pinia";
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
//创建store实例 持久化插件
const pinia = createPinia();
pinia.use(piniaPluginPersistedstate);
export default pinia;

View File

@ -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 = '加载中...'
}
}
})

View File

@ -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,
}
});

View File

@ -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) : [])]);
}

17
src/utils/aMapUtil.ts Normal file
View File

@ -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)
// })
// })
// }

54
src/utils/aesUtil.ts Normal file
View File

@ -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
}

17
src/utils/dateUtil.ts Normal file
View File

@ -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()]

7
src/utils/index.ts Normal file
View File

@ -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
}

28
src/utils/nporgress.ts Normal file
View File

@ -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()
}

23
src/utils/webSocket.ts Normal file
View File

@ -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()
}

View File

@ -0,0 +1,8 @@
<template>
</template>
<script>
</script>
<style>
</style>

View File

@ -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>

197
src/views/dev/database.vue Normal file
View File

@ -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>

9
src/views/dev/full.vue Normal file
View File

@ -0,0 +1,9 @@
<template>123
</template>
<script setup lang="tsx">
</script>
<style scoped lang="scss">
</style>

228
src/views/dev/logManage.vue Normal file
View File

@ -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>

View File

@ -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>

View File

@ -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[] = [];
//treeid
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>

View File

@ -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>

Some files were not shown because too many files have changed in this diff Show More