feat: first commit
This commit is contained in:
+42
@@ -0,0 +1,42 @@
|
||||
<script setup lang="ts">
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
// Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
|
||||
import SystemMenu from './components/SystemMenu.vue';
|
||||
import SystemLayout from './components/SystemLayout.vue';
|
||||
import "element-plus/dist/index.css";
|
||||
import "funjiaui/dist/style.css";
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
/* 滚动条 */
|
||||
::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
/* 滚动槽 */
|
||||
::-webkit-scrollbar-track {
|
||||
-webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.3);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* 滚动条滑块 */
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background: #999;
|
||||
-webkit-box-shadow: inset 0 0 2px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,135 @@
|
||||
@font-face {
|
||||
font-family: "iconfont"; /* Project id 3015159 */
|
||||
src: url('iconfont.woff2?t=1646121864615') format('woff2'),
|
||||
url('iconfont.woff?t=1646121864615') format('woff'),
|
||||
url('iconfont.ttf?t=1646121864615') format('truetype');
|
||||
}
|
||||
|
||||
.iconfont {
|
||||
font-family: "iconfont" !important;
|
||||
font-size: 16px;
|
||||
font-style: normal;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.icon-a-bianzu171:before {
|
||||
content: "\e66b";
|
||||
}
|
||||
|
||||
.icon-duobianxing:before {
|
||||
content: "\e697";
|
||||
}
|
||||
|
||||
.icon-radio-on:before {
|
||||
content: "\ea6a";
|
||||
}
|
||||
|
||||
.icon-zhibo-qingping:before {
|
||||
content: "\e63a";
|
||||
}
|
||||
|
||||
.icon-gouxuan11:before {
|
||||
content: "\e628";
|
||||
}
|
||||
|
||||
.icon-weigouxuan:before {
|
||||
content: "\e623";
|
||||
}
|
||||
|
||||
.icon-bianji1:before {
|
||||
content: "\e607";
|
||||
}
|
||||
|
||||
.icon-shanchu:before {
|
||||
content: "\e625";
|
||||
}
|
||||
|
||||
.icon-guanbi:before {
|
||||
content: "\e611";
|
||||
}
|
||||
|
||||
.icon-danxuan_weixuanzhong:before {
|
||||
content: "\e653";
|
||||
}
|
||||
|
||||
.icon-danxuan_xuanzhong:before {
|
||||
content: "\e655";
|
||||
}
|
||||
|
||||
.icon-Shape:before {
|
||||
content: "\e665";
|
||||
}
|
||||
|
||||
.icon-jiahaozhankaibeifen:before {
|
||||
content: "\e664";
|
||||
}
|
||||
|
||||
.icon-jiahaozhankai:before {
|
||||
content: "\e663";
|
||||
}
|
||||
|
||||
.icon-shangchuan:before {
|
||||
content: "\e662";
|
||||
}
|
||||
|
||||
.icon-a-gonganjubeifen5:before {
|
||||
content: "\e65e";
|
||||
}
|
||||
|
||||
.icon-gonganju:before {
|
||||
content: "\e65d";
|
||||
}
|
||||
|
||||
.icon-duoxuan-xuanzhong:before {
|
||||
content: "\e65c";
|
||||
}
|
||||
|
||||
.icon-duoxuan-weixuan:before {
|
||||
content: "\e659";
|
||||
}
|
||||
|
||||
.icon-xiala:before {
|
||||
content: "\e6b9";
|
||||
}
|
||||
|
||||
.icon-dingweixiao:before {
|
||||
content: "\e654";
|
||||
}
|
||||
|
||||
.icon-shanchu-:before {
|
||||
content: "\e652";
|
||||
}
|
||||
|
||||
.icon-daoru:before {
|
||||
content: "\e651";
|
||||
}
|
||||
|
||||
.icon-daochu:before {
|
||||
content: "\e650";
|
||||
}
|
||||
|
||||
.icon-xiazai:before {
|
||||
content: "\e64f";
|
||||
}
|
||||
|
||||
.icon-tianjia:before {
|
||||
content: "\e64e";
|
||||
}
|
||||
|
||||
.icon-rili_calendar:before {
|
||||
content: "\e64d";
|
||||
}
|
||||
|
||||
.icon-bianji:before {
|
||||
content: "\e648";
|
||||
}
|
||||
|
||||
.icon-mimabeifen:before {
|
||||
content: "\e638";
|
||||
}
|
||||
|
||||
.icon-qietu:before {
|
||||
content: "\e637";
|
||||
}
|
||||
|
||||
@@ -0,0 +1,219 @@
|
||||
{
|
||||
"id": "3015159",
|
||||
"name": "仲恺综合治理平台",
|
||||
"font_family": "iconfont",
|
||||
"css_prefix_text": "icon-",
|
||||
"description": "",
|
||||
"glyphs": [
|
||||
{
|
||||
"icon_id": "27921080",
|
||||
"name": "派出所下的二级图标",
|
||||
"font_class": "a-bianzu171",
|
||||
"unicode": "e66b",
|
||||
"unicode_decimal": 58987
|
||||
},
|
||||
{
|
||||
"icon_id": "9706996",
|
||||
"name": "多边形",
|
||||
"font_class": "duobianxing",
|
||||
"unicode": "e697",
|
||||
"unicode_decimal": 59031
|
||||
},
|
||||
{
|
||||
"icon_id": "18175740",
|
||||
"name": "圆形",
|
||||
"font_class": "radio-on",
|
||||
"unicode": "ea6a",
|
||||
"unicode_decimal": 60010
|
||||
},
|
||||
{
|
||||
"icon_id": "26849757",
|
||||
"name": "zhibo-qingping",
|
||||
"font_class": "zhibo-qingping",
|
||||
"unicode": "e63a",
|
||||
"unicode_decimal": 58938
|
||||
},
|
||||
{
|
||||
"icon_id": "16368199",
|
||||
"name": "勾选1",
|
||||
"font_class": "gouxuan11",
|
||||
"unicode": "e628",
|
||||
"unicode_decimal": 58920
|
||||
},
|
||||
{
|
||||
"icon_id": "351775",
|
||||
"name": "未勾选",
|
||||
"font_class": "weigouxuan",
|
||||
"unicode": "e623",
|
||||
"unicode_decimal": 58915
|
||||
},
|
||||
{
|
||||
"icon_id": "4880425",
|
||||
"name": "编辑",
|
||||
"font_class": "bianji1",
|
||||
"unicode": "e607",
|
||||
"unicode_decimal": 58887
|
||||
},
|
||||
{
|
||||
"icon_id": "26535246",
|
||||
"name": "删除",
|
||||
"font_class": "shanchu",
|
||||
"unicode": "e625",
|
||||
"unicode_decimal": 58917
|
||||
},
|
||||
{
|
||||
"icon_id": "4942640",
|
||||
"name": "关闭",
|
||||
"font_class": "guanbi",
|
||||
"unicode": "e611",
|
||||
"unicode_decimal": 58897
|
||||
},
|
||||
{
|
||||
"icon_id": "6548521",
|
||||
"name": "单选_未选中",
|
||||
"font_class": "danxuan_weixuanzhong",
|
||||
"unicode": "e653",
|
||||
"unicode_decimal": 58963
|
||||
},
|
||||
{
|
||||
"icon_id": "6548524",
|
||||
"name": "单选_选中",
|
||||
"font_class": "danxuan_xuanzhong",
|
||||
"unicode": "e655",
|
||||
"unicode_decimal": 58965
|
||||
},
|
||||
{
|
||||
"icon_id": "26507365",
|
||||
"name": "搜索",
|
||||
"font_class": "Shape",
|
||||
"unicode": "e665",
|
||||
"unicode_decimal": 58981
|
||||
},
|
||||
{
|
||||
"icon_id": "26507356",
|
||||
"name": "jiahaozhankai备份",
|
||||
"font_class": "jiahaozhankaibeifen",
|
||||
"unicode": "e664",
|
||||
"unicode_decimal": 58980
|
||||
},
|
||||
{
|
||||
"icon_id": "26507350",
|
||||
"name": "jiahaozhankai",
|
||||
"font_class": "jiahaozhankai",
|
||||
"unicode": "e663",
|
||||
"unicode_decimal": 58979
|
||||
},
|
||||
{
|
||||
"icon_id": "26507342",
|
||||
"name": "上传照片",
|
||||
"font_class": "shangchuan",
|
||||
"unicode": "e662",
|
||||
"unicode_decimal": 58978
|
||||
},
|
||||
{
|
||||
"icon_id": "26507072",
|
||||
"name": "街道,区域",
|
||||
"font_class": "a-gonganjubeifen5",
|
||||
"unicode": "e65e",
|
||||
"unicode_decimal": 58974
|
||||
},
|
||||
{
|
||||
"icon_id": "26507064",
|
||||
"name": "派出所",
|
||||
"font_class": "gonganju",
|
||||
"unicode": "e65d",
|
||||
"unicode_decimal": 58973
|
||||
},
|
||||
{
|
||||
"icon_id": "26507030",
|
||||
"name": "复选框-选中",
|
||||
"font_class": "duoxuan-xuanzhong",
|
||||
"unicode": "e65c",
|
||||
"unicode_decimal": 58972
|
||||
},
|
||||
{
|
||||
"icon_id": "26506763",
|
||||
"name": "复选框-未选中",
|
||||
"font_class": "duoxuan-weixuan",
|
||||
"unicode": "e659",
|
||||
"unicode_decimal": 58969
|
||||
},
|
||||
{
|
||||
"icon_id": "672036",
|
||||
"name": "下拉",
|
||||
"font_class": "xiala",
|
||||
"unicode": "e6b9",
|
||||
"unicode_decimal": 59065
|
||||
},
|
||||
{
|
||||
"icon_id": "26506542",
|
||||
"name": "定位",
|
||||
"font_class": "dingweixiao",
|
||||
"unicode": "e654",
|
||||
"unicode_decimal": 58964
|
||||
},
|
||||
{
|
||||
"icon_id": "26506492",
|
||||
"name": "删除",
|
||||
"font_class": "shanchu-",
|
||||
"unicode": "e652",
|
||||
"unicode_decimal": 58962
|
||||
},
|
||||
{
|
||||
"icon_id": "26506468",
|
||||
"name": "导入",
|
||||
"font_class": "daoru",
|
||||
"unicode": "e651",
|
||||
"unicode_decimal": 58961
|
||||
},
|
||||
{
|
||||
"icon_id": "26506447",
|
||||
"name": "导出",
|
||||
"font_class": "daochu",
|
||||
"unicode": "e650",
|
||||
"unicode_decimal": 58960
|
||||
},
|
||||
{
|
||||
"icon_id": "26506428",
|
||||
"name": "下载",
|
||||
"font_class": "xiazai",
|
||||
"unicode": "e64f",
|
||||
"unicode_decimal": 58959
|
||||
},
|
||||
{
|
||||
"icon_id": "26506404",
|
||||
"name": "添加",
|
||||
"font_class": "tianjia",
|
||||
"unicode": "e64e",
|
||||
"unicode_decimal": 58958
|
||||
},
|
||||
{
|
||||
"icon_id": "26506240",
|
||||
"name": "日期",
|
||||
"font_class": "rili_calendar",
|
||||
"unicode": "e64d",
|
||||
"unicode_decimal": 58957
|
||||
},
|
||||
{
|
||||
"icon_id": "26505995",
|
||||
"name": "编辑",
|
||||
"font_class": "bianji",
|
||||
"unicode": "e648",
|
||||
"unicode_decimal": 58952
|
||||
},
|
||||
{
|
||||
"icon_id": "26462764",
|
||||
"name": "密码",
|
||||
"font_class": "mimabeifen",
|
||||
"unicode": "e638",
|
||||
"unicode_decimal": 58936
|
||||
},
|
||||
{
|
||||
"icon_id": "26462757",
|
||||
"name": "用户",
|
||||
"font_class": "qietu",
|
||||
"unicode": "e637",
|
||||
"unicode_decimal": 58935
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 6.7 KiB |
@@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue'
|
||||
import CaptureObjectList from '@/views/CaptureObjectList.vue'
|
||||
|
||||
createApp(CaptureObjectList).mount('#app')
|
||||
@@ -0,0 +1,271 @@
|
||||
import axios from "../axios";
|
||||
// import imgList from "../../../public/data/image_cache.json";
|
||||
// import serviceInfo from "../../../public/data/get_service_info.json";
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
export const login = (params: any): Promise<any> => {
|
||||
return axios.post("/api/login", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
export const udpdatePwd = (params: any): Promise<any> => {
|
||||
return axios.post("/api/user/password/update", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取系统信息
|
||||
*/
|
||||
export const getSytemInfo = (): Promise<any> => {
|
||||
return axios.get("/api/get/system");
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取ipc列表
|
||||
*/
|
||||
export const getIpcList = (params: any): Promise<any> => {
|
||||
return axios.post("/api/manager/device/list");
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加ipc
|
||||
*/
|
||||
export const addIpc = (params: any): Promise<any> => {
|
||||
return axios.post("/api/manager/device/add", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改ipc
|
||||
*/
|
||||
export const updateIpc = (params: any): Promise<any> => {
|
||||
return axios.post("/api/manager/device/update", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除ipc
|
||||
*/
|
||||
export const deleteIpc = (params: any): Promise<any> => {
|
||||
return axios.post("/api/manager/device/delete", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 根据ipcId获取抓拍图片列表
|
||||
*/
|
||||
export const getCaputreImgListByIpcId = (params: any): Promise<any> => {
|
||||
return axios.post("/api/query/device/obejct/image/cache", params);
|
||||
// return imgList;
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取日志级别列表
|
||||
*/
|
||||
export const getLevelList = (): Promise<any> => {
|
||||
return axios.post("/api/get/log/level");
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取日志列表
|
||||
*/
|
||||
export const getLogList = (params: any): Promise<any> => {
|
||||
return axios.post("/api/get/log/last", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置日志级别
|
||||
*/
|
||||
export const setLogLevel = (params: any): Promise<any> => {
|
||||
return axios.post(`/set/log/level/${params.level}`, params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 重启系统
|
||||
*/
|
||||
export const restartSystem = (): Promise<any> => {
|
||||
return axios.get('/api/system/reboot');
|
||||
};
|
||||
|
||||
/**
|
||||
* 重启程序
|
||||
*/
|
||||
export const restartApp = (): Promise<any> => {
|
||||
return axios.get('/api/system/restart');
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取服务状态
|
||||
*/
|
||||
export const getServiceStatus = (): Promise<any> => {
|
||||
return axios.get('/get/service/info');
|
||||
// return serviceInfo;
|
||||
};
|
||||
|
||||
/**
|
||||
* 设置服务
|
||||
*/
|
||||
export const setService = (params: any): Promise<any> => {
|
||||
return axios.post('/api/set/service/manager', params);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取TravelInformation列表
|
||||
*/
|
||||
export const getTravelInformationList = (params: any): Promise<any> => {
|
||||
return axios.post("/api/TravelInformation/list", params);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 获取TravelInformation详情
|
||||
*/
|
||||
export const getTravelInformation = (params: any): Promise<any> => {
|
||||
return axios.get("/api/travelInformation", { params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加TravelInformation
|
||||
*/
|
||||
export const addTravelInformation = (params: any): Promise<any> => {
|
||||
return axios.post("/api/TravelInformation/add", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改TravelInformation
|
||||
*/
|
||||
export const updateTravelInformation = (params: any): Promise<any> => {
|
||||
return axios.post("/api/TravelInformation/upd", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除TravelInformation
|
||||
*/
|
||||
export const deleteTravelInformation = (params: any): Promise<any> => {
|
||||
return axios.post("/api/TravelInformation/delete", params);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 获取Employee列表
|
||||
*/
|
||||
export const getEmployeeList = (params: any): Promise<any> => {
|
||||
return axios.post("/api/Employee/list", params);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 获取Employee详情
|
||||
*/
|
||||
export const getEmployee = (params: any): Promise<any> => {
|
||||
return axios.get("/api/Employee", { params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加Employee
|
||||
*/
|
||||
export const addEmployee = (params: any): Promise<any> => {
|
||||
return axios.post("/api/Employee/add", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改Employee
|
||||
*/
|
||||
export const updateEmployee = (params: any): Promise<any> => {
|
||||
return axios.post("/api/Employee/upd", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除Employee
|
||||
*/
|
||||
export const deleteEmployee = (params: any): Promise<any> => {
|
||||
return axios.post("/api/Employee/delete", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* Employee登录
|
||||
*/
|
||||
export const employeeLogin = (params: any): Promise<any> => {
|
||||
return axios.post("/api/Employee/login", params);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取Notice列表
|
||||
*/
|
||||
export const getNoticeList = (params: any): Promise<any> => {
|
||||
return axios.post("/api/Notice/list", params);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 获取Notice详情
|
||||
*/
|
||||
export const getNotice = (params: any): Promise<any> => {
|
||||
return axios.get("/api/Notice", { params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加Notice
|
||||
*/
|
||||
export const addNotice = (params: any): Promise<any> => {
|
||||
return axios.post("/api/Notice/add", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改Notice
|
||||
*/
|
||||
export const updateNotice = (params: any): Promise<any> => {
|
||||
return axios.post("/api/Notice/upd", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除Notice
|
||||
*/
|
||||
export const deleteNotice = (params: any): Promise<any> => {
|
||||
return axios.post("/api/Notice/delete", params);
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 获取QA列表
|
||||
*/
|
||||
export const getQAList = (params: any): Promise<any> => {
|
||||
return axios.post("/api/QA/list", params);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* 获取QA详情
|
||||
*/
|
||||
export const getQA = (params: any): Promise<any> => {
|
||||
return axios.get("/api/QA", { params });
|
||||
};
|
||||
|
||||
/**
|
||||
* 添加QA
|
||||
*/
|
||||
export const addQA = (params: any): Promise<any> => {
|
||||
return axios.post("/api/QA/add", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 修改QA
|
||||
*/
|
||||
export const updateQA = (params: any): Promise<any> => {
|
||||
return axios.post("/api/QA/upd", params);
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除QA
|
||||
*/
|
||||
export const deleteQA = (params: any): Promise<any> => {
|
||||
return axios.post("/api/QA/delete", params);
|
||||
};
|
||||
@@ -0,0 +1,116 @@
|
||||
import axios, { AxiosRequestConfig, AxiosResponse } from "axios";
|
||||
import Cookies from "js-cookie";
|
||||
import 'element-plus/es/components/message/style/css'
|
||||
import { ElMessage } from 'element-plus'
|
||||
|
||||
// axios.defaults.baseURL = ""; // 请求后台地址
|
||||
axios.defaults.headers.post["Content-Type"] = "application/json;charset=utf-8";
|
||||
|
||||
interface LogOutCodesProps {
|
||||
status: number;
|
||||
code: number;
|
||||
msg?: string;
|
||||
}
|
||||
|
||||
axios.interceptors.request.use(
|
||||
(config: AxiosRequestConfig) => {
|
||||
// token授权
|
||||
const jwtToken: string = Cookies.get("Authorization") ?? "";
|
||||
config.headers = config.headers || {};
|
||||
config.headers.Authorization = `${jwtToken}`;
|
||||
// 缓存问题
|
||||
config.headers["Cache-Control"] = "no-cache";
|
||||
return config;
|
||||
},
|
||||
(err) => {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
);
|
||||
|
||||
axios.interceptors.response.use(
|
||||
(response: AxiosResponse) => {
|
||||
const xhr: any = response.data ?? {};
|
||||
const { code, msg: resultMsg } = xhr;
|
||||
// 需要退出重登的code和msg
|
||||
let msg: string = "";
|
||||
switch (code) {
|
||||
case 9:
|
||||
msg = "请重新登录";
|
||||
break;
|
||||
case 18:
|
||||
msg = "授权过期,请重新登录";
|
||||
break;
|
||||
case 27:
|
||||
msg = "未安装License或License已过期";
|
||||
break;
|
||||
case 28:
|
||||
msg = "License权限不足";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (code) {
|
||||
ElMessage.error(msg || resultMsg || "系统异常");
|
||||
}
|
||||
return xhr;
|
||||
},
|
||||
(error) => {
|
||||
const response: any = error.response ?? {};
|
||||
const { data, status } = response;
|
||||
const { code, reason } = data;
|
||||
let msg: string = "";
|
||||
// 获取错误提示
|
||||
switch (status) {
|
||||
case 400:
|
||||
msg =
|
||||
code === 17
|
||||
? `异地登录,登录IP为${data.data},请重新登录`
|
||||
: "请求错误";
|
||||
break;
|
||||
case 401:
|
||||
msg = reason || "鉴权失败";
|
||||
break;
|
||||
case 403:
|
||||
msg = "拒绝访问";
|
||||
break;
|
||||
case 404:
|
||||
msg = "请求错误,未找到该资源";
|
||||
break;
|
||||
case 405:
|
||||
msg = "请求方法未允许";
|
||||
break;
|
||||
case 408:
|
||||
msg = "请求超时";
|
||||
break;
|
||||
case 413:
|
||||
msg = "请求数据过多";
|
||||
break;
|
||||
case 500:
|
||||
msg = "服务器端出错";
|
||||
break;
|
||||
case 501:
|
||||
msg = "网络未实现";
|
||||
break;
|
||||
case 503:
|
||||
msg = "服务不可用";
|
||||
break;
|
||||
case 504:
|
||||
msg = "网络超时";
|
||||
break;
|
||||
case 505:
|
||||
msg = "http版本不支持该请求";
|
||||
break;
|
||||
case 502:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
ElMessage.error(msg);
|
||||
return Promise.reject(error).catch(() => {
|
||||
if (401 == status) {
|
||||
window.location.href = "/";
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export default axios;
|
||||
@@ -0,0 +1,147 @@
|
||||
/**
|
||||
* 获取目标类型的名称 0:人脸, 1:自行车, 2:巴士, 3:小汽车, 4:电动车|摩托车, 5:人体, 6:三轮车,7货车、8车牌、9推车,10鸟、11猫,12 狗,13老鼠
|
||||
* @param objectType
|
||||
* @returns
|
||||
*/
|
||||
export const getObjectTypeName = (objectType: TObjectType) => {
|
||||
switch (objectType) {
|
||||
case 0:
|
||||
return "人脸";
|
||||
case 1:
|
||||
return "自行车";
|
||||
case 2:
|
||||
return "巴士";
|
||||
case 3:
|
||||
return "小汽车";
|
||||
case 4:
|
||||
return "电动车|摩托车";
|
||||
case 5:
|
||||
return "人体";
|
||||
case 6:
|
||||
return "三轮车";
|
||||
case 7:
|
||||
return "货车";
|
||||
case 8:
|
||||
return "车牌";
|
||||
case 9:
|
||||
return "推车";
|
||||
case 10:
|
||||
return "鸟";
|
||||
case 11:
|
||||
return "猫";
|
||||
case 12:
|
||||
return "狗";
|
||||
case 13:
|
||||
return "老鼠";
|
||||
default:
|
||||
return "--";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 解决加减乘除中浮点数精度问题
|
||||
|
||||
/**
|
||||
* arg1 第一位数字
|
||||
* arg2 第二位数字
|
||||
* type 运算符类型 加add, 减reduce,乘ride,除except
|
||||
*/
|
||||
export const FloatPoint = (arg1: number, arg2: number, type: 'add' | 'reduce' | 'ride' | 'except') => {
|
||||
let r1: number = 0;
|
||||
let r2: number = 0;
|
||||
try {
|
||||
r1 = arg1.toString().split(".")[1].length;
|
||||
} catch (e) {
|
||||
r1 = 0;
|
||||
}
|
||||
try {
|
||||
r2 = arg2.toString().split(".")[1].length;
|
||||
} catch (e) {
|
||||
r2 = 0;
|
||||
}
|
||||
const c: number = Math.abs(r1 - r2); // 位数差的绝对值
|
||||
const m: number = Math.pow(10, Math.max(r1, r2)); // 较大数的幂
|
||||
if (c > 0) {
|
||||
// 位数相差
|
||||
const cm: number = Math.pow(10, c);
|
||||
if (r1 > r2) {
|
||||
arg1 = Number(arg1.toString().replace(".", "")); // 转化成数字
|
||||
arg2 = Number(arg2.toString().replace(".", "")) * cm;
|
||||
} else {
|
||||
arg1 = Number(arg1.toString().replace(".", "")) * cm;
|
||||
arg2 = Number(arg2.toString().replace(".", ""));
|
||||
}
|
||||
} else { // 位数相等
|
||||
arg1 = Number(arg1.toString().replace(".", ""));
|
||||
arg2 = Number(arg2.toString().replace(".", ""));
|
||||
}
|
||||
let res: any = null;
|
||||
if (type === 'add') {
|
||||
res = (arg1 + arg2) / m;
|
||||
} else if (type === 'reduce') {
|
||||
res = (arg1 - arg2) / m;
|
||||
} else if (type === 'ride') {
|
||||
res = (arg1 * arg2) / m / m;
|
||||
} else if (type === 'except') {
|
||||
res = (arg1 / arg2);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
|
||||
/**
|
||||
* 对字符串保留小数
|
||||
* @param value 原始值,有可能大于1
|
||||
* @param num 位数
|
||||
*/
|
||||
export const toFixed = (str: any, num: number = 2) => {
|
||||
try {
|
||||
if (typeof (str) === "string") {
|
||||
str = parseFloat(str);
|
||||
} else if (typeof (str) === "number") {
|
||||
str = str;
|
||||
} else {
|
||||
str = parseFloat(str);
|
||||
}
|
||||
str = FloatPoint(str, 100, 'ride');
|
||||
str = str.toString(); // 字符串值
|
||||
const pointIdx = str.indexOf("."); // 字符串中小数点的位置
|
||||
if (pointIdx !== -1) {
|
||||
if (num > 0) {
|
||||
// 存在小数点的情况下
|
||||
// 最多保留num位小数
|
||||
str = str.substring(0, pointIdx + (num + 1));
|
||||
// 小数位小于num位的情况下,在末尾补零
|
||||
const decimalStr = str.substring(pointIdx + 1); // 小数位字符串
|
||||
if (decimalStr.length < num) {
|
||||
for (let i = 0; i < num - decimalStr.length; i++) {
|
||||
str += "0";
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
str = str.substring(0, pointIdx);
|
||||
return parseFloat(str);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return 0;
|
||||
}
|
||||
return parseFloat(str);
|
||||
}
|
||||
|
||||
export function encode(str) {
|
||||
// 对字符串进行编码
|
||||
const encode = encodeURI(str);
|
||||
// 对编码的字符串转化base64
|
||||
const base64 = btoa(encode);
|
||||
return base64;
|
||||
}
|
||||
|
||||
export function decode(base64) {
|
||||
// 对base64转编码
|
||||
const decode = atob(base64);
|
||||
// 编码转字符串
|
||||
const str = decodeURI(decode);
|
||||
return str;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div :class="'angle ' + identity + ' ' + direction">
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts">
|
||||
// 箭头方向
|
||||
export enum EDirection {
|
||||
LeftTop = "left-top",
|
||||
LeftBottom = "left-bottom",
|
||||
RightTop = "right-top",
|
||||
RightBottom = "right-bottom"
|
||||
}
|
||||
|
||||
// 身份类型
|
||||
export enum EIdentity {
|
||||
// 重点人员
|
||||
Suspect = "suspect",
|
||||
// 陌生人
|
||||
Stranger = "stranger",
|
||||
// 居民
|
||||
Resident = "resident",
|
||||
// 未归档
|
||||
Unknow = "unknow",
|
||||
// 其它
|
||||
Other = "other"
|
||||
}
|
||||
</script>
|
||||
<script setup lang="ts">
|
||||
withDefaults(defineProps<{ identity: EIdentity, direction: EDirection }>(), {
|
||||
identity: EIdentity.Stranger,
|
||||
direction: EDirection.LeftTop
|
||||
});
|
||||
</script>
|
||||
<style lang="scss">
|
||||
$suspect-color:#ff5757;
|
||||
$stranger-color:#f9db12;
|
||||
$residents-color:#12DB68;
|
||||
$unknow-color:#989898;
|
||||
$primary1_color:#00C9DE;
|
||||
|
||||
|
||||
@mixin borderStyle($color) {
|
||||
height: 8px;
|
||||
width: 8px;
|
||||
border-top: 3px solid $color;
|
||||
border-left: 3px solid $color;
|
||||
}
|
||||
|
||||
@mixin angle-block($color) {
|
||||
&.left-top {
|
||||
@include borderStyle($color);
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.left-bottom {
|
||||
@include borderStyle($color);
|
||||
transform: rotate(270deg);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
&.right-top {
|
||||
@include borderStyle($color);
|
||||
transform: rotate(90deg);
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
&.right-bottom {
|
||||
@include borderStyle($color);
|
||||
transform: rotate(180deg);
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.angle {
|
||||
|
||||
// 重点人员
|
||||
&.suspect {
|
||||
@include angle-block($suspect-color);
|
||||
}
|
||||
|
||||
// 陌生人
|
||||
&.stranger {
|
||||
@include angle-block($stranger-color);
|
||||
}
|
||||
|
||||
// 居民
|
||||
&.resident {
|
||||
@include angle-block($residents-color);
|
||||
}
|
||||
|
||||
// 未归档
|
||||
&.unknow {
|
||||
@include angle-block($unknow-color);
|
||||
}
|
||||
|
||||
// 其它
|
||||
&.other {
|
||||
@include angle-block($primary1_color);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,13 @@
|
||||
<template>
|
||||
<object-rect :top="top" :left="left" :width="-(left - right)" :height="-(top - bottom)"></object-rect>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// import { EIdentity } from './Angle.vue';
|
||||
import { computed } from 'vue';
|
||||
const props = defineProps<{
|
||||
top: number,
|
||||
right: number,
|
||||
bottom: number,
|
||||
left: number
|
||||
}>()
|
||||
</script>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<codemirror v-model="value" placeholder="Code goes here..." :style="{ height: '400px' }" :autofocus="true"
|
||||
:indent-with-tab="true" :tab-size="2" :extensions="extensions" @ready="handleReady" @change="log('change', $event)"
|
||||
@focus="log('focus', $event)" @blur="log('blur', $event)" />
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { defineComponent } from 'vue'
|
||||
import { Codemirror } from 'vue-codemirror'
|
||||
import { javascript } from '@codemirror/lang-javascript'
|
||||
// import { oneDark } from '@codemirror/theme-one-dark'
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
Codemirror
|
||||
},
|
||||
setup() {
|
||||
const code = ref(`console.log('Hello, world!')`)
|
||||
const extensions = [javascript()]
|
||||
|
||||
// Codemirror EditorView instance ref
|
||||
const view = shallowRef()
|
||||
const handleReady = (payload) => {
|
||||
view.value = payload.view
|
||||
}
|
||||
|
||||
// Status is available at all times via Codemirror EditorView
|
||||
const getCodemirrorStates = () => {
|
||||
const state = view.value.state
|
||||
const ranges = state.selection.ranges
|
||||
const selected = ranges.reduce((r, range) => r + range.to - range.from, 0)
|
||||
const cursor = ranges[0].anchor
|
||||
const length = state.doc.length
|
||||
const lines = state.doc.lines
|
||||
// more state info ...
|
||||
// return ...
|
||||
}
|
||||
|
||||
return {
|
||||
value,
|
||||
extensions,
|
||||
handleReady,
|
||||
log: console.log
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,36 @@
|
||||
<template>
|
||||
<p class="el-message__content">{{ getMsgTempalte ? getMsgTempalte(countDownTime) : countDownTime }}</p>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { onBeforeMount, ref, onBeforeUnmount } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
timespan?: number,
|
||||
getMsgTempalte: (value: number) => string
|
||||
}>(), {
|
||||
timespan: 3
|
||||
})
|
||||
|
||||
const emit = defineEmits<{
|
||||
(n: 'next'): void
|
||||
}>()
|
||||
|
||||
const countDownTime = ref(props.timespan);
|
||||
const timeout = ref();
|
||||
|
||||
onBeforeMount(() => {
|
||||
timeout.value = setInterval(() => {
|
||||
if (countDownTime.value >= 1) {
|
||||
countDownTime.value -= 1;
|
||||
}
|
||||
else {
|
||||
clearInterval(timeout.value);
|
||||
emit('next');
|
||||
}
|
||||
}, 1000);
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(timeout.value);
|
||||
})
|
||||
</script>
|
||||
@@ -0,0 +1,57 @@
|
||||
<template>
|
||||
<el-tag v-for="tag in dynamicTags" :key="tag" class="mx-1" closable :disable-transitions="false"
|
||||
@close="handleClose(tag)">
|
||||
{{ tag }}
|
||||
</el-tag>
|
||||
<el-input v-if="inputVisible" ref="InputRef" v-model="inputValue" class="ml-1 w-20" size="small"
|
||||
@keyup.enter="handleInputConfirm" @blur="handleInputConfirm" />
|
||||
<el-button v-else class="button-new-tag ml-1" size="small" @click="showInput">
|
||||
+ New Tag
|
||||
</el-button>
|
||||
</template>
|
||||
<style scoped>
|
||||
.mx-1 {
|
||||
margin-left: 0.25rem;
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { nextTick, ref } from 'vue'
|
||||
import { ElInput } from 'element-plus'
|
||||
|
||||
const props = defineProps<{ modelValue: [] }>();
|
||||
const emits = defineEmits<{
|
||||
(
|
||||
e: "update:modelValue",
|
||||
val: string | number | (string | number)[] | undefined
|
||||
): void;
|
||||
}>();
|
||||
|
||||
const inputValue = ref('')
|
||||
const dynamicTags = ref(props.modelValue)
|
||||
const inputVisible = ref(false)
|
||||
const InputRef = ref<InstanceType<typeof ElInput>>()
|
||||
|
||||
const handleClose = (tag: string) => {
|
||||
dynamicTags.value.splice(dynamicTags.value.indexOf(tag), 1)
|
||||
emits('update:modelValue', dynamicTags.value)
|
||||
}
|
||||
|
||||
const showInput = () => {
|
||||
inputVisible.value = true
|
||||
nextTick(() => {
|
||||
InputRef.value!.input!.focus()
|
||||
})
|
||||
}
|
||||
|
||||
const handleInputConfirm = () => {
|
||||
if (inputValue.value) {
|
||||
dynamicTags.value.push(inputValue.value)
|
||||
emits('update:modelValue', dynamicTags.value)
|
||||
}
|
||||
inputVisible.value = false
|
||||
inputValue.value = ''
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -0,0 +1,230 @@
|
||||
<template>
|
||||
<div :class="$style['img-preview']">
|
||||
<vue-cropper :key="id" ref="cropper" :src="imgUrl" alt="" :autoCrop="false" dragMode="move" :background="false"
|
||||
:viewMode="2" :toggleDragModeOnDblclick="false" @ready="onReady" @crop="onCrop" @zoom="onZoom" />
|
||||
<div :class="$style['mask']">
|
||||
<calc-rect-dock :top="rectPosition.top" :right="rectPosition.right" :bottom="rectPosition.bottom"
|
||||
:left="rectPosition.left"></calc-rect-dock>
|
||||
</div>
|
||||
<div :class="$style['prev']" @click="$emit('imgIdxChange', -1)">
|
||||
<el-icon>
|
||||
<d-arrow-left />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div :class="$style['next']" @click="$emit('imgIdxChange', 1)">
|
||||
<el-icon>
|
||||
<d-arrow-right />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import VueCropper from 'vue-cropperjs';
|
||||
import 'cropperjs/dist/cropper.css';
|
||||
import { reactive, ref, onBeforeMount, watch } from "vue";
|
||||
import { computed } from '@vue/reactivity';
|
||||
import { merge, cloneDeep } from "lodash-es";
|
||||
import {
|
||||
DArrowLeft,
|
||||
DArrowRight,
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
const props = defineProps<{
|
||||
id: "",
|
||||
imgUrl: "",
|
||||
position: "",
|
||||
}>();
|
||||
|
||||
defineEmits<{
|
||||
(n: 'imgIdxChange', value: TImgPreviewIdx): void
|
||||
}>();
|
||||
|
||||
const cropper = ref();
|
||||
const cropOption = reactive({
|
||||
ratio: 1,
|
||||
delta: { deltaX: 0, deltaY: 0 }
|
||||
});
|
||||
const rectPosition = reactive({
|
||||
top: 0,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
left: 0
|
||||
});
|
||||
const defRatio = ref(0);
|
||||
|
||||
onBeforeMount(() => {
|
||||
initRectPosition();
|
||||
})
|
||||
|
||||
watch([cropOption, () => props.id], () => {
|
||||
initRectPosition();
|
||||
})
|
||||
|
||||
const initRectPosition = () => {
|
||||
const rectOption: any = formatRectPosition(cloneDeep(props.position)) || {};
|
||||
merge(rectPosition, getStyle(rectOption));
|
||||
}
|
||||
|
||||
const initCropOption = (e) => {
|
||||
const canvasData: any = e.target?.cropper?.canvasData;
|
||||
if (canvasData) {
|
||||
if (e?.detail?.ratio && e.detail.ratio >= defRatio.value) {
|
||||
merge(cropOption, {
|
||||
ratio: e.detail.ratio,
|
||||
delta: {
|
||||
deltaX: canvasData.left,
|
||||
deltaY: canvasData.top
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
merge(cropOption, {
|
||||
delta: {
|
||||
deltaX: canvasData.left,
|
||||
deltaY: canvasData.top
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片渲染完成事件
|
||||
* @param e
|
||||
*/
|
||||
const onReady = (e: any) => {
|
||||
const canvasData: any = e.target?.cropper?.canvasData;
|
||||
if (canvasData) {
|
||||
defRatio.value = canvasData.width / e.target.width;
|
||||
merge(cropOption, {
|
||||
ratio: defRatio.value,
|
||||
delta: { deltaX: 0, deltaY: 0 }
|
||||
})
|
||||
}
|
||||
};
|
||||
|
||||
const onCrop = (e) => {
|
||||
initCropOption(e);
|
||||
}
|
||||
|
||||
const onZoom = (e) => {
|
||||
initCropOption(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* 格式化矩形框位置信息
|
||||
* @param position json字符串
|
||||
*/
|
||||
const formatRectPosition = (position: any) => {
|
||||
const fpm: any = {};
|
||||
let rectInfo: any = {};
|
||||
if (typeof (position) === "string") {
|
||||
try {
|
||||
rectInfo = JSON.parse(position);
|
||||
const { face_rect, object_rect } = rectInfo;
|
||||
if (face_rect) {
|
||||
fpm.top = face_rect.top;
|
||||
fpm.right = face_rect.right;
|
||||
fpm.bottom = face_rect.bottom;
|
||||
fpm.left = face_rect.left;
|
||||
}
|
||||
else if (object_rect) {
|
||||
fpm.top = object_rect.top;
|
||||
fpm.right = object_rect.right;
|
||||
fpm.bottom = object_rect.bottom;
|
||||
fpm.left = object_rect.left;
|
||||
fpm.type = 2;//ERectType.Body;
|
||||
}
|
||||
return fpm;
|
||||
} catch (error) {
|
||||
console.warn("小图位置信息有误", position);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (position.face_rect) {
|
||||
return position.face_rect;
|
||||
}
|
||||
else if (position.object_rect) {
|
||||
return position.object_rect;
|
||||
}
|
||||
}
|
||||
console.warn("小图位置信息有误", position);
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 定位矩形框
|
||||
*/
|
||||
const getStyle = (position: any) => {
|
||||
const faceRect = position;
|
||||
const { ratio, delta } = cropOption;
|
||||
if (ratio != -1) {
|
||||
for (const item in faceRect) {
|
||||
if (["top", "right", "bottom", "left"].includes(item))
|
||||
faceRect[item] = parseFloat(faceRect[item]) * ratio
|
||||
}
|
||||
}
|
||||
return {
|
||||
top: faceRect.top + delta.deltaY,
|
||||
left: faceRect.left + delta.deltaX,
|
||||
bottom: faceRect.bottom + delta.deltaY,
|
||||
right: faceRect.right + delta.deltaX,
|
||||
};
|
||||
}
|
||||
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.img-preview) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
:local(.mask) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
|
||||
:local(.prev),
|
||||
:local(.next) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
:local(.prev),
|
||||
:local(.next) {
|
||||
position: absolute;
|
||||
height: 100px;
|
||||
width: 100px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
background-color: rgb(10 10 10 / 20%);
|
||||
line-height: 100px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
pointer-events: painted;
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
:local(.prev) {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
:local(.next) {
|
||||
right: 0;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<system-layout>
|
||||
<template #sidebar>
|
||||
<system-menu menuIdx="1-1" :parentIdxList="[1]" />
|
||||
</template>
|
||||
<template #content>
|
||||
<router-view></router-view>
|
||||
</template>
|
||||
</system-layout>
|
||||
</template>
|
||||
@@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<div :class="['face-identity-rect', getRootClassName(), identity].join(' ')"
|
||||
:style="{ width: width + 'px', height: height + 'px' }">
|
||||
<angle :identity="identity" :direction="EDirection.LeftTop"></angle>
|
||||
<angle :identity="identity" :direction="EDirection.LeftBottom"></angle>
|
||||
<angle :identity="identity" :direction="EDirection.RightTop"></angle>
|
||||
<angle :identity="identity" :direction="EDirection.RightBottom"></angle>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { EDirection, EIdentity } from "./Angle.vue";
|
||||
import { computed } from "vue";
|
||||
|
||||
const props: any = withDefaults(defineProps<{
|
||||
identity: EIdentity, type: number,
|
||||
width: number, height: number,
|
||||
top: number,
|
||||
left: number
|
||||
}>(), {
|
||||
identity: EIdentity.Resident,
|
||||
type: 1
|
||||
})
|
||||
|
||||
const topVar = computed(() => props.top + 'px')
|
||||
const leftVar = computed(() => props.left + 'px')
|
||||
|
||||
|
||||
const getRootClassName = () => {
|
||||
// if (props.type == 1)
|
||||
// return "face-rect";
|
||||
// else
|
||||
return "body-rect";
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.face-identity-rect {
|
||||
position: absolute;
|
||||
overflow: hidden;
|
||||
top: v-bind(topVar);
|
||||
left: v-bind(leftVar);
|
||||
|
||||
&.body-rect {
|
||||
//.face-rect {
|
||||
// background-color: rgba(255, 140, 0, .19);
|
||||
border: solid 2px #ff8c00;
|
||||
// }
|
||||
|
||||
.angle {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,47 @@
|
||||
<template>
|
||||
<div :class="$style['upload']" @change="onFileChange">
|
||||
<input ref="fileRef" :accept="accept" v-show="false" type="file" />
|
||||
<el-input :model-value="fileName" :disabled="true" :style="{ width: '200px' }" />
|
||||
<el-button :class="$style['scan']" @click="onPreUpload">浏览</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
|
||||
withDefaults(defineProps<{
|
||||
accept?: any
|
||||
}>(), {
|
||||
accept: '*'
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(n: "fileChange", file: File): void
|
||||
}>();
|
||||
|
||||
const fileRef = ref();
|
||||
const fileName = ref();
|
||||
|
||||
const onFileChange = (e: any) => {
|
||||
const files = e.target.files;
|
||||
fileName.value = files?.[0]?.name;
|
||||
emit('fileChange', files?.[0]);
|
||||
}
|
||||
|
||||
const onPreUpload = () => {
|
||||
fileRef.value.value = "";
|
||||
fileRef.value?.click();
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.upload) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
:local(.scan) {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,51 @@
|
||||
<template>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
<slot name="header" :style="{ height: '80px' }">
|
||||
<home-header></home-header>
|
||||
</slot>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row :style="{
|
||||
height: 'calc(100% - 80px)'
|
||||
}">
|
||||
<el-col :span="3">
|
||||
<slot name="sidebar"></slot>
|
||||
</el-col>
|
||||
<el-col :span="21" :style="{
|
||||
background: '#f0f2f5', padding: '20px', overflow: 'auto',
|
||||
height: 'calc(100vh - 80px)'
|
||||
}">
|
||||
<slot name="content"></slot>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="24">
|
||||
</el-col>
|
||||
</el-row>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import HomeHeader from "@/containers/HomeHeader.vue"
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
/* .el-menu-vertical-demo:not(.el-menu--collapse) {
|
||||
width: 200px;
|
||||
min-height: 400px;
|
||||
} */
|
||||
|
||||
:global {
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
:local(.layout) {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<!-- <el-radio-group v-model="isCollapse" style="margin-bottom: 20px">
|
||||
<el-radio-button :label="false">expand</el-radio-button>
|
||||
<el-radio-button :label="true">collapse</el-radio-button>
|
||||
</el-radio-group> -->
|
||||
<div :class="$style['system-menu']">
|
||||
<el-menu :default-active="activeIndex" :default-openeds="openeds" :router="true" class="el-menu-vertical-demo"
|
||||
:collapse="isCollapse" active-text-color="#409eff" background-color="#001529" text-color="#fff"
|
||||
:style="{ border: 0 }" @open="handleOpen" @close="handleClose">
|
||||
<el-menu-item index="/app/Employee">
|
||||
用戶列表
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/app/TravelInformation">
|
||||
出行记录
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/app/Notice">
|
||||
通知列表
|
||||
</el-menu-item>
|
||||
<el-menu-item index="/app/QA">
|
||||
问答列表
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onBeforeMount } from 'vue'
|
||||
import {
|
||||
Menu as IconMenu,
|
||||
Setting,
|
||||
} from '@element-plus/icons-vue'
|
||||
import { useRoute } from "vue-router";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
menuIdx: string,
|
||||
parentIdxList: string[]
|
||||
}>(), {
|
||||
menuIdx: "/app/system/info",
|
||||
parentIdxList: () => ['system']
|
||||
})
|
||||
|
||||
const activeIndex = ref();
|
||||
const openeds = ref<string[]>([]);
|
||||
const router = useRoute();
|
||||
|
||||
onBeforeMount(() => {
|
||||
activeIndex.value = router.path;
|
||||
switch (true) {
|
||||
case router.path.includes('/app/device'):
|
||||
openeds.value = ['/app/device']
|
||||
break;
|
||||
case router.path.includes('/app/system'):
|
||||
default:
|
||||
openeds.value = ['/app/system/info'];
|
||||
break;
|
||||
}
|
||||
})
|
||||
|
||||
const isCollapse = ref(false)
|
||||
const handleOpen = (key: string, keyPath: string[]) => {
|
||||
}
|
||||
const handleClose = (key: string, keyPath: string[]) => {
|
||||
}
|
||||
|
||||
const goTo = (url) => {
|
||||
document.location.href = url;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style module lang="scss">
|
||||
/* .el-menu-vertical-demo:not(.el-menu--collapse) {
|
||||
width: 200px;
|
||||
min-height: 400px;
|
||||
} */
|
||||
|
||||
:global {
|
||||
:local(.system-menu) {
|
||||
height: 100%;
|
||||
|
||||
.el-menu-vertical-demo {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
|
||||
import ExecutionEnvironment from 'exenv';
|
||||
|
||||
function transitionEnd() {
|
||||
const transitionEndEventNames = {
|
||||
WebkitTransition: 'webkitTransitionEnd',
|
||||
MozTransition: 'transitionend',
|
||||
OTransition: 'oTransitionEnd otransitionend',
|
||||
transition: 'transitionend'
|
||||
};
|
||||
if (!ExecutionEnvironment.canUseDOM) {
|
||||
return transitionEndEventNames;
|
||||
}
|
||||
const el = document.createElement('pin');
|
||||
|
||||
for (const name in transitionEndEventNames) {
|
||||
if (el.style[name] !== undefined) {
|
||||
return transitionEndEventNames[name];
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const ifHasTransitionEnd = transitionEnd();
|
||||
|
||||
const prefixes = ['Webkit', 'Moz', 'ms', 'O', ''];
|
||||
|
||||
class AnimationManager {
|
||||
constructor() {
|
||||
this.animationHandle = `css${ifHasTransitionEnd ? 3 : 2}Animation`;
|
||||
}
|
||||
|
||||
generate(options) {
|
||||
Object.assign(this, options);
|
||||
return this[this.animationHandle]();
|
||||
}
|
||||
|
||||
css2Animation() {
|
||||
const style = {};
|
||||
style[this.horizontalDirection] = `${this.position[0]}px`;
|
||||
style[this.verticalDirection] = `${this.position[1]}px`;
|
||||
|
||||
this.mixAnimation(style);
|
||||
return style;
|
||||
}
|
||||
|
||||
css3Animation() {
|
||||
const style = {};
|
||||
|
||||
prefixes.map(prefix => {
|
||||
let x, y;
|
||||
|
||||
if (this.horizontalDirection === 'right') {
|
||||
x = this.containerWidth - this.size.width - this.position[0];
|
||||
} else {
|
||||
x = this.position[0];
|
||||
}
|
||||
|
||||
if (this.verticalDirection === 'bottom') {
|
||||
y = this.containerHeight - this.size.height - this.position[1];
|
||||
} else {
|
||||
y = this.position[1];
|
||||
}
|
||||
|
||||
style[`${prefix}Transform`] = `translate3d(${x}px, ${y}px, 0)`;
|
||||
});
|
||||
|
||||
this.mixAnimation(style);
|
||||
return style;
|
||||
}
|
||||
|
||||
mixAnimation(style) {
|
||||
if (!this.closeAnimation) {
|
||||
prefixes.map(prefix => {
|
||||
style[`${prefix}TransitionDuration`] = `${this.transitionDuration}s`;
|
||||
style[`${prefix}TransitionTimingFunction`] = this.transitionTimingFunction;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default AnimationManager;
|
||||
@@ -0,0 +1,175 @@
|
||||
<template>
|
||||
<div ref="container" :class="`${prefixClassName}-container`" :style="containerStyle">
|
||||
<slot></slot>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
GridSort
|
||||
} from 'autoresponsive-core3';
|
||||
import pkg from '../../package';
|
||||
import AnimationManager from './animation';
|
||||
import {onBeforeMount, onMounted, onUpdated, ref} from "vue";
|
||||
|
||||
const props = defineProps({
|
||||
containerWidth: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
containerHeight: {
|
||||
type: Number,
|
||||
default: null
|
||||
},
|
||||
gridWidth: {
|
||||
type: Number,
|
||||
default: 10
|
||||
},
|
||||
prefixClassName: {
|
||||
type: String,
|
||||
default: pkg.name
|
||||
},
|
||||
itemClassName: {
|
||||
type: String,
|
||||
default: 'item'
|
||||
},
|
||||
itemMargin: {
|
||||
type: Number,
|
||||
default: 0
|
||||
},
|
||||
horizontalDirection: {
|
||||
type: String,
|
||||
default: 'left'
|
||||
},
|
||||
transitionDuration: {
|
||||
type: [String, Number],
|
||||
default: 1
|
||||
},
|
||||
transitionTimingFunction: {
|
||||
type: String,
|
||||
default: 'linear'
|
||||
},
|
||||
verticalDirection: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
},
|
||||
closeAnimation: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
onItemDidLayout: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
},
|
||||
onContainerDidLayout: {
|
||||
type: Function,
|
||||
default: () => {}
|
||||
}
|
||||
});
|
||||
|
||||
const containerStyle = {
|
||||
position: 'relative'
|
||||
};
|
||||
|
||||
let animationManager, fixedContainerHeight;
|
||||
onBeforeMount(() => {
|
||||
animationManager = new AnimationManager();
|
||||
fixedContainerHeight = typeof props.containerHeight === 'number';
|
||||
})
|
||||
|
||||
const mixItemInlineStyle = (s) => {
|
||||
const itemMargin = props.itemMargin;
|
||||
let style = {
|
||||
display: 'block',
|
||||
float: 'left',
|
||||
margin: `0 ${itemMargin}px ${itemMargin}px 0`
|
||||
};
|
||||
|
||||
if (props.containerWidth) {
|
||||
style = {
|
||||
position: 'absolute'
|
||||
};
|
||||
}
|
||||
Object.assign(s, style);
|
||||
}
|
||||
|
||||
const container = ref(null);
|
||||
|
||||
const updateChildren = () => {
|
||||
const sortManager = new GridSort({
|
||||
containerWidth: props.containerWidth,
|
||||
gridWidth: props.gridWidth
|
||||
});
|
||||
sortManager.init();
|
||||
|
||||
let containerHeight = props.verticalDirection === 'bottom' || fixedContainerHeight ? props.containerHeight : 0;
|
||||
|
||||
const children = container.value.children;
|
||||
|
||||
for (let i = 0; i < children.length; i++) {
|
||||
const node = children[i];
|
||||
const canvas = node.__vnode.el;
|
||||
|
||||
let style = {};
|
||||
switch (canvas.style.constructor.name) {
|
||||
case 'CSS2Properties':
|
||||
Object.values(canvas.style).forEach((prop) => {
|
||||
style[prop] = canvas.style[prop];
|
||||
});
|
||||
break;
|
||||
case 'CSSStyleDeclaration':
|
||||
style = canvas.style;
|
||||
break;
|
||||
}
|
||||
|
||||
if (node.className &&
|
||||
props.itemClassName &&
|
||||
!~node.className.indexOf(props.itemClassName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const childWidth = parseInt(style.width, 10) + props.itemMargin;
|
||||
const childHeight = parseInt(style.height, 10) + props.itemMargin;
|
||||
const calculatedPosition = sortManager.getPosition(childWidth, childHeight);
|
||||
|
||||
if (fixedContainerHeight) {
|
||||
container.value.style.height = `${containerHeight}px`;
|
||||
} else {
|
||||
if (calculatedPosition[1] + childHeight > containerHeight) {
|
||||
containerHeight = calculatedPosition[1] + childHeight;
|
||||
container.value.style.height = `${containerHeight}px`;
|
||||
}
|
||||
}
|
||||
|
||||
const options = Object.assign({}, props, {
|
||||
position: calculatedPosition,
|
||||
size: {
|
||||
width: childWidth,
|
||||
height: childHeight
|
||||
},
|
||||
containerHeight: containerHeight
|
||||
});
|
||||
|
||||
const calculatedStyle = animationManager.generate(options);
|
||||
|
||||
mixItemInlineStyle(calculatedStyle);
|
||||
|
||||
Object.assign(node.style, calculatedStyle);
|
||||
|
||||
props.onItemDidLayout(node);
|
||||
|
||||
if (i + 1 === children.length) {
|
||||
props.onContainerDidLayout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateChildren();
|
||||
})
|
||||
|
||||
onUpdated(() => {
|
||||
updateChildren();
|
||||
})
|
||||
|
||||
</script>
|
||||
@@ -0,0 +1,176 @@
|
||||
<template>
|
||||
<div :class="$style['device-list']">
|
||||
<div :class="$style['form-search']">
|
||||
<label>类型:</label>
|
||||
<el-radio-group v-model="objectType" size="large">
|
||||
<el-radio :label="-1">全部</el-radio>
|
||||
<el-radio :label="0">人脸</el-radio>
|
||||
<el-radio :label="5">人体</el-radio>
|
||||
<el-radio :label="3">车</el-radio>
|
||||
<el-radio :label="4">电动车</el-radio>
|
||||
<el-radio :label="8">车牌</el-radio>
|
||||
<el-radio :label="11">猫</el-radio>
|
||||
<el-radio :label="12">狗</el-radio>
|
||||
</el-radio-group>
|
||||
</div>
|
||||
<image-list :data="ipcImgList" :containerHeight="imgListContainerHeight" @show="onShow"></image-list>
|
||||
<um-dialog v-model="visible" :custom-class="$style['img-preview-dialog']" title="" width="1100px"
|
||||
:footer="false">
|
||||
<div :style="{ height: '650px' }">
|
||||
<img-preview :id="imgPreviewInfo.id" :img-url="imgPreviewInfo.imgUrl"
|
||||
:position="imgPreviewInfo.position" @img-idx-change="onImgIdxChange">
|
||||
</img-preview>
|
||||
<div :class="$style['footer']">
|
||||
<div><span>抓拍时间:</span><span>{{ fDT(imgPreviewInfo.time) }}</span></div>
|
||||
<div><span>相机名称:</span><span>{{ imgPreviewInfo.deviceName }}</span></div>
|
||||
<div><span>类型:</span><span>{{ getObjectTypeName(imgPreviewInfo?.objectType) }}</span></div>
|
||||
<div><span>置信度:</span><span>{{ toFixed((imgPreviewInfo?.confidence || 0) / 100) }}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</um-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// import UmTableClassEnhance from "@/components/UmTable/UmByClassEnhance.vue";
|
||||
// import UmDialog from "@/components/UmDialog/index.vue";
|
||||
import { UmByClassEnhance as UmTableClassEnhance,UmDialog } from "funjiaui";
|
||||
// import CaptureObjectList from "@/containers/viewModel/CaptureObjectList";
|
||||
import ImgPreview from "@/components/ImgPreview.vue";
|
||||
import { ref, reactive, watch, onBeforeMount } from "vue";
|
||||
import * as api from "@/common/api";
|
||||
import { fDT } from "funjialib";
|
||||
import ImageList from "./ImageList.vue";
|
||||
import { toFixed, getObjectTypeName } from "@/common/tool"
|
||||
|
||||
const visible = ref(false);
|
||||
const imgIndex = ref(0);
|
||||
const ipcImgList = ref([]);
|
||||
const imgPreviewInfo = reactive({
|
||||
id: "",
|
||||
imgUrl: "",
|
||||
position: "",
|
||||
time: "",
|
||||
deviceName: "",
|
||||
confidence: "",
|
||||
objectType: ""
|
||||
})
|
||||
const objectType = ref<undefined | TObjectType>(-1);
|
||||
|
||||
const props = defineProps<{ deviceIds: [], imgListContainerHeight: number }>();
|
||||
|
||||
// CaptureObjectList.singleListen = (rowData) => {
|
||||
// initImgPreviewInfo(rowData);
|
||||
// imgIndex.value = (ipcImgList.value || []).findIndex(item => rowData.id == item.id) || 0;
|
||||
// visible.value = true;
|
||||
// }
|
||||
|
||||
onBeforeMount(() => {
|
||||
getData();
|
||||
})
|
||||
|
||||
watch(objectType, () => {
|
||||
getData();
|
||||
})
|
||||
|
||||
const getData = async () => {
|
||||
const { data } = await api.getCaputreImgListByIpcId({
|
||||
deviceIds: props?.deviceIds || undefined, objectType: objectType.value
|
||||
});
|
||||
ipcImgList.value = [];
|
||||
if (data && data.objectList) {
|
||||
ipcImgList.value = (data.objectList || []).sort((a, b) => {
|
||||
return new Date(b.time) - new Date(a.time);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const initImgPreviewInfo = (data) => {
|
||||
imgPreviewInfo.id = data?.["id"];
|
||||
imgPreviewInfo.imgUrl = data?.["imgUrl"];
|
||||
imgPreviewInfo.position = data?.["position"];
|
||||
imgPreviewInfo.time = data?.["time"];
|
||||
imgPreviewInfo.deviceName = data?.["deviceName"];
|
||||
imgPreviewInfo.confidence = data?.["confidence"];
|
||||
imgPreviewInfo.objectType = data?.["objectType"];
|
||||
}
|
||||
|
||||
watch(imgIndex, (i) => {
|
||||
initImgPreviewInfo(ipcImgList.value?.[i]);
|
||||
})
|
||||
|
||||
watch(visible, () => {
|
||||
if (!visible) {
|
||||
imgIndex.value = 0;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const onDataChange = (data) => {
|
||||
ipcImgList.value = data || [];
|
||||
}
|
||||
|
||||
const onImgIdxChange = (idx: TImgPreviewIdx) => {
|
||||
let tempIdx = imgIndex.value;
|
||||
tempIdx += idx;
|
||||
|
||||
if (tempIdx >= 0 && tempIdx < ipcImgList.value?.length) {
|
||||
imgIndex.value = tempIdx;
|
||||
}
|
||||
}
|
||||
|
||||
const onShow = (id) => {
|
||||
const rowData = (ipcImgList.value || []).find(item => id == item.id);
|
||||
if (rowData) {
|
||||
initImgPreviewInfo(rowData);
|
||||
imgIndex.value = (ipcImgList.value || []).findIndex(item => rowData.id == item.id) || 0;
|
||||
visible.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
const onFire = async (key: string, data: any) => {
|
||||
imgIndex.value = 0;
|
||||
const resData = await api.getCaputreImgListByIpcId({ id: data })
|
||||
ipcImgList.value = resData.data?.objectList || [];
|
||||
initImgPreviewInfo(ipcImgList.value?.[0]);
|
||||
visible.value = true;
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.device-list) {
|
||||
padding: 20px;
|
||||
height: calc(100% - 40px);
|
||||
overflow: auto;
|
||||
|
||||
:local(.form-search) {
|
||||
margin-bottom: 20px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
.el-radio-group {
|
||||
.el-radio__inner {
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
|
||||
&::after {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:local(.img-preview-dialog) {
|
||||
:local(.footer) {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
>div {
|
||||
margin-left: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,119 @@
|
||||
<template>
|
||||
<div :class="$style['header']">
|
||||
<!-- <img src="/logo.png" height="80"> -->
|
||||
<el-row :style="{ width: '100%' }">
|
||||
<el-col :span="3">
|
||||
<div class="system-name">
|
||||
出行管理
|
||||
<!-- <img src="/logo.png" height="60"> -->
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="21">
|
||||
<div :class="$style['header-content']">
|
||||
<el-dropdown trigger="click" :class="$style['user-panel']">
|
||||
<div ref="userPopup" class="header-user">
|
||||
<span class="name" :style="{ cursor: 'pointer' }">
|
||||
<el-link>
|
||||
{{ appStore.userInfo?.userName }}
|
||||
<el-icon class="el-icon--right">
|
||||
<arrow-down />
|
||||
</el-icon>
|
||||
</el-link>
|
||||
</span>
|
||||
</div>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="openPwdDialog">更改密码</el-dropdown-item>
|
||||
<el-dropdown-item @click="onLogout">退出登录</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<pwd-dialog ref="pwdRef" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import Cookies from "js-cookie";
|
||||
import { useAppStore } from "@/store/app";
|
||||
import PwdDialog from "@/containers/PwdDialog/index.vue";
|
||||
import { ref, onBeforeMount } from "vue";
|
||||
import {
|
||||
ArrowDown
|
||||
} from "@element-plus/icons-vue";
|
||||
import { useRouter } from "vue-router";
|
||||
|
||||
const appStore = useAppStore();
|
||||
const pwdRef = ref<any>();
|
||||
const router=useRouter();
|
||||
|
||||
onBeforeMount(() => {
|
||||
const userInfo = Cookies.get("UserInfo");
|
||||
try {
|
||||
const uI = JSON.parse(userInfo);
|
||||
if (!uI?.id) {
|
||||
router.push("/login");
|
||||
return;
|
||||
}
|
||||
appStore.saveUserInfo(uI);
|
||||
} catch (error) {
|
||||
router.push("/login");
|
||||
}
|
||||
})
|
||||
|
||||
const onLogout = () => {
|
||||
Cookies.remove("Authorization")
|
||||
window.location.href = '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
const openPwdDialog = () => {
|
||||
pwdRef.value?.show();
|
||||
};
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.header) {
|
||||
height: 80px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
|
||||
.system-name {
|
||||
height: 80px;
|
||||
overflow: hidden;
|
||||
line-height: 80px;
|
||||
background: #002140;
|
||||
// display: inline-block;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-weight: 600;
|
||||
font-size: 20px;
|
||||
width: 100%;
|
||||
line-height: 80px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
:local(.header-content) {
|
||||
box-shadow: 0 1px 4px rgb(0 21 41 / 8%);
|
||||
height: 80px; // calc(100% - 4px);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
|
||||
:local(.user-panel) {
|
||||
position: absolute;
|
||||
right: 40px;
|
||||
top: 50%;
|
||||
transform: translate(0, -50%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,84 @@
|
||||
<template>
|
||||
<div :style="style" :class="$style['image-cell']" @click="$emit('show', id)">
|
||||
<div :class="$style['img-container']">
|
||||
<div class="img-box">
|
||||
<img :src="src">
|
||||
<div class="footer">
|
||||
<div>
|
||||
<div>时间:{{ fDT(info?.time) }}</div>
|
||||
<div>置信度:{{ toFixed((info?.confidence || 0) / 100) }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div>类型:{{ getObjectTypeName(info?.objectType) }}</div>
|
||||
<div>地点:{{ info?.deviceName || '--' }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { toFixed, getObjectTypeName } from "@/common/tool"
|
||||
import { fDT } from "funjialib";
|
||||
|
||||
const props = defineProps<{
|
||||
id: any,
|
||||
src: string,
|
||||
confidence: any,
|
||||
info: any,
|
||||
style: any
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(n: 'show', id: any): void
|
||||
}>();
|
||||
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.image-cell) {
|
||||
:local(.img-container) {
|
||||
padding: 0 10px 10px 10px;
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 40px);
|
||||
|
||||
.img-box {
|
||||
background-color: #d3d3d3;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
|
||||
img {
|
||||
object-fit: contain;
|
||||
height: calc(100% - 40px);
|
||||
}
|
||||
|
||||
.footer {
|
||||
width: 100px;
|
||||
// border: 2px solid #989898;
|
||||
color: #000;
|
||||
border: 0;
|
||||
// border-left-width: 6px;
|
||||
height: 50px;
|
||||
width: 100%; //calc(100% - 8px);
|
||||
// line-height: 36px;
|
||||
padding-left: 4px;
|
||||
font-size: 14px;
|
||||
background-color: #FFF;
|
||||
|
||||
&>div {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
padding: 0 4px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,58 @@
|
||||
<template>
|
||||
<div :class="$style['image-list']">
|
||||
<fixed-size-grid :key="gridKey" :columnCount="columnCount" :columnWidth="304" :height="containerHeight || 600"
|
||||
:rowCount="rowCount" :rowHeight="240" :width="1560">
|
||||
<template v-slot:default="slotProps">
|
||||
<image-cell v-if="isShowColumn(slotProps)" :key="getData(slotProps)?.id || slotProps.key"
|
||||
:style="slotProps.style" :id="getData(slotProps)?.id" :src="getData(slotProps)?.objectUrl"
|
||||
:confidence="getData(slotProps)?.confidence" :info="{ ...getData(slotProps) }"
|
||||
@show="(id) => $emit('show', id)">
|
||||
</image-cell>
|
||||
</template>
|
||||
</fixed-size-grid>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { FixedSizeGrid } from '@kousum/vue3-window';
|
||||
import ImageCell from "./ImageCell.vue";
|
||||
import { watch, ref, computed } from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{ data: any[], containerHeight: number }>(), {
|
||||
containerHeight: 600
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(n: 'show', id: any): void
|
||||
}>();
|
||||
|
||||
|
||||
const gridKey = ref(-1);
|
||||
const columnCount = ref(5);
|
||||
|
||||
watch(() => props.data, (v) => {
|
||||
gridKey.value = new Date().getTime();
|
||||
})
|
||||
|
||||
const rowCount = computed(() => {
|
||||
const len = props.data?.length || 0;
|
||||
let _rowCount = len / columnCount.value;
|
||||
if (len % columnCount.value) {
|
||||
_rowCount += 1;
|
||||
}
|
||||
return _rowCount;
|
||||
})
|
||||
|
||||
const getData = (prop) => {
|
||||
return props.data?.[prop.rowIndex * columnCount.value + prop.columnIndex];
|
||||
}
|
||||
|
||||
const isShowColumn = (prop) => {
|
||||
return (props.data?.length || 0) > prop.rowIndex * columnCount.value + prop.columnIndex;
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.image-list) {
|
||||
width: 1400px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,117 @@
|
||||
<template>
|
||||
<div :class="$style['license-update']">
|
||||
<single-upload @file-change="onFileChange" />
|
||||
<el-button :class="$style['submit']" :disabled="!enableSubmit" @click="onSubmit">更新</el-button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ElMessage } from "element-plus";
|
||||
import { decode } from "@/common/tool";
|
||||
import SingleUpload from '@/components/SingleUpload.vue';
|
||||
import { ref } from "vue";
|
||||
|
||||
const enableSubmit = ref(false);
|
||||
// 当前浏览的文件
|
||||
const currentFile = ref();
|
||||
|
||||
const verifyFileSize = (size: number) => {
|
||||
const maxKb = 1;
|
||||
const minB = 300;
|
||||
const fileMaxSize = maxKb * 1024;
|
||||
const fileMinSize = minB;
|
||||
|
||||
if (size > fileMaxSize) {
|
||||
ElMessage.warning("附件大小不能大于" + maxKb + "KB!");
|
||||
return false;
|
||||
}
|
||||
else if (size < fileMinSize) {
|
||||
ElMessage.warning("附件大小不能小于" + minB + "B!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const verifyUploadFile = (file: File) => {
|
||||
const //fileName = rawFile.name,
|
||||
fileSize = file.size;
|
||||
const methodMap = [[verifyFileSize], [fileSize]];
|
||||
let result = true;
|
||||
let method: any, params;
|
||||
for (let i = 0; i < methodMap[0].length; i++) {
|
||||
method = methodMap[0][i];
|
||||
params = methodMap[1][i];
|
||||
|
||||
if (result && method) {
|
||||
result = method?.(params);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const onFileChange = (file: File) => {
|
||||
currentFile.value = null;
|
||||
enableSubmit.value = verifyUploadFile(file);
|
||||
if (enableSubmit.value) {
|
||||
currentFile.value = file;
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
const file = currentFile.value;
|
||||
const p = new Promise((resolve, reject) => {
|
||||
// 获取文件内容
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file);//发起异步请求
|
||||
reader.onload = function () {
|
||||
try {
|
||||
let value: any = this.result?.split?.('base64,')?.slice(1);
|
||||
// base64转文本
|
||||
value = decode(value.join(""));
|
||||
|
||||
resolve([true, value]);
|
||||
} catch (error) {
|
||||
resolve([false]);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
const [result, value] = await p;
|
||||
if (result) {
|
||||
try {
|
||||
const { status, reason } = await (await fetch('/api/system/license/update', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ license: value })
|
||||
})).json()
|
||||
|
||||
if (status == "200") {
|
||||
ElMessage.success("更新成功");
|
||||
}
|
||||
else {
|
||||
ElMessage.error(reason || "更新失败");
|
||||
}
|
||||
} catch (error) {
|
||||
ElMessage.error("更新失败");
|
||||
}
|
||||
}
|
||||
else {
|
||||
ElMessage.error("文件格式不正确");
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.license-update) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
:local(.submit) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<el-form :inline="true" :model="form">
|
||||
<el-form-item prop="autoRefersh" label="自动刷新">
|
||||
<el-checkbox v-model="form.autoRefersh"></el-checkbox>
|
||||
</el-form-item>
|
||||
<el-form-item prop="level" label="日志级别">
|
||||
<el-select v-model="form.level">
|
||||
<el-option v-for="item of levelList" :value="item.name">
|
||||
{{ item.name }}
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="setLogLevel">设置日志级别</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<variable-size-list :class="$style['log-list']" :height="800" :itemCount="logData.length" :itemSize="getItemSize"
|
||||
width="100%">
|
||||
<template v-slot:default="slotProps">
|
||||
<div :key="slotProps.key" :style="slotProps.style" class="log-row">
|
||||
{{ logData[slotProps.index]?.info }}
|
||||
</div>
|
||||
</template>
|
||||
</variable-size-list>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onBeforeMount, watch, onBeforeUnmount } from "vue";
|
||||
import * as api from "@/common/api";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { VariableSizeList } from '@kousum/vue3-window';
|
||||
|
||||
const levelList = ref([]);
|
||||
const form = reactive({
|
||||
level: "info",
|
||||
autoRefersh: true
|
||||
})
|
||||
const props = {
|
||||
value: 'id',
|
||||
label: 'info',
|
||||
}
|
||||
const logData = ref([]);
|
||||
// 读取日志行索引
|
||||
const logIdx = ref(0);
|
||||
const loopToken = ref(-1);
|
||||
|
||||
watch(() => form.autoRefersh, (v) => {
|
||||
if (v) {
|
||||
enableLoopLog();
|
||||
}
|
||||
else {
|
||||
noEnableLoopLog();
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeMount(() => {
|
||||
getLevelList();
|
||||
getLogList();
|
||||
|
||||
if (form.autoRefersh) {
|
||||
enableLoopLog();
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
noEnableLoopLog();
|
||||
})
|
||||
|
||||
// These row heights are arbitrary.
|
||||
// Yours should be based on the content of the row.
|
||||
const rowHeights = new Array(1000)
|
||||
.fill(true)
|
||||
.map(() => 25 + Math.round(Math.random() * 50));
|
||||
|
||||
const getItemSize = index => {
|
||||
const len = logData.value?.[index]?.info?.length;
|
||||
let row = logData.value?.[index].info.length / 200
|
||||
if (len % 200 > 0) {
|
||||
row += 1;
|
||||
}
|
||||
return row * 30;
|
||||
};
|
||||
|
||||
const getLevelList = async () => {
|
||||
const data = await api.getLevelList();
|
||||
if (data) {
|
||||
form.level = data?.level_name;
|
||||
levelList.value = data?.levels || [];
|
||||
}
|
||||
}
|
||||
|
||||
const getLogList = async () => {
|
||||
const data = await api.getLogList({ index: logIdx.value });
|
||||
if (logIdx.value != 0) {
|
||||
logData.value = (data?.logs || []).sort((a, b) => {
|
||||
return new Date(b.time) - new Date(a.time);
|
||||
}).concat(logData.value);
|
||||
} else {
|
||||
logData.value = (data?.logs || []).sort((a, b) => {
|
||||
return new Date(b.time) - new Date(a.time);
|
||||
});
|
||||
}
|
||||
logIdx.value = data?.max_index || 0;
|
||||
}
|
||||
|
||||
const enableLoopLog = () => {
|
||||
clearTimeout(loopToken.value);
|
||||
|
||||
loopToken.value = setTimeout(async () => {
|
||||
await getLogList();
|
||||
if (form.autoRefersh) {
|
||||
enableLoopLog();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const noEnableLoopLog = () => {
|
||||
clearTimeout(loopToken.value);
|
||||
}
|
||||
|
||||
const setLogLevel = async () => {
|
||||
logData.value = [];
|
||||
const data = await api.setLogLevel({ level: form.level })
|
||||
if (data) {
|
||||
logIdx.value = 0;
|
||||
clearTimeout();
|
||||
getLogList();
|
||||
if (form.autoRefersh) {
|
||||
enableLoopLog();
|
||||
}
|
||||
ElMessage.success("设置成功")
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.log-list) {
|
||||
.log-row {
|
||||
// line-height: 60px;
|
||||
// padding: 0 20px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.log-row:hover {
|
||||
background-color: #d5d5d5;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,93 @@
|
||||
<template>
|
||||
<el-form ref="formRef" label-width="80px" :model="formModel" :rules="rules">
|
||||
<el-form-item label="用户名">
|
||||
<span>{{ appStore.userinfo.userName }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="原密码" prop="oldPassword">
|
||||
<el-input placeholder="请输入" show-password v-model="formModel.oldPassword" />
|
||||
</el-form-item>
|
||||
<el-form-item label="新密码" prop="newPassword">
|
||||
<el-input placeholder="6-18个字母,区分大小写" show-password v-model="formModel.newPassword" />
|
||||
</el-form-item>
|
||||
<el-form-item label="确认密码" prop="newPassword2">
|
||||
<el-input placeholder="请再次输入密码" show-password v-model="formModel.newPassword2" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export interface PwdFormRefProps {
|
||||
submit: () => void;
|
||||
}
|
||||
|
||||
interface FormDataProps {
|
||||
oldPassword: string;
|
||||
newPassword: string;
|
||||
newPassword2: string;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref } from "vue";
|
||||
import type { FormItemRule } from "element-plus";
|
||||
import { useAppStore } from "@/store";
|
||||
|
||||
/**
|
||||
* emit
|
||||
*/
|
||||
const emit = defineEmits<{
|
||||
(e: "onLoading", v: boolean): void;
|
||||
(e: "onSuccess"): void;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const formRef = ref();
|
||||
const formModel = reactive<FormDataProps>({
|
||||
oldPassword: "",
|
||||
newPassword: "",
|
||||
newPassword2: "",
|
||||
});
|
||||
const rules = ref<Record<string, FormItemRule | FormItemRule[]>>({
|
||||
oldPassword: {
|
||||
required: true,
|
||||
message: "请输入原密码",
|
||||
},
|
||||
newPassword: {
|
||||
required: true,
|
||||
message: "请输入新密码",
|
||||
},
|
||||
newPassword2: {
|
||||
required: true,
|
||||
message: "请确认新密码",
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
const onUpdate = () => {
|
||||
setTimeout(() => {
|
||||
emit("onLoading", false);
|
||||
emit("onSuccess");
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交表单
|
||||
*/
|
||||
const onSubmit = () => {
|
||||
formRef.value.validate((valid: boolean) => {
|
||||
if (valid) {
|
||||
emit("onLoading", true);
|
||||
onUpdate();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* 暴露给父组件
|
||||
*/
|
||||
defineExpose({
|
||||
submit: onSubmit,
|
||||
} as PwdFormRefProps);
|
||||
</script>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<um-dialog v-model="visible" title="修改密码" width="328px" :footer="false">
|
||||
<um-table-form :target="UpdatePassword" :data="formData" @ok="onResetSubmit" @cancel="onResetCancel">
|
||||
</um-table-form>
|
||||
</um-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
export interface PwdDialogRefProps {
|
||||
/**
|
||||
* 打开弹窗
|
||||
*/
|
||||
show: () => void;
|
||||
}
|
||||
</script>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from "vue";
|
||||
import PwdForm, { PwdFormRefProps } from "./PwdForm.vue";
|
||||
import { UmTableForm,UmDialog } from "funjiaui";
|
||||
import { UpdatePassword } from "@/containers/viewModel/UpdatePassword";
|
||||
import { useAppStore } from "@/store/app";
|
||||
import * as api from "@/common/api";
|
||||
import { ElMessage } from "element-plus";
|
||||
import { useRouter } from "vue-router";
|
||||
import md5 from "md5";
|
||||
import { encode } from "@/common/tool"
|
||||
|
||||
/**
|
||||
* emit
|
||||
*/
|
||||
const emit = defineEmits<{
|
||||
(e: "onSuccess"): void;
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
const visible = ref<boolean>(false);
|
||||
const formRef = ref<PwdFormRefProps>();
|
||||
const loading = ref<boolean>(false);
|
||||
const app = useAppStore();
|
||||
const formData = ref<any>({
|
||||
userName: app.userInfo?.userName,
|
||||
});
|
||||
|
||||
/**
|
||||
* 修改loading状态
|
||||
*/
|
||||
const onLoading = (v: boolean) => {
|
||||
loading.value = v;
|
||||
};
|
||||
|
||||
/**
|
||||
* 表单操作成功
|
||||
*/
|
||||
const onSuccess = () => {
|
||||
visible.value = false;
|
||||
loading.value = false;
|
||||
emit("onSuccess");
|
||||
};
|
||||
|
||||
/**
|
||||
* 提交表单
|
||||
*/
|
||||
const onOk = () => {
|
||||
formRef.value?.submit();
|
||||
};
|
||||
|
||||
const onResetSubmit = async (formData: any) => {
|
||||
const params = {
|
||||
old_password: md5(formData.oldPassword),
|
||||
new_password: encode(formData.password),
|
||||
};
|
||||
|
||||
const { status, reason } = await api.udpdatePwd(params);
|
||||
if (status != "200") {
|
||||
ElMessage.error(reason || "修改失败");
|
||||
} else {
|
||||
ElMessage.success("修改密码成功,3秒内将退出登录");
|
||||
setTimeout(() => {
|
||||
router.push("/login");
|
||||
}, 3000);
|
||||
}
|
||||
};
|
||||
|
||||
const onResetCancel = (value: boolean) => {
|
||||
visible.value = value;
|
||||
};
|
||||
|
||||
/**
|
||||
* 暴露给父组件
|
||||
*/
|
||||
defineExpose({
|
||||
show: () => {
|
||||
visible.value = true;
|
||||
},
|
||||
} as PwdDialogRefProps);
|
||||
</script>
|
||||
|
||||
<style scoped></style>
|
||||
@@ -0,0 +1,154 @@
|
||||
<template>
|
||||
<div :class="$style['system-info']">
|
||||
<h2>系统信息</h2>
|
||||
<div :class="$style['content']">
|
||||
<div class="info" v-for="item in data">
|
||||
<span>{{ item.name }}:</span>
|
||||
<span>{{ item.value }}</span>
|
||||
<!-- <el-divider border-style="dotted" /> -->
|
||||
</div>
|
||||
<div class="info">
|
||||
<span>授权状态:</span>
|
||||
<div :class="[$style['license-status'], license.valid ? '' : 'no-valid']">
|
||||
<span>{{ license.valid ? "有效" : "无效" }}</span>
|
||||
<span>{{ license.errorReason }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info">
|
||||
<span>授权有效期:</span>
|
||||
<span>{{ license.expire ?? '--' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as api from "@/common/api";
|
||||
import { onBeforeMount, reactive, onBeforeUnmount, ref } from "vue";
|
||||
class v {
|
||||
name: any = ""
|
||||
value: any = ""
|
||||
VFormat: any = null;
|
||||
constructor(arg: any = null) {
|
||||
this.name = arg?.name;
|
||||
this.VFormat = arg?.vFormat;
|
||||
}
|
||||
}
|
||||
const format = (v) => {
|
||||
return parseFloat(v).toFixed(2) + "%";
|
||||
}
|
||||
|
||||
const data = reactive({
|
||||
"cpu": new v({ name: "CPU", vFormat: format }),
|
||||
"memory": new v({ name: "Memory", vFormat: format }),
|
||||
"tup_util": new v({ name: "TPU", vFormat: format }),
|
||||
"tup_memory": new v({ name: "TPU memory" }),
|
||||
"sn": new v({ name: "SN" }),
|
||||
"commit_id": new v({ name: "Commit id" }),
|
||||
"commit_time": new v({ name: "Commit time" }),
|
||||
"version": new v({ name: "版本" }),
|
||||
"run_time": new v({ name: "启动时间" }),
|
||||
"task_sum": new v({ name: "设备数" }),
|
||||
"snap_count": new v({ name: "抓拍图片总数" }),
|
||||
});
|
||||
const license = reactive({
|
||||
expire: null,
|
||||
errorReason: "",
|
||||
valid: true
|
||||
})
|
||||
|
||||
const loopToken = ref(-1);
|
||||
|
||||
onBeforeMount(() => {
|
||||
getData();
|
||||
enableLoop();
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => { noEnableLoop() })
|
||||
|
||||
const enableLoop = () => {
|
||||
clearTimeout(loopToken.value)
|
||||
loopToken.value = setTimeout(() => {
|
||||
getData();
|
||||
enableLoop();
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
const noEnableLoop = () => {
|
||||
clearTimeout(loopToken.value)
|
||||
}
|
||||
|
||||
const getData = async () => {
|
||||
const resData = await api.getSytemInfo();
|
||||
if (resData) {
|
||||
let item = null;
|
||||
for (const key in resData) {
|
||||
item = data?.[key];
|
||||
if (item) {
|
||||
if (item.VFormat) {
|
||||
item.value = item.VFormat(resData[key])
|
||||
}
|
||||
else {
|
||||
item.value = resData[key]
|
||||
}
|
||||
|
||||
if (!item.name) {
|
||||
// 如果未设置名称则以该字段作为name
|
||||
item.name = key;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
license.expire = resData?.license?.expire;
|
||||
license.errorReason = resData?.license?.error_reason;
|
||||
license.valid = resData?.license?.valid;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.system-info) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
|
||||
:local(.content) {
|
||||
padding: 20px;
|
||||
|
||||
.info {
|
||||
margin-bottom: 18px;
|
||||
|
||||
span {
|
||||
font-size: 18px;
|
||||
|
||||
&:first-child {
|
||||
display: inline-block;
|
||||
width: 180px;
|
||||
text-align: right;
|
||||
}
|
||||
}
|
||||
|
||||
.el-divider {
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
:local(.license-status) {
|
||||
display: inline-flex;
|
||||
flex-direction: column;
|
||||
justify-content: flex-start;
|
||||
|
||||
span {
|
||||
text-align: left !important;
|
||||
width: auto !important;
|
||||
margin-right: 40px;
|
||||
}
|
||||
|
||||
// align-items: center;
|
||||
|
||||
&.no-valid {
|
||||
color: red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<div :class="$style['up-server']">
|
||||
<h2>平台注册配置</h2>
|
||||
<um-table-form :key="formKey" ref="formRef" :target="UpServer" :data="formData" :is-show-default-submit="false"
|
||||
@ok="onResetSubmit">
|
||||
</um-table-form>
|
||||
<div :style="{ textAlign: 'center', width: '500px' }">
|
||||
<el-button @click="onSave">保存</el-button>
|
||||
<el-button @click="onRefreshStatus">刷新状态</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import UpServer from '@/containers/viewModel/UpServer';
|
||||
import { reactive, ref, onBeforeMount } from 'vue';
|
||||
import * as api from "@/common/api";
|
||||
import { ElMessage } from 'element-plus';
|
||||
|
||||
const formData = reactive(new UpServer());
|
||||
const formRef = ref();
|
||||
const formKey = ref(-1);
|
||||
|
||||
onBeforeMount(() => {
|
||||
onRefreshStatus();
|
||||
})
|
||||
|
||||
const onSave = () => {
|
||||
formRef?.value.onSubmit?.();
|
||||
}
|
||||
|
||||
const onRefreshStatus = async () => {
|
||||
const data = await api.getServiceStatus();
|
||||
if (data) {
|
||||
formData.id = data?.id;
|
||||
formData.ip = data?.manage?.ip;
|
||||
formData.port = data?.manage?.port;
|
||||
formData.manage_status = data?.manage?.status;
|
||||
formData.kafka_ip = data?.kafka?.ip;
|
||||
formData.kafka_port = data?.kafka?.port;
|
||||
formData.kafka_status = data?.kafka?.status;
|
||||
if (data?.manage?.enable) {
|
||||
formData.type = 1;
|
||||
} else {
|
||||
formData.type = 0;
|
||||
}
|
||||
formKey.value = new Date().getTime();
|
||||
}
|
||||
}
|
||||
|
||||
const onResetSubmit = async (params) => {
|
||||
const data = await api.setService(params);
|
||||
if (data) {
|
||||
onRefreshStatus();
|
||||
ElMessage.success('提交成功');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.up-server) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
|
||||
.el-form {
|
||||
width: 500px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,371 @@
|
||||
<template>
|
||||
<el-upload v-if="false" class="upload-demo" drag action="/api/system/update" accept=".zip,.bin,.gz"
|
||||
:multiple="false" :limit="1" :auto-upload="true" @progress="onProgress" @error="onError" @success="onSuccess">
|
||||
<el-icon class="el-icon--upload">
|
||||
<upload-filled />
|
||||
</el-icon>
|
||||
<div class="el-upload__text">
|
||||
可拖动文件到这里 <em>点击上传文件</em>
|
||||
</div>
|
||||
<template #tip>
|
||||
<div class="el-upload__tip">
|
||||
支持[.zip,.bin,.gz]文件类型,文件需小于1GB
|
||||
</div>
|
||||
</template>
|
||||
</el-upload>
|
||||
<div :class="$style['upgrade-system']">
|
||||
<single-upload accept=".zip,.bin,.gz" @file-change="onFileChange" />
|
||||
<el-button :class="$style['submit']" :disabled="!enableSubmit" @click="onSubmit">升级</el-button>
|
||||
</div>
|
||||
|
||||
<um-dialog v-model="visible" title="提示" :footer="null" :show-close="showDialogClose" :close-on-press-escape="false"
|
||||
width="640px" @update:model-value="onModelValueChange">
|
||||
<div :style="{ height: '120px' }">
|
||||
<div class="upload-file-progress">
|
||||
<label>文件上传进度:</label>
|
||||
<el-progress :text-inside="true" :stroke-width="18" :percentage="percentage"></el-progress>
|
||||
</div>
|
||||
<div class="ugrade-system-progress">
|
||||
<label>系统升级进度:</label>
|
||||
<el-progress :text-inside="true" :stroke-width="18" :percentage="upgradeProgressParams.percentage">
|
||||
</el-progress>
|
||||
</div>
|
||||
<div class="tip">
|
||||
{{ msg }}
|
||||
</div>
|
||||
</div>
|
||||
</um-dialog>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ElMessage, ElProgress, UploadFiles, UploadProgressEvent, UploadRawFile } from "element-plus";
|
||||
import {
|
||||
UploadFilled
|
||||
} from '@element-plus/icons-vue';
|
||||
import { reactive, ref, h, watch, onBeforeUnmount } from "vue";
|
||||
import { toFixed } from "@/common/tool";
|
||||
import { useRouter, onBeforeRouteLeave } from "vue-router";
|
||||
import CountDownMessage from "@/components/CountDownMessage.vue";
|
||||
import { merge } from "lodash-es";
|
||||
import SingleUpload from '@/components/SingleUpload.vue';
|
||||
import axios from "axios";
|
||||
|
||||
const PINGURL = "/api/login/ping";
|
||||
// 请求成功时得到的步长,基本上4次请求就可以确定程序重启成功了
|
||||
const PONGSTEP = 1 / 4;
|
||||
|
||||
const showDialogClose = ref(false);
|
||||
const percentage = ref(0);
|
||||
const upgradeProgressParams = reactive({
|
||||
percentage: 0,
|
||||
startTime: null,
|
||||
maxTimeSpan: 60,// 单位:秒
|
||||
});
|
||||
const visible = ref(false);
|
||||
const msg = ref();
|
||||
const coutDownTOV = ref(-1);
|
||||
const router = useRouter();
|
||||
const enableSubmit = ref(false);
|
||||
const isStop = ref(false);
|
||||
// 当前浏览的文件
|
||||
const currentFile = ref();
|
||||
|
||||
watch(visible, (v) => {
|
||||
if (!v) {
|
||||
msg.value = "";
|
||||
showDialogClose.value = false;
|
||||
percentage.value = 0;
|
||||
upgradeProgressParams.percentage = 0;
|
||||
upgradeProgressParams.startTime = null;
|
||||
coutDownTOV.value = -1;
|
||||
enableSubmit.value = false;
|
||||
isStop.value = false;
|
||||
}
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('beforeunload', closeWindow);
|
||||
clearTimeout(coutDownTOV.value);
|
||||
})
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
if (!upgradeProgressParams.startTime) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ElMessage.warning('任务进行中,请不要操作系统');
|
||||
}
|
||||
return false;
|
||||
})
|
||||
|
||||
const verifyFileType = (fileName: string) => {
|
||||
const fileTypes = [".bin", ".zip", ".gz"];
|
||||
const fileEnd = fileName.substring(fileName.lastIndexOf("."));
|
||||
if (!fileTypes.includes(fileEnd)) {
|
||||
ElMessage.warning("不支持该类型文件");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const verifyFileSize = (size: number) => {
|
||||
const maxMb = 1;
|
||||
const minKb = 14;
|
||||
const fileMaxSize = 1024 * 1024 * 1024 * maxMb;//300M
|
||||
const fileMinSize = 1024 * 14;
|
||||
|
||||
if (size > fileMaxSize) {
|
||||
ElMessage.warning("附件大小不能大于" + maxMb + "GB!");
|
||||
return false;
|
||||
}
|
||||
else if (size < fileMinSize) {
|
||||
ElMessage.warning("附件大小不能小于" + minKb + "KB!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const verifyUploadFile = (file: File) => {
|
||||
const fileName = file.name, fileSize = file.size;
|
||||
const methodMap = [[verifyFileType, verifyFileSize], [fileName, fileSize]];
|
||||
let result = true;
|
||||
let method: any, params;
|
||||
for (let i = 0; i < methodMap[0].length; i++) {
|
||||
method = methodMap[0][i];
|
||||
params = methodMap[1][i];
|
||||
|
||||
if (result && method) {
|
||||
result = method?.(params);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// // 隐藏弹出框右上角关闭按钮
|
||||
// if (result) {
|
||||
// // showDialogClose.value = true;
|
||||
// }
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const onError = (response: any) => {
|
||||
console.log("response:", response);
|
||||
msg.value = response;
|
||||
ElMessage.error("文件上传失败");
|
||||
}
|
||||
|
||||
const onSuccess = (response: any) => {
|
||||
if (response.status == "200") {
|
||||
enableProgress('');
|
||||
msg.value = "文件上传成功";
|
||||
ElMessage.success("文件上传成功");
|
||||
}
|
||||
else {
|
||||
ElMessage.error(response.reason || "文件上传失败");
|
||||
msg.value = response?.reason
|
||||
showDialogClose.value = true;
|
||||
}
|
||||
}
|
||||
|
||||
const onModelValueChange = (value: boolean) => {
|
||||
visible.value = value;
|
||||
}
|
||||
|
||||
const onProgress = (evt: UploadProgressEvent) => {
|
||||
visible.value = true;
|
||||
percentage.value = toFixed(evt.percent / 100);
|
||||
}
|
||||
|
||||
const closeWindow = (event) => {
|
||||
// Cancel the event as stated by the standard.
|
||||
event.preventDefault();
|
||||
// Chrome requires returnValue to be set.
|
||||
event.returnValue = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用进度条
|
||||
*/
|
||||
const enableProgress = (succMsg) => {
|
||||
window.removeEventListener('beforeunload', closeWindow);
|
||||
window.addEventListener('beforeunload', closeWindow);
|
||||
|
||||
upgradeProgressParams.startTime = new Date();
|
||||
enableCountDown(succMsg);
|
||||
}
|
||||
|
||||
const enableCountDown = (succMsg) => {
|
||||
clearTimeout(coutDownTOV.value);
|
||||
const uPP = upgradeProgressParams;
|
||||
coutDownTOV.value = setTimeout(async () => {
|
||||
msg.value = "系统升级中...";
|
||||
const timeSpan = new Date().getTime() - (uPP.startTime || new Date())?.getTime();
|
||||
if ((timeSpan / uPP.maxTimeSpan / 1000) > 4 / 5) {
|
||||
uPP.percentage = uPP.percentage < 80 ? 80 : uPP.percentage;
|
||||
}
|
||||
else {
|
||||
const percentage = toFixed(timeSpan / 1000 / uPP.maxTimeSpan);
|
||||
uPP.percentage = uPP.percentage < percentage ? percentage : uPP.percentage;
|
||||
}
|
||||
|
||||
const pong = await pingService();
|
||||
// console.log("pong:", pong)
|
||||
if (pong && isStop.value) {
|
||||
// 只要能正确获取返回值,一般4秒内就可以判定服务已经启动成功
|
||||
uPP.percentage = toFixed((parseFloat(uPP.percentage) / 100 + PONGSTEP));
|
||||
}
|
||||
else if (!pong) {
|
||||
// 先验证是否停止了服务,再验证服务是否启动了
|
||||
isStop.value = true;
|
||||
|
||||
}
|
||||
|
||||
if (uPP.percentage < 100) {
|
||||
enableCountDown(succMsg);
|
||||
}
|
||||
else {
|
||||
uPP.percentage = 100;
|
||||
showMsg();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/** ping接口服务,如果ping通代表服务当前是启动状态 */
|
||||
const pingService = async (): Promise<boolean> => {
|
||||
try {
|
||||
// 用于终止Fetch请求的控制器。
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
|
||||
// Promise.race 可以处理异步请求的先后问题,这里用于
|
||||
// 处理接口是否按预期请求完成
|
||||
const data = await Promise.race([
|
||||
fetch(PINGURL, { signal }),
|
||||
new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
// 终止目标请求
|
||||
controller.abort();
|
||||
resolve({ error: true });
|
||||
}, 2000)
|
||||
})
|
||||
]).catch((err) => {
|
||||
return { error: true };
|
||||
})
|
||||
|
||||
console.log("ping-log:", data);
|
||||
// 数据中未返回error字段或字段不为true时,
|
||||
// 或者接口返回值中status是200|401 则表明请求是成功的
|
||||
// return !!!data?.error || [200, 401].includes(data?.status);
|
||||
return data?.error !== true || data?.statusText == "Not Found"; //data?.includes?.("File not found") || false
|
||||
} catch (error) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const showMsg = () => {
|
||||
msg.value = "系统升级成功";
|
||||
const msgInstance = ElMessage({
|
||||
type: "success",
|
||||
message: h(CountDownMessage, {
|
||||
getMsgTempalte(value: number) {
|
||||
return `操作成功,${value}秒倒计时后将跳转到登录页!`
|
||||
},
|
||||
onNext() {
|
||||
msgInstance.close();
|
||||
clear();
|
||||
router.push('/login');
|
||||
}
|
||||
}),
|
||||
duration: 10 * 1000
|
||||
});
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
merge(upgradeProgressParams, {
|
||||
percentage: 0,
|
||||
startTime: null,
|
||||
maxTimeSpan: 30,// 单位:秒
|
||||
});
|
||||
|
||||
clearTimeout(coutDownTOV.value);
|
||||
}
|
||||
|
||||
const onFileChange = (file: File) => {
|
||||
currentFile.value = null;
|
||||
enableSubmit.value = verifyUploadFile(file);
|
||||
if (enableSubmit.value) {
|
||||
currentFile.value = file;
|
||||
}
|
||||
}
|
||||
|
||||
const onSubmit = async () => {
|
||||
const file = currentFile.value;
|
||||
visible.value = true;
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
|
||||
try {
|
||||
const { status, reason } = await axios.post('/api/system/update', formData,
|
||||
{
|
||||
//获取上传进度
|
||||
onUploadProgress: function (pE) {
|
||||
// console.log('进度', progressEvent)
|
||||
percentage.value = Math.round(pE.loaded / pE.total * 100);
|
||||
}
|
||||
}
|
||||
).catch(error => {
|
||||
// _this.$Message.error(error)
|
||||
})
|
||||
|
||||
if (status == "200") {
|
||||
enableProgress('');
|
||||
msg.value = "文件上传成功";
|
||||
ElMessage.success("文件上传成功");
|
||||
}
|
||||
else {
|
||||
ElMessage.error(reason || "文件上传失败");
|
||||
msg.value = reason
|
||||
showDialogClose.value = true;
|
||||
}
|
||||
} catch (error) {
|
||||
msg.value = "文件上传失败";
|
||||
showDialogClose.value = true;
|
||||
// ElMessage.error("文件上传失败");
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
|
||||
.upload-file-progress,
|
||||
.ugrade-system-progress {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
.el-progress {
|
||||
width: 500px;
|
||||
}
|
||||
}
|
||||
|
||||
.ugrade-system-progress {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
:local(.upgrade-system) {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
:local(.submit) {
|
||||
margin-left: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,100 @@
|
||||
import 'reflect-metadata';
|
||||
import {
|
||||
TableColumnPropertyDecorator as tcpd,
|
||||
TableFormColumnPropertyDecorator as tfcpd,
|
||||
TableColumnMethodDecorator as tcmd, TOperateType,
|
||||
TableSearchFormPropertyDecorator as tsfpd,
|
||||
UmTableForm
|
||||
} from "funjiaui";
|
||||
import * as api from "@/common/api"
|
||||
import { h } from "vue";
|
||||
import { getObjectTypeName } from "@/common/tool";
|
||||
|
||||
export default class CaptureObjectList {
|
||||
|
||||
static singleListen = null;
|
||||
|
||||
@tcpd({ "lang": "", "def": "设备名称", minWidth: 120 })
|
||||
deviceName: string = "";
|
||||
|
||||
@tcpd({
|
||||
"lang": "", "def": "置信度", minWidth: 120, columnRender2(value) {
|
||||
try {
|
||||
return (parseFloat(value).toFixed(2) * 100) + "%";
|
||||
} catch (error) {
|
||||
}
|
||||
return '--';
|
||||
}
|
||||
})
|
||||
confidence: number = 0;
|
||||
|
||||
@tcpd({
|
||||
"lang": "", "def": "目标类型", minWidth: 120, columnRender(value) {
|
||||
return getObjectTypeName(value);
|
||||
}
|
||||
})
|
||||
@tsfpd({
|
||||
lang: "", "def": "目标类型", fieldType: "select", async getData() {
|
||||
return {
|
||||
data: Array.from({ length: 7 }).map((item, index) => {
|
||||
return {
|
||||
label: getObjectTypeName(index),
|
||||
value: index
|
||||
}
|
||||
}),
|
||||
multiple: true
|
||||
}
|
||||
}
|
||||
})
|
||||
objectType: string = "";
|
||||
|
||||
@tcpd({
|
||||
"lang": "", "def": "抓拍照片", columnRender(value, f, rowData) {
|
||||
return h('img', {
|
||||
src: value,
|
||||
style: { width: '100px', height: '100px' },
|
||||
'onClick': (e) => {
|
||||
CaptureObjectList.singleListen?.(rowData)
|
||||
}
|
||||
})
|
||||
},
|
||||
width: 220
|
||||
})
|
||||
objectUrl: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "抓拍时间", minWidth: 120, "fieldType": "date" })
|
||||
time: string = "";
|
||||
|
||||
@tcmd({ "key": "table:searchFormMethod", "value": { "type": "getList", "es": "onAfter" } })
|
||||
getList = async (params: any) => {
|
||||
const key = "captureObjectList";
|
||||
let ipcListCache: string | null = sessionStorage.getItem(key);
|
||||
try {
|
||||
ipcListCache = ipcListCache ? JSON.parse(ipcListCache) : null;
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
if (params.page == 1 || !ipcListCache) {
|
||||
const { data } = await api.getCaputreImgListByIpcId(params);
|
||||
let listData: any = null;
|
||||
if (data?.objectList?.length > 0) {
|
||||
listData = {
|
||||
data: data?.objectList.sort((a, b) => {
|
||||
return new Date(b.time) - new Date(a.time);
|
||||
}), code: 0, total: data.total
|
||||
}
|
||||
}
|
||||
else {
|
||||
listData = { data: [], code: 0, total: 0 }
|
||||
}
|
||||
sessionStorage.setItem(key, JSON.stringify(listData));
|
||||
return { data: listData.data.slice(0, params.pageSize), code: 0, total: listData.total }
|
||||
}
|
||||
else {
|
||||
const startIndex = (params.page - 1) * params.pageSize,
|
||||
endIndex = startIndex + params.pageSize;
|
||||
|
||||
return { data: (ipcListCache?.data || []).slice(startIndex, endIndex), code: 0, total: ipcListCache?.total || 0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
import 'reflect-metadata';
|
||||
import {
|
||||
TableColumnPropertyDecorator as tcpd,
|
||||
TableFormColumnPropertyDecorator as tfcpd,
|
||||
TableColumnMethodDecorator as tcmd, TOperateType,
|
||||
TableSearchFormPropertyDecorator as tsfpd,
|
||||
UmTableForm
|
||||
} from "funjiaui";
|
||||
import * as api from "@/common/api"
|
||||
|
||||
|
||||
export default class Employee {
|
||||
@tfcpd({ "lang": "", "def": "id", fieldType: "primaryKey" })
|
||||
id: number = 0;
|
||||
|
||||
@tcpd({ "lang": "", "def": "姓名", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "姓名", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
name: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "用戶名", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "用戶名", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
userName: string = "";
|
||||
|
||||
@tfcpd({ "lang": "", "def": "密码", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
pwd: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "手机号", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "手机号", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
phone: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "车牌号码", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "车牌号码", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
vehiclePlateNumber: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "用户信息图片地址", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "用户信息图片地址", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
userInfoPic: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "通知总数", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "通知总数", fieldType: "number", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
noticeTotal: number = 0;
|
||||
|
||||
@tcpd({ "lang": "", "def": "备注", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "备注" })
|
||||
remark: string = "";
|
||||
|
||||
@tcmd({ "key": "table:searchFormMethod", "value": { "type": "getList", "es": "onAfter" } })
|
||||
getList = async (params: any) => {
|
||||
return await api.getEmployeeList(params);
|
||||
}
|
||||
|
||||
// @tcmd({
|
||||
// "key": "table:toolbar", "value": {
|
||||
// "type": "添加", "priority": 1, "es": "onAfter", "dialogContent": UmTableForm, "dialogTitle": "增加", "dialogWidth": 650
|
||||
// }
|
||||
// })
|
||||
// onAdd = async (data: any) => {
|
||||
// const { code, msg } = await api.addEmployee(data);
|
||||
// if (code != 0) {
|
||||
// return { code: 1, message: msg };
|
||||
// }
|
||||
// else {
|
||||
// return { code: 0 };
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 获取详情数据
|
||||
*/
|
||||
@tcmd({ "value": { "type": "edit", "es": "onBefore", "dialogContent": UmTableForm, "dialogTitle": "编辑" } })
|
||||
getDetail = async (rowId: number, r, meta) => {
|
||||
const { data } = await api.getEmployee({ id: rowId });
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@tcmd({
|
||||
"value": {
|
||||
"type": "edit", "es": "onAfter",
|
||||
sort: 2,
|
||||
// isShow
|
||||
}
|
||||
})
|
||||
onEdit = async (params: any) => {
|
||||
params.source_type = "IPC";
|
||||
params.forever = true;
|
||||
|
||||
const { code, msg } = await api.updateEmployee(params);
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// @tcmd({
|
||||
// "value": {
|
||||
// "type": "del", "es": "onAfter", "dialogContent": "确定要删除该条数据吗?",
|
||||
// sort: 3,
|
||||
// // isShow
|
||||
// }
|
||||
// })
|
||||
// delByIds = async (rowId: number) => {
|
||||
// const { code, msg } = await api.deleteEmployee({ ids: [rowId] });
|
||||
// if (code != 0) {
|
||||
// return { code: 1, message: msg };
|
||||
// }
|
||||
// else {
|
||||
// return { code: 0 };
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,216 @@
|
||||
|
||||
import 'reflect-metadata';
|
||||
import {
|
||||
TableColumnPropertyDecorator as tcpd,
|
||||
TableFormColumnPropertyDecorator as tfcpd,
|
||||
TableColumnMethodDecorator as tcmd, TOperateType,
|
||||
TableSearchFormPropertyDecorator as tsfpd,
|
||||
UmTableForm
|
||||
} from "funjiaui";
|
||||
import * as api from "@/common/api"
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { isEmpty } from 'funjialib';
|
||||
|
||||
const isShow = (rowData: any) => {
|
||||
if (rowData?.source_add_type == "1") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default class Ipc {
|
||||
// 侵入性事件注入
|
||||
static singleListen: any = null
|
||||
|
||||
static isEdit = false;
|
||||
|
||||
// 操作列宽度
|
||||
static operateWidth = "140px";
|
||||
|
||||
@tcpd({ "lang": "", "def": "设备ID", width: 200 })
|
||||
@tfcpd({
|
||||
"lang": "", "def": "ID", fieldType: 'number', rule: [{ "type": "require", message: "不能为空" }],
|
||||
isDisabled(rowData) {
|
||||
return rowData?.['$ref:birthday'];
|
||||
}
|
||||
})
|
||||
id: number = 0;
|
||||
|
||||
@tcpd({ "lang": "", "def": "名称", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "名称", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
name: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "视频流地址", minWidth: 120 })
|
||||
@tfcpd({
|
||||
"lang": "", "def": "流地址", fieldType: 'string', placeholder: "rtsp://admin:admin1234@192.168.1.108", rule: [{ "type": "require", message: "不能为空" }]
|
||||
})
|
||||
url: string = "";
|
||||
|
||||
@tfcpd({
|
||||
"lang": "", "def": "算法", fieldType: "select", getData() {
|
||||
return {
|
||||
data: [{ label: "人脸", value: "face" },
|
||||
{ label: "人车非", value: "person" }
|
||||
],
|
||||
multiple: true
|
||||
}
|
||||
}, rule: [{ "type": "require", message: "不能为空" }]
|
||||
})
|
||||
type: string[] = [];
|
||||
|
||||
@tfcpd({ "lang": "", "def": "", isShow: async () => false })
|
||||
source_type = "IPC";
|
||||
|
||||
@tfcpd({ "lang": "", "def": "", isShow: async () => false })
|
||||
forever = true;
|
||||
|
||||
@tfcpd({ "lang": "", "def": "", isShow: async () => false })
|
||||
birthday: any;
|
||||
|
||||
@tcpd({
|
||||
"lang": "", "def": "最后抓拍时间", columnRender(v, f, rowData) {
|
||||
return rowData?.time?.last_image;
|
||||
}, width: 220
|
||||
})
|
||||
last_image: string = "";
|
||||
|
||||
|
||||
// @tcpd({
|
||||
// "lang": "", "def": "最后抓拍照片", columnRender(value, f, rowData) {
|
||||
// return h('img', {
|
||||
// src: value,
|
||||
// style: { width: '100px', height: '100px' },
|
||||
// 'onClick': (e) => {
|
||||
// Ipc.singleListen?.(rowData)
|
||||
// }
|
||||
// })
|
||||
// },
|
||||
// width: 220
|
||||
// })
|
||||
// objectUrl: string = "";
|
||||
|
||||
@tcpd({
|
||||
"lang": "", "def": "状态", columnRender(v, f, rowData) {
|
||||
let statusinfo = "在线";
|
||||
if (rowData.status != "6") {
|
||||
statusinfo = rowData.status_info;
|
||||
}
|
||||
return statusinfo;
|
||||
}, width: 80
|
||||
})
|
||||
status: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "抓拍数", width: 80 })
|
||||
snaper_image: string = "";
|
||||
|
||||
@tcmd({ "key": "table:searchFormMethod", "value": { "type": "getList", "es": "onAfter" } })
|
||||
getList = async (params: any) => {
|
||||
let ipcListCache: string | null = sessionStorage.getItem("ipcList");
|
||||
try {
|
||||
ipcListCache = ipcListCache ? JSON.parse(ipcListCache) : null;
|
||||
} catch (error) {
|
||||
}
|
||||
|
||||
if (params.page == 1 || !ipcListCache) {
|
||||
const data = await api.getIpcList(params);
|
||||
let listData: any = null;
|
||||
if (data?.devices?.length > 0) {
|
||||
listData = {
|
||||
data: data?.devices.sort((a, b) => {
|
||||
return new Date(b.time) - new Date(a.time);
|
||||
}), code: 0, total: data.size
|
||||
}
|
||||
}
|
||||
else {
|
||||
listData = { data: [], code: 0, total: 0 }
|
||||
}
|
||||
sessionStorage.setItem("ipcList", JSON.stringify(listData));
|
||||
return { data: listData.data.slice(0, params.pageSize), code: 0, total: listData.total }
|
||||
}
|
||||
else {
|
||||
const startIndex = (params.page - 1) * params.pageSize,
|
||||
endIndex = startIndex + params.pageSize;
|
||||
|
||||
return { data: (ipcListCache?.data || []).slice(startIndex, endIndex), code: 0, total: ipcListCache?.total || 0 }
|
||||
}
|
||||
}
|
||||
|
||||
@tcmd({
|
||||
"key": "table:toolbar", "value": {
|
||||
"type": "添加", "priority": 1, "es": "onAfter", "dialogContent": UmTableForm, "dialogTitle": "增加视频分析相机", "dialogWidth": 650
|
||||
}
|
||||
})
|
||||
onAdd = async (data: any) => {
|
||||
const { status, reason } = await api.addIpc(data);
|
||||
if (status != 200) {
|
||||
return { code: 1, message: reason };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详情数据
|
||||
*/
|
||||
@tcmd({ "value": { "type": "edit", "es": "onBefore", "dialogContent": UmTableForm, "dialogTitle": "编辑视频分析相机" } })
|
||||
getDetail = async (rowId: number, r, meta, data) => {
|
||||
return data.find(item => rowId == item.id);
|
||||
}
|
||||
|
||||
|
||||
@tcmd({
|
||||
"value": {
|
||||
"type": "edit", "es": "onAfter",
|
||||
sort: 2,
|
||||
isShow
|
||||
}
|
||||
})
|
||||
onEdit = async (params: any) => {
|
||||
params.source_type = "IPC";
|
||||
params.forever = true;
|
||||
|
||||
const { status, reason } = await api.updateIpc(params);
|
||||
if (status != 200) {
|
||||
return { code: 1, message: reason };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
@tcmd({
|
||||
"value": {
|
||||
"type": "del", "es": "onAfter", "dialogContent": "确定要删除该条数据吗?",
|
||||
sort: 3,
|
||||
isShow
|
||||
}
|
||||
})
|
||||
delByIds = async (rowId: number) => {
|
||||
const { status, reason } = await api.deleteIpc({ id: rowId });
|
||||
if (status != 200) {
|
||||
return { code: 1, message: reason };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
@tcmd({
|
||||
"value": {
|
||||
"id": 1, "type": "customOperateEvent", "title": "详情",
|
||||
sort: 1,
|
||||
isShow: (rowData) => {
|
||||
if (rowData?.snaper_image > 0) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
jumpEmployee = (rowId: number) => {
|
||||
return rowId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
|
||||
import 'reflect-metadata';
|
||||
import {
|
||||
TableColumnPropertyDecorator as tcpd,
|
||||
TableFormColumnPropertyDecorator as tfcpd,
|
||||
TableColumnMethodDecorator as tcmd, TOperateType,
|
||||
TableSearchFormPropertyDecorator as tsfpd,
|
||||
UmTableForm
|
||||
} from "funjiaui";
|
||||
import * as api from "@/common/api"
|
||||
// import { ElMessage } from 'element-plus'
|
||||
import { h } from 'vue';
|
||||
|
||||
const isShow = (rowData: any) => {
|
||||
if (rowData?.source_add_type == "1") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default class Notice {
|
||||
// 侵入性事件注入
|
||||
static singleListen: any = null
|
||||
|
||||
static isEdit = false;
|
||||
|
||||
// 操作列宽度
|
||||
static operateWidth = "140px";
|
||||
|
||||
@tfcpd({ "lang": "", "def": "id", fieldType: "primaryKey" })
|
||||
id: number = 0;
|
||||
|
||||
@tcpd({ "lang": "", "def": "消息", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "消息", fieldType: "string", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
message: string = ""
|
||||
|
||||
@tcpd({ "lang": "", "def": "排序", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "排序", fieldType: "number", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
sort: number = 1;
|
||||
|
||||
static userId: any;
|
||||
|
||||
@tcmd({ "key": "table:searchFormMethod", "value": { "type": "getList", "es": "onAfter" } })
|
||||
getList = async (params: any) => {
|
||||
return await api.getNoticeList(params);
|
||||
}
|
||||
|
||||
@tcmd({
|
||||
"key": "table:toolbar", "value": {
|
||||
"type": "添加", "priority": 1, "es": "onAfter", "dialogContent": UmTableForm, "dialogTitle": "增加", "dialogWidth": 650
|
||||
}
|
||||
})
|
||||
onAdd = async (data: any) => {
|
||||
const { code, msg } = await api.addNotice({ ...data, userId: Notice.userId });
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详情数据
|
||||
*/
|
||||
@tcmd({ "value": { "type": "edit", "es": "onBefore", "dialogContent": UmTableForm, "dialogTitle": "编辑" } })
|
||||
getDetail = async (rowId: number, r, meta) => {
|
||||
const { data } = await api.getNotice({ id: rowId });
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@tcmd({
|
||||
"value": {
|
||||
"type": "edit", "es": "onAfter",
|
||||
sort: 2,
|
||||
// isShow
|
||||
}
|
||||
})
|
||||
onEdit = async (params: any) => {
|
||||
const { code, msg } = await api.updateNotice({ ...params, userId: Notice.userId });
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
@tcmd({
|
||||
"value": {
|
||||
"type": "del", "es": "onAfter", "dialogContent": "确定要删除该条数据吗?",
|
||||
sort: 3,
|
||||
// isShow
|
||||
}
|
||||
})
|
||||
delByIds = async (rowId: number) => {
|
||||
const { code, msg } = await api.deleteNotice({ ids: [rowId] });
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// @tcmd({
|
||||
// "value": {
|
||||
// "id": 1, "type": "customOperateEvent", "title": "详情",
|
||||
// sort: 1,
|
||||
// isShow: (rowData) => {
|
||||
// if (rowData?.snaper_image > 0) {
|
||||
// return true;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// jumpEmployee = (rowId: number) => {
|
||||
// return rowId;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,242 @@
|
||||
import 'reflect-metadata';
|
||||
// import {
|
||||
// TableColumnPropertyDecorator as tcpd,
|
||||
// TableFormColumnPropertyDecorator as tfcpd,
|
||||
// TableColumnMethodDecorator as tcmd, TOperateType,
|
||||
// TableSearchFormPropertyDecorator as tsfpd
|
||||
// } from "@/components/UmTable";
|
||||
import * as api from "@/common/api"
|
||||
// import UmTableForm from '@/components/UmTable/UmTableForm.vue';
|
||||
|
||||
import { UmTableForm,
|
||||
TableColumnPropertyDecorator as tcpd,
|
||||
TableFormColumnPropertyDecorator as tfcpd,
|
||||
TableColumnMethodDecorator as tcmd, TOperateType,
|
||||
TableSearchFormPropertyDecorator as tsfpd
|
||||
} from "funjiaui";
|
||||
// import {
|
||||
// TableColumnPropertyDecorator as tcpd,
|
||||
// TableFormColumnPropertyDecorator as tfcpd,
|
||||
// TableColumnMethodDecorator as tcmd, TOperateType,
|
||||
// TableSearchFormPropertyDecorator as tsfpd
|
||||
// } from "funjiaui/UmTable";
|
||||
|
||||
|
||||
import DynamicTags from '@/components/DynamicTags.vue';
|
||||
// import { ElButton, ElInput, ElTag,ElDialog } from 'element-plus';
|
||||
import { Codemirror } from 'vue-codemirror';
|
||||
|
||||
|
||||
export default class QA {
|
||||
@tcpd({ "lang": "", "def": "id", width: 240 })
|
||||
@tfcpd({ "lang": "", "def": "id", fieldType: "primaryKey" })
|
||||
id: number = 0;
|
||||
|
||||
@tsfpd({ lang: "", "def": "题目" })
|
||||
@tcpd({ "lang": "", "def": "题目", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "题目", rule: [{ "type": "require", message: "不能为空" }], component: Codemirror })
|
||||
question: string = "";
|
||||
|
||||
// @tcpd({ "lang": "", "def": "答案", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "答案", component: Codemirror, width: 300 })
|
||||
answer: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "标签", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "标签", component: DynamicTags, width: 300 })
|
||||
@tsfpd({
|
||||
lang: "", "def": "标签", fieldType: "select", async getData() {
|
||||
return {
|
||||
data: [{
|
||||
label: "vue3",
|
||||
value: "vue3"
|
||||
}, {
|
||||
label: "react",
|
||||
value: "react"
|
||||
}, {
|
||||
label: "js",
|
||||
value: "js"
|
||||
}, {
|
||||
label: "html",
|
||||
value: "html"
|
||||
}, {
|
||||
label: "css",
|
||||
value: "css"
|
||||
}, {
|
||||
label: "webpack",
|
||||
value: "webpack"
|
||||
}, {
|
||||
label: "微信小程序",
|
||||
value: "微信小程序"
|
||||
}, {
|
||||
label: "typescript",
|
||||
value: "typescript"
|
||||
}, {
|
||||
label: "boss_vue",
|
||||
value: "boss_vue"
|
||||
}, {
|
||||
label: "高频_css",
|
||||
value: "高频_css"
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
tag: string[] = [];
|
||||
|
||||
@tcmd({ "key": "table:searchFormMethod", "value": { "type": "getList", "es": "onAfter" } })
|
||||
getList = async (params: any) => {
|
||||
return await api.getQAList(params);
|
||||
}
|
||||
|
||||
@tcmd({
|
||||
"key": "table:toolbar", "value": {
|
||||
"type": "添加", "priority": 1, "es": "onAfter", "dialogContent": UmTableForm, "dialogTitle": "增加", "dialogWidth": 1200
|
||||
}
|
||||
})
|
||||
onAdd = async (data: any) => {
|
||||
const { code, msg } = await api.addQA({ ...data });
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
exportText(filename, text) {
|
||||
var element = document.createElement('a');
|
||||
element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
|
||||
element.setAttribute('download', filename);
|
||||
|
||||
element.style.display = 'none';
|
||||
document.body.appendChild(element);
|
||||
|
||||
element.click();
|
||||
|
||||
document.body.removeChild(element);
|
||||
}
|
||||
|
||||
@tcmd({
|
||||
"key": "table:toolbar", "value": {
|
||||
"type": "自定义", "priority": 1, "es": "onAfter", title: "导出anki脚本"
|
||||
}
|
||||
})
|
||||
onExport = async (params: any) => {
|
||||
const { data } = await api.getQAList({ ...params, page: 1, pageSize: 10000 });
|
||||
const head = `#separator:tab
|
||||
#html:true
|
||||
#tags column:3
|
||||
`
|
||||
const style = `<style>iframe{width:100%;height:90vh;}</style>`;
|
||||
|
||||
const content = data.map(item => {
|
||||
return `"${style}<iframe src=""http://192.168.6.130:5176/#/question?id=${item.id}"" frameborder=""0""></iframe><br>" "${style}<iframe src=""http://192.168.6.130:5176/#/answer?id=${item.id}"" frameborder=""0""></iframe><br>"`;
|
||||
}).join("\n")
|
||||
|
||||
this.exportText("anki脚本.txt", head + content);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详情数据
|
||||
*/
|
||||
@tcmd({ "value": { "type": "edit", "es": "onBefore", "dialogContent": UmTableForm, "dialogTitle": "编辑", "dialogWidth": 1200 } })
|
||||
getDetail = async (rowId: number, r, meta) => {
|
||||
const { data } = await api.getQA({ id: rowId });
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@tcmd({
|
||||
"value": {
|
||||
"type": "edit", "es": "onAfter",
|
||||
sort: 2,
|
||||
// isShow
|
||||
}
|
||||
})
|
||||
onEdit = async (params: any) => {
|
||||
const { code, msg } = await api.updateQA({ ...params });
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
@tcmd({
|
||||
"value": {
|
||||
"type": "del", "es": "onAfter", "dialogContent": "确定要删除该条数据吗?",
|
||||
sort: 3,
|
||||
dialogWidth: 400
|
||||
// isShow
|
||||
}
|
||||
})
|
||||
delByIds = async (rowId: number) => {
|
||||
const { code, msg } = await api.deleteQA({ ids: [rowId] });
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// @tcmd({
|
||||
// "key": "table:toolbar", "value": {
|
||||
// "type": "添加", "priority": 1, "es": "onAfter", "dialogContent": UmTableForm, "dialogTitle": "增加", "dialogWidth": 650
|
||||
// }
|
||||
// })
|
||||
// onAdd = async (data: any) => {
|
||||
// const { code, msg } = await api.addQA(data);
|
||||
// if (code != 0) {
|
||||
// return { code: 1, message: msg };
|
||||
// }
|
||||
// else {
|
||||
// return { code: 0 };
|
||||
// }
|
||||
// }
|
||||
|
||||
/**
|
||||
* 获取详情数据
|
||||
*/
|
||||
@tcmd({ "value": { "type": "edit", "es": "onBefore", "dialogContent": UmTableForm, "dialogTitle": "编辑", dialogWidth: 1200 } })
|
||||
getDetail = async (rowId: number, r, meta) => {
|
||||
const { data } = await api.getQA({ id: rowId });
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@tcmd({
|
||||
"value": {
|
||||
"type": "edit", "es": "onAfter",
|
||||
sort: 2,
|
||||
// isShow
|
||||
}
|
||||
})
|
||||
onEdit = async (params: any) => {
|
||||
const { code, msg } = await api.updateQA(params);
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// @tcmd({
|
||||
// "value": {
|
||||
// "type": "del", "es": "onAfter", "dialogContent": "确定要删除该条数据吗?",
|
||||
// sort: 3,
|
||||
// // isShow
|
||||
// }
|
||||
// })
|
||||
// delByIds = async (rowId: number) => {
|
||||
// const { code, msg } = await api.deleteQA({ ids: [rowId] });
|
||||
// if (code != 0) {
|
||||
// return { code: 1, message: msg };
|
||||
// }
|
||||
// else {
|
||||
// return { code: 0 };
|
||||
// }
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,214 @@
|
||||
|
||||
import 'reflect-metadata';
|
||||
import {
|
||||
TableColumnPropertyDecorator as tcpd,
|
||||
TableFormColumnPropertyDecorator as tfcpd,
|
||||
TableColumnMethodDecorator as tcmd, TOperateType,
|
||||
TableSearchFormPropertyDecorator as tsfpd,
|
||||
UmTableForm
|
||||
} from "funjiaui";
|
||||
import * as api from "@/common/api"
|
||||
// import UmTableForm from '@/components/UmTable/UmTableForm.vue';
|
||||
import { ElMessage } from 'element-plus'
|
||||
// import { isEmpty } from 'marsLib/is';
|
||||
import { fDT } from 'funjialib';
|
||||
import { h } from 'vue';
|
||||
|
||||
const isShow = (rowData: any) => {
|
||||
if (rowData?.source_add_type == "1") {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default class TravelInformation {
|
||||
// 侵入性事件注入
|
||||
static singleListen: any = null
|
||||
|
||||
static isEdit = false;
|
||||
|
||||
// 操作列宽度
|
||||
static operateWidth = "140px";
|
||||
|
||||
@tfcpd({ "lang": "", "def": "id", fieldType: "primaryKey" })
|
||||
id: number = 0;
|
||||
|
||||
@tcpd({ "lang": "", "def": "用车时间", minWidth: 120 })
|
||||
@tfcpd({ "lang": "", "def": "用车时间", fieldType: "date", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
useDateTime: string = fDT(new Date(), "YYYY-MM-DD HH:mm:ss");
|
||||
|
||||
@tcpd({ "lang": "", "def": "联系手机", minWidth: 120 })
|
||||
@tfcpd({
|
||||
"lang": "", "def": "联系手机", fieldType: 'string', rule: [{ "type": "require", message: "不能为空" }]
|
||||
})
|
||||
contactPhone: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "起点", minWidth: 120 })
|
||||
@tfcpd({
|
||||
"lang": "", "def": "起点", fieldType: 'string', rule: [{ "type": "require", message: "不能为空" }]
|
||||
})
|
||||
beginLocation: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "终点", minWidth: 120 })
|
||||
@tfcpd({
|
||||
"lang": "", "def": "终点", fieldType: 'string', rule: [{ "type": "require", message: "不能为空" }]
|
||||
})
|
||||
endLocation: string = "";
|
||||
|
||||
@tcpd({ "lang": "", "def": "人数", minWidth: 120 })
|
||||
@tfcpd({
|
||||
"lang": "", "def": "人数", fieldType: 'number', rule: [{ "type": "require", message: "不能为空" }],
|
||||
cascadeFiled: ['sum']
|
||||
})
|
||||
total: number = 1;
|
||||
|
||||
@tcpd({ "lang": "", "def": "单价", minWidth: 120 })
|
||||
@tfcpd({
|
||||
"lang": "", "def": "单价", fieldType: 'number', rule: [{ "type": "require", message: "不能为空" }],
|
||||
cascadeFiled: ['sum']
|
||||
})
|
||||
price: number = 100;
|
||||
|
||||
@tcpd({ "lang": "", "def": "总价", minWidth: 120 })
|
||||
@tfcpd({
|
||||
"lang": "", "def": "总价", fieldType: "string",
|
||||
disabled: true,
|
||||
getData(formModel) {
|
||||
return Number(formModel.total) * Number(formModel.price);
|
||||
},
|
||||
isCascade: true
|
||||
})
|
||||
sum: number = 0;
|
||||
|
||||
@tcpd({
|
||||
"lang": "", "def": "状态", minWidth: 120, columnRender(value) {
|
||||
let content = "";
|
||||
switch (value) {
|
||||
case 0:
|
||||
content = "待完成";
|
||||
break;
|
||||
case 1:
|
||||
content = "已完成";
|
||||
break;
|
||||
case 2:
|
||||
content = "已取消";
|
||||
break;
|
||||
}
|
||||
return h("div", content);
|
||||
}
|
||||
})
|
||||
@tfcpd({
|
||||
"lang": "", "def": "状态", fieldType: "select",
|
||||
disabled: true,
|
||||
getData() {
|
||||
return {
|
||||
data: [
|
||||
{ label: "待完成", value: 0 },
|
||||
{ label: "已完成", value: 1 },
|
||||
{ label: "已取消", value: 2 }
|
||||
]
|
||||
}
|
||||
}
|
||||
})
|
||||
@tsfpd({
|
||||
lang: "", "def": "订单状态", fieldType: "select", async getData() {
|
||||
return {
|
||||
data: [{
|
||||
label: "待完成",
|
||||
value: 0
|
||||
}, {
|
||||
label: "已完成",
|
||||
value: 1
|
||||
}, {
|
||||
label: "已取消",
|
||||
value: 2
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
status: TStatus = 0;
|
||||
|
||||
static userId: any;
|
||||
|
||||
@tcmd({ "key": "table:searchFormMethod", "value": { "type": "getList", "es": "onAfter" } })
|
||||
getList = async (params: any) => {
|
||||
return await api.getTravelInformationList(params);
|
||||
}
|
||||
|
||||
@tcmd({
|
||||
"key": "table:toolbar", "value": {
|
||||
"type": "添加", "priority": 1, "es": "onAfter", "dialogContent": UmTableForm, "dialogTitle": "增加", "dialogWidth": 650
|
||||
}
|
||||
})
|
||||
onAdd = async (data: any) => {
|
||||
const { code, msg } = await api.addTravelInformation({ ...data, userId: TravelInformation.userId });
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取详情数据
|
||||
*/
|
||||
@tcmd({ "value": { "type": "edit", "es": "onBefore", "dialogContent": UmTableForm, "dialogTitle": "编辑" } })
|
||||
getDetail = async (rowId: number, r, meta) => {
|
||||
const { data } = await api.getTravelInformation({ id: rowId });
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
@tcmd({
|
||||
"value": {
|
||||
"type": "edit", "es": "onAfter",
|
||||
sort: 2,
|
||||
// isShow
|
||||
}
|
||||
})
|
||||
onEdit = async (params: any) => {
|
||||
const { code, msg } = await api.updateTravelInformation({ ...params, userId: TravelInformation.userId });
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// @tcmd({
|
||||
// "value": {
|
||||
// "type": "del", "es": "onAfter", "dialogContent": "确定要删除该条数据吗?",
|
||||
// sort: 3,
|
||||
// // isShow
|
||||
// }
|
||||
// })
|
||||
delByIds = async (rowId: number) => {
|
||||
const { code, msg } = await api.deleteTravelInformation({ ids: [rowId] });
|
||||
if (code != 0) {
|
||||
return { code: 1, message: msg };
|
||||
}
|
||||
else {
|
||||
return { code: 0 };
|
||||
}
|
||||
}
|
||||
|
||||
// @tcmd({
|
||||
// "value": {
|
||||
// "id": 1, "type": "customOperateEvent", "title": "详情",
|
||||
// sort: 1,
|
||||
// isShow: (rowData) => {
|
||||
// if (rowData?.snaper_image > 0) {
|
||||
// return true;
|
||||
// } else {
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
// jumpEmployee = (rowId: number) => {
|
||||
// return rowId;
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
|
||||
import 'reflect-metadata';
|
||||
import {
|
||||
TableColumnPropertyDecorator as tcpd,
|
||||
TableFormColumnPropertyDecorator as tfcpd,
|
||||
TableColumnMethodDecorator as tcmd, TOperateType,
|
||||
TableSearchFormPropertyDecorator as tsfpd,
|
||||
UmTableForm
|
||||
} from "funjiaui";
|
||||
import { h } from 'vue';
|
||||
|
||||
export default class UpServer {
|
||||
@tfcpd({
|
||||
"lang": "", "def": "平台注册", "fieldType": "select",
|
||||
getData: () => {
|
||||
return {
|
||||
data: [{ label: "不启用", value: 0 },
|
||||
{ label: "", value: 1 }
|
||||
]
|
||||
}
|
||||
},
|
||||
rule: [{ "type": "require", message: "不能为空" }]
|
||||
})
|
||||
type: number = 0;
|
||||
|
||||
@tfcpd({ "lang": "", "def": "注册id", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
id: string = "";
|
||||
|
||||
@tfcpd({ "lang": "", "def": "服务器地址", rule: [{ "type": "require", message: "不能为空" }, { "type": "ip" }] })
|
||||
ip: string = "";
|
||||
|
||||
@tfcpd({ "lang": "", "def": "服务器端口", rule: [{ "type": "require", message: "不能为空" }, { "type": "port" }] })
|
||||
port: string = "";
|
||||
|
||||
@tfcpd({
|
||||
"lang": "", "def": "服务状态", component: (props) => {
|
||||
return props?.modelValue;
|
||||
}
|
||||
})
|
||||
manage_status: string = "Success";
|
||||
|
||||
@tfcpd({ "lang": "", "def": "Kafka服务地址", rule: [{ "type": "require", message: "不能为空" }, { "type": "ip" }] })
|
||||
kafka_ip: string = "";
|
||||
|
||||
@tfcpd({ "lang": "", "def": "Kafka端口", rule: [{ "type": "require", message: "不能为空" }, { "type": "port" }] })
|
||||
kafka_port: string = "";
|
||||
|
||||
@tfcpd({
|
||||
"lang": "", "def": "Kafka状态", component: (props) => {
|
||||
return props?.modelValue;
|
||||
}
|
||||
})
|
||||
kafka_status: string = "Success";
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
|
||||
import 'reflect-metadata';
|
||||
import {
|
||||
TableColumnPropertyDecorator as tcpd,
|
||||
TableFormColumnPropertyDecorator as tfcpd,
|
||||
TableColumnMethodDecorator as tcmd, TOperateType,
|
||||
TableSearchFormPropertyDecorator as tsfpd,
|
||||
UmTableForm
|
||||
} from "funjiaui";
|
||||
import * as api from "@/common/api"
|
||||
// import UmTableForm from '@/components/UmTable/UmTableForm.vue';
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
export class UpdatePassword {
|
||||
@tfcpd({ "lang": "", "def": "编号", "fieldType": "primaryKey" })
|
||||
id: number | string = 0;
|
||||
|
||||
// @tcpd({ lang: "name", def: "账号名称" })
|
||||
// @tfcpd({
|
||||
// "lang": "", "def": "账号名称", "fieldType": "default", rule: [{ "type": "require", message: "不能为空" }],
|
||||
// disabled: true
|
||||
// })
|
||||
// userName: string = "";
|
||||
|
||||
@tfcpd({ "lang": "", "def": "原密码", "fieldType": "password", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
oldPassword: string = "";
|
||||
|
||||
@tfcpd({ "lang": "", "def": "新密码", "fieldType": "password", rule: [{ "type": "require", message: "不能为空" }] })
|
||||
password: string = "";
|
||||
|
||||
@tfcpd({
|
||||
"lang": "", "def": "确认密码", "fieldType": "password", rule: [{
|
||||
"type": "require", message: "不能为空",
|
||||
}, {
|
||||
"type": "validator",
|
||||
validator: function (rule: any, value: any, callback: any) {
|
||||
const that: any = this;
|
||||
if (value != that.formData["password"]) {
|
||||
return new Error("两次输入的密码不对");
|
||||
}
|
||||
else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
})
|
||||
passwordConfirm: string = "";
|
||||
}
|
||||
Vendored
+10
@@ -0,0 +1,10 @@
|
||||
/// <reference types="vite/client" />
|
||||
|
||||
declare module '*.vue' {
|
||||
import type { DefineComponent } from 'vue'
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
|
||||
const component: DefineComponent<{}, {}, any>
|
||||
export default component
|
||||
}
|
||||
|
||||
declare module 'vue-cropperjs';
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue'
|
||||
import DeviceList from '@/views/DeviceList.vue'
|
||||
|
||||
createApp(DeviceList).mount('#app')
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import { createApp } from 'vue'
|
||||
import App from './App.vue'
|
||||
import router from "@/router";
|
||||
import { createPinia } from "pinia";
|
||||
import "./assets/icon/iconfont.css";
|
||||
|
||||
const app = createApp(App);
|
||||
app.use(router);
|
||||
app.use(createPinia());
|
||||
app.mount('#app')
|
||||
@@ -0,0 +1,56 @@
|
||||
import { createRouter, createWebHashHistory, RouteRecordRaw } from "vue-router";
|
||||
// import { ManageRouterFuncIds } from "@/utils/constant";
|
||||
// import SystemInfo from "@/views/SystemInfo.vue";
|
||||
// import SystemLog from "@/views/SystemLog.vue";
|
||||
// import UpServer from "@/views/UpServer.vue";
|
||||
// import Sett from "@/views/Sett.vue";
|
||||
// import DeviceList from "@/views/DeviceList.vue";
|
||||
// import CaptureObjectList from "@/views/CaptureObjectList.vue";
|
||||
import TravelInformation from "@/views/TravelInformation.vue";
|
||||
import Employee from "@/views/Employee.vue";
|
||||
import Notice from "@/views/Notice.vue";
|
||||
import QA from "@/views/QA.vue";
|
||||
|
||||
const routes: Array<RouteRecordRaw | any> = [
|
||||
{
|
||||
path: "/",
|
||||
component: () => import("@/views/Login.vue"),
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
component: () => import("@/views/Login.vue"),
|
||||
},
|
||||
{
|
||||
path: "/app",
|
||||
component: () => import("@/components/Layout.vue"),
|
||||
children: [
|
||||
{
|
||||
path: "/app/TravelInformation",
|
||||
component: TravelInformation,
|
||||
},
|
||||
{
|
||||
path: "/app/Employee",
|
||||
component: Employee,
|
||||
},
|
||||
{
|
||||
path: "/app/Notice",
|
||||
component: Notice,
|
||||
},
|
||||
{
|
||||
path: "/app/QA",
|
||||
component: QA,
|
||||
}
|
||||
]
|
||||
},
|
||||
// {
|
||||
// path: "/:pathMatch(.*)*",
|
||||
// component: () => import("@/views/NotFound/index.vue"),
|
||||
// },
|
||||
];
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
});
|
||||
|
||||
export default router;
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue'
|
||||
import Sett from '@/views/Sett.vue'
|
||||
|
||||
createApp(Sett).mount('#app')
|
||||
@@ -0,0 +1,24 @@
|
||||
import { defineStore } from "pinia";
|
||||
|
||||
interface AppType {
|
||||
userInfo: any;
|
||||
isMiniMenu: boolean;
|
||||
}
|
||||
|
||||
export const useAppStore = defineStore("app", {
|
||||
state: (): AppType => ({
|
||||
userInfo: {},
|
||||
isMiniMenu: false
|
||||
}),
|
||||
actions: {
|
||||
/**
|
||||
* 保存登录者信息
|
||||
*/
|
||||
saveUserInfo(payload: any) {
|
||||
this.userInfo = payload;
|
||||
},
|
||||
removeUserInfo() {
|
||||
this.userInfo = null;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue'
|
||||
import SystemInfo from '@/views/SystemInfo.vue'
|
||||
|
||||
createApp(SystemInfo).mount('#app')
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue'
|
||||
import SystemLog from '@/views/SystemLog.vue'
|
||||
|
||||
createApp(SystemLog).mount('#app')
|
||||
+50
@@ -0,0 +1,50 @@
|
||||
function countSquaresInContainer(x, y, objList) {
|
||||
const containerEffectiveArea = x * y;
|
||||
let currentArea = { count: 0 };
|
||||
const innerObj = [];
|
||||
let count = 0;
|
||||
while (true) {
|
||||
count = 0;
|
||||
objList.forEach(obj => {
|
||||
if (calcSquare(obj, containerEffectiveArea, currentArea)) {
|
||||
innerObj.push(obj);
|
||||
count++;
|
||||
}
|
||||
});
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return innerObj
|
||||
}
|
||||
|
||||
const calcSquare = (obj, containerEffectiveArea, currentArea) => {
|
||||
const totalArea = obj.width * obj.height + currentArea.count;
|
||||
if (totalArea < containerEffectiveArea) {
|
||||
currentArea.count = totalArea;
|
||||
// console.log(totalArea);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const containerWidth = 793.7,
|
||||
containerHeight = 1122.5,
|
||||
|
||||
square1Width = 100,
|
||||
square1Height = 100,
|
||||
|
||||
square2Width = 200,
|
||||
square2Height = 100,
|
||||
|
||||
square3Width = 160,
|
||||
square3Height = 40;
|
||||
|
||||
const value = countSquaresInContainer(containerWidth, containerHeight, [{ width: square1Width, height: square1Height, type: 1 }, {
|
||||
width: square3Width, height: square3Width, type: 3
|
||||
},{
|
||||
width: square2Width, height: square2Width, type: 2
|
||||
}]);
|
||||
|
||||
console.log(value.sort((a, b) => a.type -b.type ));
|
||||
@@ -0,0 +1,15 @@
|
||||
/** 图片预览图片索引切换类型值 */
|
||||
declare type TImgPreviewIdx = -1 | 1;
|
||||
|
||||
/**
|
||||
* 图片类型
|
||||
* 图片类型, 0:人脸, 1:自行车, 2:巴士, 3:小汽车, 4:电动车|摩托车, 5:人体, 6:三轮车,7货车、8车牌、9推车,10鸟、11猫,12 狗,13老鼠
|
||||
* "0 face","1 bicycle","2 bus","3 car","4 motorbike","5 person","6 tricycle","7 truck","8 carplate","9 pram","10 bird","11 cat", "12 dog", "13 mouse"
|
||||
*/
|
||||
declare type TObjectType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13;
|
||||
|
||||
/**
|
||||
* 订单状态
|
||||
* 0:待完成 1:已完成 2:已取消
|
||||
*/
|
||||
declare type TStatus = 0 | 1 | 2;
|
||||
@@ -0,0 +1,4 @@
|
||||
import { createApp } from 'vue'
|
||||
import UpServer from '@/views/UpServer.vue'
|
||||
|
||||
createApp(UpServer).mount('#app')
|
||||
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div :class="$style['device-list']">
|
||||
<h2>抓拍列表</h2>
|
||||
<capture-list></capture-list>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import CaptureList from '@/containers/CaptureList.vue';
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.device-list) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
:local(.img-preview-dialog) {
|
||||
:local(.footer) {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
>div {
|
||||
margin-left: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<div :class="$style['device-list']">
|
||||
<h2>摄像机列表</h2>
|
||||
<um-table-class-enhance class="role-right" :target="Ipc" :data="[]" @data-change="onDataChange" @fire="onFire">
|
||||
</um-table-class-enhance>
|
||||
<um-dialog v-model="visible" top="60px" :custom-class="$style['img-preview-dialog']" title="" width="1700px"
|
||||
:footer="false">
|
||||
<div :style="{ height: '660px' }">
|
||||
<capture-list :device-ids="[currentDeviceId]" :imgListContainerHeight="580"></capture-list>
|
||||
</div>
|
||||
</um-dialog>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// import UmTableClassEnhance from "@/components/UmTable/UmByClassEnhance.vue";
|
||||
// import UmDialog from "@/components/UmDialog/index.vue";
|
||||
import { UmByClassEnhance as UmTableClassEnhance,UmDialog } from "funjiaui";
|
||||
import Ipc from "@/containers/viewModel/Ipc";
|
||||
import ImgPreview from "@/components/ImgPreview.vue";
|
||||
import { ref, reactive, watch } from "vue";
|
||||
import * as api from "@/common/api";
|
||||
// import { fDT } from "marsLib/date";
|
||||
import CaptureList from "@/containers/CaptureList.vue";
|
||||
|
||||
const visible = ref(false);
|
||||
const imgIndex = ref(0);
|
||||
const ipcImgList = ref([]);
|
||||
const imgPreviewInfo = reactive({
|
||||
id: "",
|
||||
imgUrl: "",
|
||||
position: "",
|
||||
time: "",
|
||||
deviceName: ""
|
||||
});
|
||||
const currentDeviceId = ref(null);
|
||||
|
||||
Ipc.singleListen = (rowData) => {
|
||||
initImgPreviewInfo(rowData);
|
||||
imgIndex.value = (ipcImgList.value || []).findIndex(item => rowData.id == item.id) || 0;
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
const initImgPreviewInfo = (data) => {
|
||||
imgPreviewInfo.id = data?.["id"];
|
||||
imgPreviewInfo.imgUrl = data?.["imgUrl"];
|
||||
imgPreviewInfo.position = data?.["position"];
|
||||
imgPreviewInfo.time = data?.["time"];
|
||||
imgPreviewInfo.deviceName = data?.["deviceName"];
|
||||
}
|
||||
|
||||
watch(imgIndex, (i) => {
|
||||
initImgPreviewInfo(ipcImgList.value?.[i]);
|
||||
})
|
||||
|
||||
watch(visible, () => {
|
||||
if (!visible) {
|
||||
imgIndex.value = 0;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const onDataChange = (data) => {
|
||||
// ipcImgList.value = data || [];
|
||||
}
|
||||
|
||||
const onImgIdxChange = (idx: TImgPreviewIdx) => {
|
||||
let tempIdx = imgIndex.value;
|
||||
tempIdx += idx;
|
||||
|
||||
if (tempIdx >= 0 && tempIdx < ipcImgList.value?.length) {
|
||||
imgIndex.value = tempIdx;
|
||||
}
|
||||
}
|
||||
|
||||
const onFire = async (key: string, value, data: any) => {
|
||||
// imgIndex.value = 0;
|
||||
// const resData = await api.getCaputreImgListByIpcId({ id: data })
|
||||
// ipcImgList.value = resData.data?.objectList || [];
|
||||
// initImgPreviewInfo(ipcImgList.value?.[0]);
|
||||
currentDeviceId.value = value;
|
||||
visible.value = true;
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.device-list) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
:local(.img-preview-dialog) {
|
||||
:local(.footer) {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
>div {
|
||||
margin-left: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,353 @@
|
||||
<template>
|
||||
<div :class="$style['device-list']">
|
||||
<h2>用戶列表</h2>
|
||||
<!-- <um-table-class-enhance class="role-right" :target="Employee" :data="[]" @data-change="onDataChange"
|
||||
@fire="onFire">
|
||||
</um-table-class-enhance>
|
||||
<um-table-class-enhance2 class="role-right" :target="Employee" :data="[]" @data-change="onDataChange"
|
||||
@fire="onFire">
|
||||
</um-table-class-enhance2>
|
||||
<um-dialog v-model="visible" top="60px" :custom-class="$style['img-preview-dialog']" title="" width="1700px"
|
||||
:footer="false">
|
||||
<div :style="{ height: '660px' }">
|
||||
<capture-list :device-ids="[currentDeviceId]" :imgListContainerHeight="580"></capture-list>
|
||||
</div>
|
||||
</um-dialog> -->
|
||||
<div :class="$style['params-field']"><label>宽度:</label><div><el-input v-model="widthHeigth.width"/></div></div>
|
||||
<div :class="$style['params-field']"><label>高度:</label><div><el-input v-model="widthHeigth.height"/></div></div>
|
||||
<div :class="$style['params-field']"><label>内边距:</label><div><el-input v-model="widthHeigth.padding"/></div></div>
|
||||
<div :class="$style['params-field']"><label>字号:</label><div><el-input v-model="widthHeigth.fontSize"/></div></div>
|
||||
<div :class="$style['params-field']"><label>字色:</label><div><el-input v-model="widthHeigth.fontColor"/></div></div>
|
||||
<div :class="$style['params-field']"><label>itemMargin:</label><div><el-input v-model="itemMargin"/></div></div>
|
||||
<div :class="$style['params-field']"><label>gridWidth:</label><div><el-input v-model="gridWidth"/></div></div>
|
||||
<div :class="$style['params-field']"><el-button @click="onFreeLayout">自由排版</el-button></div>
|
||||
<div :class="$style['params-field']"><el-button @click="print">打印</el-button></div>
|
||||
<DndProvider :backend="HTML5Backend">
|
||||
<Box2 id="111" :index="1" v-bind="box1Data"/>
|
||||
<Box3 id="112" :index="2" v-bind="box2Data"/>
|
||||
<Box4 id="112" :index="2" v-bind="box3Data"/>
|
||||
<div ref="a4ContainerRef" :class="$style['a4']" id="a4">
|
||||
<Container ref="a4Ref" :item-margin="itemMargin" :grid-width="gridWidth"></Container>
|
||||
</div>
|
||||
</DndProvider>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// import { UmByClassEnhance as UmTableClassEnhance2 } from "funjiaui/um_table.es.js";
|
||||
import { Printd } from 'printd'
|
||||
import html2pdf from "html2pdf.js";
|
||||
// import { UmByClassEnhance as UmTableClassEnhance2 } from "../../lib/funjiaui.es.js";
|
||||
// import UmTableClassEnhance from "@/components/UmTable/UmByClassEnhance.vue";
|
||||
// import "funjiaui/style.css";
|
||||
import UmDialog from "funjiaui";
|
||||
import Employee from "@/containers/viewModel/Employee";
|
||||
|
||||
import { ref, reactive, watch,onMounted,onUnmounted, computed } from "vue";
|
||||
import * as api from "@/common/api";
|
||||
// import { fDT } from "marsLib/date";
|
||||
import CaptureList from "@/containers/CaptureList.vue";
|
||||
import { DndProvider } from 'vue3-dnd'
|
||||
import { HTML5Backend } from 'react-dnd-html5-backend'
|
||||
import Container from "@/views/Employee/A4.vue";
|
||||
import Box2 from "./Employee/Box2.vue";
|
||||
import { Box } from "./Employee/boxType.js";
|
||||
import Box3 from "./Employee/Box3.vue";
|
||||
import Box4 from "./Employee/Box4.vue";
|
||||
|
||||
|
||||
const containerWidth=793.7;
|
||||
const containerHeight=ref(1100);
|
||||
|
||||
const visible = ref(false);
|
||||
const imgIndex = ref(0);
|
||||
const ipcImgList = ref([]);
|
||||
const imgPreviewInfo = reactive({
|
||||
id: "",
|
||||
imgUrl: "",
|
||||
position: "",
|
||||
time: "",
|
||||
deviceName: ""
|
||||
});
|
||||
const currentTemplateType = ref("Box1");
|
||||
const widthHeigth=reactive({
|
||||
width:74,
|
||||
height:46,
|
||||
padding:0,
|
||||
fontSize:12,
|
||||
fontColor:"black"
|
||||
});
|
||||
|
||||
|
||||
const templateOption:ITemplate[]=reactive([
|
||||
{
|
||||
type:"Box1",
|
||||
width:74,
|
||||
height:66,
|
||||
padding:0,
|
||||
fontSize:12,
|
||||
fontColor:"red"
|
||||
}
|
||||
// ,{
|
||||
// type:"Box2",
|
||||
// width:48,
|
||||
// height:18,
|
||||
// padding:1,
|
||||
// fontSize:12,
|
||||
// fontColor:"black"
|
||||
// },{
|
||||
// type:"Box3",
|
||||
// width:14,
|
||||
// height:62,
|
||||
// padding:1,
|
||||
// fontSize:12,
|
||||
// fontColor:"black"
|
||||
// }
|
||||
]);
|
||||
|
||||
const a4ContainerRef =ref();
|
||||
const a4Ref =ref();
|
||||
const observerRef=ref();
|
||||
const itemMargin=ref(1);
|
||||
const gridWidth=ref(1);
|
||||
|
||||
// Ipc.singleListen = (rowData) => {
|
||||
// initImgPreviewInfo(rowData);
|
||||
// imgIndex.value = (ipcImgList.value || []).findIndex(item => rowData.id == item.id) || 0;
|
||||
// visible.value = true;
|
||||
// }
|
||||
|
||||
watch([widthHeigth,currentTemplateType,templateOption],([_widthHeigth,_currentTemplateType,_templateOption])=>{
|
||||
const data=_templateOption.find(item=>item.type==_currentTemplateType);
|
||||
if(data)
|
||||
{
|
||||
data.width=_widthHeigth.width;
|
||||
data.height=_widthHeigth.height;
|
||||
data.padding=_widthHeigth.padding;
|
||||
data.fontSize=_widthHeigth.fontSize;
|
||||
data.fontColor=_widthHeigth.fontColor;
|
||||
}
|
||||
})
|
||||
|
||||
onMounted(()=>{
|
||||
const targetNode:any=a4ContainerRef.value;
|
||||
|
||||
const config={attribute:true,childList:true,subtree:true,attributeFilter:['class','style'],attributeOldValue:true,characterDataOldValue:true};
|
||||
|
||||
const callback=function(mutationsList,observer){
|
||||
console.log(mutationsList);
|
||||
for(let mutation of mutationsList){
|
||||
if(mutation.type=="attributes"&&mutation.target.className=="column"){
|
||||
const column=mutation.target;
|
||||
// if()
|
||||
console.log(column.style.transform);
|
||||
const matrix3dRegex = /translate3d\((.+)\)/,
|
||||
match=column.style.transform.match(matrix3dRegex);
|
||||
if(match&&column.children.length>0){
|
||||
const values=match[1].split(',');
|
||||
const y=parseFloat(values[1]);
|
||||
// 元素偏移高度加自身高度超过容器本身高度时需移除当前元素
|
||||
if( y+column.children[0].offsetHeight>containerHeight.value){
|
||||
// observerRef.value.disconnect();
|
||||
a4Ref.value.removeLastNode();
|
||||
console.log('remove');
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
observerRef.value=new MutationObserver(callback);
|
||||
observerRef.value.observe(targetNode,config);
|
||||
|
||||
})
|
||||
|
||||
onUnmounted(()=>{
|
||||
if( observerRef.value){
|
||||
observerRef.value.disconnect();
|
||||
}
|
||||
})
|
||||
|
||||
// 自由排版
|
||||
const onFreeLayout=async ()=>{
|
||||
a4Ref.value.clearNode();
|
||||
|
||||
const x=containerWidth,y=containerHeight.value;
|
||||
|
||||
const boxList=templateOption.map(item=>{
|
||||
const box=new Box(item.width,item.height,item.padding,item.type);
|
||||
box.fontSize=item.fontSize;
|
||||
box.fontColor=item.fontColor;
|
||||
return {width:box.getOffsetWidth(),height:box.getOffsetHeight(),origin:box};
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
const value=countSquaresInContainer(x,y,boxList).sort((a,b)=>
|
||||
{
|
||||
return a.origin.type.localeCompare(b.origin.type);
|
||||
|
||||
});
|
||||
console.log(value,value.length);
|
||||
|
||||
for(const item of value){
|
||||
|
||||
console.log(item.origin);
|
||||
a4Ref.value.addLastNode({
|
||||
...item.origin,
|
||||
id: new Date().getTime()
|
||||
})
|
||||
await waitFor(2)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
function waitFor(ms){
|
||||
return new Promise((resolve)=>{
|
||||
setTimeout(resolve, ms);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
function countSquaresInContainer(x, y, objList) {
|
||||
const containerEffectiveArea = x * y;
|
||||
let currentArea = { count: 0 };
|
||||
const innerObj = [];
|
||||
let count = 0;
|
||||
while (true) {
|
||||
count = 0;
|
||||
objList.forEach(obj => {
|
||||
if (calcSquare(obj, containerEffectiveArea, currentArea)) {
|
||||
innerObj.push(obj);
|
||||
count++;
|
||||
}
|
||||
});
|
||||
if (count == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return innerObj
|
||||
}
|
||||
|
||||
const calcSquare = (obj, containerEffectiveArea, currentArea) => {
|
||||
const totalArea = obj.width * obj.height + currentArea.count;
|
||||
if (totalArea < containerEffectiveArea) {
|
||||
currentArea.count = totalArea;
|
||||
// console.log(totalArea);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
const initImgPreviewInfo = (data) => {
|
||||
imgPreviewInfo.id = data?.["id"];
|
||||
imgPreviewInfo.imgUrl = data?.["imgUrl"];
|
||||
imgPreviewInfo.position = data?.["position"];
|
||||
imgPreviewInfo.time = data?.["time"];
|
||||
imgPreviewInfo.deviceName = data?.["deviceName"];
|
||||
}
|
||||
|
||||
watch(imgIndex, (i) => {
|
||||
initImgPreviewInfo(ipcImgList.value?.[i]);
|
||||
})
|
||||
|
||||
watch(visible, () => {
|
||||
if (!visible) {
|
||||
imgIndex.value = 0;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const onDataChange = (data) => {
|
||||
// ipcImgList.value = data || [];
|
||||
}
|
||||
|
||||
const onImgIdxChange = (idx: TImgPreviewIdx) => {
|
||||
let tempIdx = imgIndex.value;
|
||||
tempIdx += idx;
|
||||
|
||||
if (tempIdx >= 0 && tempIdx < ipcImgList.value?.length) {
|
||||
imgIndex.value = tempIdx;
|
||||
}
|
||||
}
|
||||
|
||||
const onFire = async (key: string, value, data: any) => {
|
||||
// imgIndex.value = 0;
|
||||
// const resData = await api.getCaputreImgListByIpcId({ id: data })
|
||||
// ipcImgList.value = resData.data?.objectList || [];
|
||||
// initImgPreviewInfo(ipcImgList.value?.[0]);
|
||||
currentDeviceId.value = value;
|
||||
visible.value = true;
|
||||
}
|
||||
|
||||
const box1Data=computed(()=>{
|
||||
return templateOption.find(item=>item.type=="Box1");
|
||||
});
|
||||
|
||||
const box2Data=computed(()=>{
|
||||
return templateOption.find(item=>item.type=="Box2");
|
||||
});
|
||||
|
||||
const box3Data=computed(()=>{
|
||||
return templateOption.find(item=>item.type=="Box3");
|
||||
});
|
||||
|
||||
const containerHeightValue=computed(()=>{
|
||||
return containerHeight;
|
||||
});
|
||||
|
||||
const print=()=>{
|
||||
var element = document.getElementById('a4');
|
||||
var opt = {
|
||||
margin: 0,
|
||||
filename: 'myfile.pdf',
|
||||
image: { type: 'jpeg', quality:1 },
|
||||
html2canvas: { scale: 1 },
|
||||
jsPDF: { unit: 'in', format: 'letter', orientation: 'portrait' }
|
||||
};
|
||||
|
||||
// New Promise-based usage:
|
||||
html2pdf().set(opt).from(element).save();
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.device-list) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
:local(.img-preview-dialog) {
|
||||
:local(.footer) {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
>div {
|
||||
margin-left: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:local(.a4) {
|
||||
width: 793.7px;
|
||||
height: v-bind(containerHeightValue)+"px";
|
||||
// border: 1px solid black;
|
||||
}
|
||||
|
||||
:local(.params-field){
|
||||
display:flex;
|
||||
flex-direction:row;
|
||||
align-items:center;
|
||||
label{
|
||||
// width:100px;
|
||||
// flex:1;
|
||||
// flex-wrap:nowrap;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,142 @@
|
||||
<template>
|
||||
<!-- <el-watermark :font="font" :content="['加零', '+0']" :gap="[20,20]"> -->
|
||||
<div :ref="drop" class="target-box" :class="$style['a4']">
|
||||
<!-- {{ isActive ? 'Release to drop' : 'Drag item here' }} -->
|
||||
<!-- <Card v-for="(card, index) in cards" :id="card.id" :key="card.id" :text="card.text" :index="index"
|
||||
:move-card="moveCard" /> -->
|
||||
<auto-responsive v-bind="options" :itemMargin="itemMargin" :gridWidth="gridWidth" >
|
||||
<div class="column" v-for="(card, index) in cardList" :style="{height:card.getOffsetHeight()+'px',width:card.getOffsetWidth()+'px'}">
|
||||
<component :is="switchCom(card)" v-bind="card" :index="index"
|
||||
:moveCard="moveCard"></component>
|
||||
</div>
|
||||
</auto-responsive>
|
||||
</div>
|
||||
<!-- </el-watermark> -->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, unref, h, reactive } from 'vue'
|
||||
import Card from './Box1.vue'
|
||||
import { useDrop } from 'vue3-dnd'
|
||||
import { toRefs } from '@vueuse/core'
|
||||
import Box2 from './Box2.vue';
|
||||
import AutoResponsive from "../../components/autoresponsive.vue";
|
||||
import { Box } from './boxType';
|
||||
import Box3 from './Box3.vue';
|
||||
import Box4 from './Box4.vue';
|
||||
|
||||
|
||||
const options = {
|
||||
itemMargin: 1,
|
||||
containerWidth: 793.7,
|
||||
itemClassName: 'column',
|
||||
gridWidth: 1,
|
||||
transitionDuration: '.5'
|
||||
};
|
||||
|
||||
const font = reactive({
|
||||
color: 'rgba(0, 0, 0, .45)',
|
||||
})
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
itemMargin: number
|
||||
gridWidth: number
|
||||
}>(), {
|
||||
itemMargin:1,
|
||||
gridWidth: 1
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
addLastNode:(data)=>{
|
||||
const box=new Box(data.width,data.height,data.padding,data.type);
|
||||
box.fontSize=data.fontSize;
|
||||
box.fontColor=data.fontColor;
|
||||
cardList.value.push(box);
|
||||
},
|
||||
removeLastNode:()=>{
|
||||
cardList.value.splice(cardList.value.length-1);
|
||||
},
|
||||
clearNode:()=>{
|
||||
cardList.value.splice(0);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const cards = ref<Item[]>()
|
||||
|
||||
const cardList = ref<IBox[]>([]);
|
||||
|
||||
const [collect, drop] = useDrop(() => ({
|
||||
accept: 'card',
|
||||
collect: (monitor) => {
|
||||
return { isActive: monitor.canDrop() && monitor.isOver() }
|
||||
},
|
||||
drop: (item: any, monitor) => {
|
||||
// console.log(monitor.canDrop(), monitor.didDrop(), monitor.getDropResult(), monitor.getItem(), item, monitor.isOver({ shallow: true }));
|
||||
const boxItem:IBox=monitor.getItem() as IBox;
|
||||
if (monitor.canDrop() && !monitor.didDrop() && monitor.isOver({ shallow: true })) {
|
||||
const box=new Box(boxItem.width.toString(),boxItem.height.toString(),boxItem.padding.toString(),boxItem.type);
|
||||
box.fontSize=boxItem.fontSize;
|
||||
box.fontColor=boxItem.fontColor;
|
||||
cardList.value.push(box);
|
||||
console.log('add', cardList.value);
|
||||
}
|
||||
}
|
||||
}))
|
||||
const { isActive } = toRefs(collect)
|
||||
|
||||
interface Item {
|
||||
id: number
|
||||
text: string
|
||||
}
|
||||
|
||||
const switchCom = (item) => {
|
||||
switch (item.type) {
|
||||
case "Box1":
|
||||
return Box2;
|
||||
case "Box2":
|
||||
return Box3;
|
||||
case "Box3":
|
||||
return Box4;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const moveCard = (dragIndex: number, hoverIndex: number) => {
|
||||
const item = cards.value[dragIndex]
|
||||
cards.value.splice(dragIndex, 1)
|
||||
cards.value.splice(hoverIndex, 0, item)
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.device-list) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
:local(.img-preview-dialog) {
|
||||
:local(.footer) {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
>div {
|
||||
margin-left: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:local(.a4) {
|
||||
width: 793.7px;
|
||||
// height: 1122.5px;
|
||||
border: 1px solid black;
|
||||
|
||||
|
||||
.column {}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,118 @@
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, unref } from 'vue'
|
||||
import { useDrag, useDrop } from 'vue3-dnd'
|
||||
// import { ItemTypes } from './ItemTypes'
|
||||
import type { XYCoord, Identifier } from 'dnd-core'
|
||||
import { toRefs } from '@vueuse/core'
|
||||
|
||||
const props = defineProps<{
|
||||
id: any
|
||||
text: string
|
||||
index: number
|
||||
moveCard: (dragIndex: number, hoverIndex: number) => void
|
||||
}>()
|
||||
|
||||
interface DragItem {
|
||||
index: number
|
||||
id: string
|
||||
type: string
|
||||
}
|
||||
|
||||
const card = ref<HTMLDivElement>()
|
||||
const [dropCollect, drop] = useDrop<
|
||||
DragItem,
|
||||
void,
|
||||
{ handlerId: Identifier | null }
|
||||
>({
|
||||
accept: 'card',
|
||||
collect(monitor) {
|
||||
return {
|
||||
handlerId: monitor.getHandlerId(),
|
||||
}
|
||||
},
|
||||
hover(item: DragItem, monitor) {
|
||||
if (!card.value) {
|
||||
return
|
||||
}
|
||||
const dragIndex = item.index
|
||||
const hoverIndex = props.index
|
||||
// Don't replace items with themselves
|
||||
if (dragIndex === hoverIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine rectangle on screen
|
||||
const hoverBoundingRect = card.value?.getBoundingClientRect()
|
||||
|
||||
// Get vertical middle
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
|
||||
|
||||
// Determine mouse position
|
||||
const clientOffset = monitor.getClientOffset()
|
||||
|
||||
// Get pixels to the top
|
||||
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top
|
||||
|
||||
// Only perform the move when the mouse has crossed half of the items height
|
||||
// When dragging downwards, only move when the cursor is below 50%
|
||||
// When dragging upwards, only move when the cursor is above 50%
|
||||
|
||||
// Dragging downwards
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Dragging upwards
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Time to actually perform the action
|
||||
props.moveCard(dragIndex, hoverIndex)
|
||||
|
||||
// Note: we're mutating the monitor item here!
|
||||
// Generally it's better to avoid mutations,
|
||||
// but it's good here for the sake of performance
|
||||
// to avoid expensive index searches.
|
||||
item.index = hoverIndex
|
||||
},
|
||||
})
|
||||
|
||||
const dragId = ref();
|
||||
|
||||
const [collect, drag] = useDrag({
|
||||
type: 'card',
|
||||
item: () => {
|
||||
return { id: props.id, index: props.index, dragId: dragId.value }
|
||||
},
|
||||
collect: (monitor: any) => {
|
||||
return ({
|
||||
isDragging: monitor.isDragging(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const { handlerId } = toRefs(dropCollect)
|
||||
const { isDragging } = toRefs(collect)
|
||||
const opacity = computed(() => (unref(isDragging) ? 0 : 1))
|
||||
|
||||
const setRef = (el: HTMLDivElement) => {
|
||||
card.value = drag(drop(el)) as HTMLDivElement
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div :ref="setRef" class="card" :style="{ opacity }" :data-handler-id="handlerId">
|
||||
{{ text }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
margin-bottom: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: white;
|
||||
border: 1px dashed gray;
|
||||
cursor: move;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div :ref="setRef" class="card" :style="{ opacity }" :data-handler-id="handlerId">
|
||||
<div>i5 10代</div>
|
||||
<div>16g 256g</div>
|
||||
<div>显卡:2060</div>
|
||||
<div>加零租赁</div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, unref, useCssVars } from 'vue'
|
||||
import { useDrag, useDrop } from 'vue3-dnd'
|
||||
// import { ItemTypes } from './ItemTypes'
|
||||
import type { XYCoord, Identifier } from 'dnd-core'
|
||||
import { toRefs } from '@vueuse/core'
|
||||
import {Box} from "./boxType";
|
||||
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
id: any
|
||||
index: number
|
||||
width: number
|
||||
height: number
|
||||
padding:number
|
||||
fontSize:number
|
||||
fontColor:string
|
||||
moveCard?: (dragIndex: number, hoverIndex: number) => void
|
||||
}>(), {
|
||||
width: 100,
|
||||
height: 100,
|
||||
padding:10,
|
||||
fontSize:18,
|
||||
fontColor:"black"
|
||||
})
|
||||
|
||||
interface DragItem {
|
||||
index: number
|
||||
id: string
|
||||
type: string
|
||||
}
|
||||
|
||||
const BOXTYPE:TBoxType = "Box1"
|
||||
const card = ref<HTMLDivElement>()
|
||||
const [dropCollect, drop] = useDrop<
|
||||
DragItem,
|
||||
void,
|
||||
{ handlerId: Identifier | null }
|
||||
>({
|
||||
accept: 'card',
|
||||
collect(monitor) {
|
||||
return {
|
||||
handlerId: monitor.getHandlerId(),
|
||||
}
|
||||
},
|
||||
hover(item: DragItem, monitor) {
|
||||
if (!card.value) {
|
||||
return
|
||||
}
|
||||
const dragIndex = item.index
|
||||
const hoverIndex = props.index
|
||||
// Don't replace items with themselves
|
||||
if (dragIndex === hoverIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine rectangle on screen
|
||||
const hoverBoundingRect = card.value?.getBoundingClientRect()
|
||||
|
||||
// Get vertical middle
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
|
||||
|
||||
// Determine mouse position
|
||||
const clientOffset = monitor.getClientOffset()
|
||||
|
||||
// Get pixels to the top
|
||||
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top
|
||||
|
||||
// Only perform the move when the mouse has crossed half of the items height
|
||||
// When dragging downwards, only move when the cursor is below 50%
|
||||
// When dragging upwards, only move when the cursor is above 50%
|
||||
|
||||
// Dragging downwards
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Dragging upwards
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Time to actually perform the action
|
||||
props.moveCard(dragIndex, hoverIndex)
|
||||
|
||||
// Note: we're mutating the monitor item here!
|
||||
// Generally it's better to avoid mutations,
|
||||
// but it's good here for the sake of performance
|
||||
// to avoid expensive index searches.
|
||||
item.index = hoverIndex
|
||||
},
|
||||
})
|
||||
|
||||
const dragId = ref();
|
||||
|
||||
const [collect, drag] = useDrag({
|
||||
type: 'card',
|
||||
item: ():IBox => {
|
||||
const {width,height,padding,index,fontColor,fontSize}=props;
|
||||
const box=new Box(width,height,padding,BOXTYPE);
|
||||
box.fontSize=fontSize;
|
||||
box.fontColor=fontColor;
|
||||
box.index=index;
|
||||
return box;
|
||||
},
|
||||
collect: (monitor: any) => {
|
||||
return ({
|
||||
isDragging: monitor.isDragging(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const { handlerId } = toRefs(dropCollect)
|
||||
const { isDragging } = toRefs(collect)
|
||||
const opacity = computed(() => (unref(isDragging) ? 0 : 1))
|
||||
|
||||
const setRef = (el: HTMLDivElement) => {
|
||||
// card.value = drag(drop(el)) as HTMLDivElement
|
||||
card.value = drag(el) as HTMLDivElement
|
||||
}
|
||||
|
||||
const widthValue = computed(() => {
|
||||
return props.width + "px";
|
||||
})
|
||||
|
||||
const heightValue = computed(() => {
|
||||
return props.height + "px";
|
||||
})
|
||||
|
||||
const paddingValue=computed(()=>{
|
||||
console.log("padding:",props.padding)
|
||||
return props.padding+"px";
|
||||
})
|
||||
|
||||
const fontSizeValue=computed(()=>{
|
||||
console.log("padding:",props.padding)
|
||||
return props.fontSize+"px";
|
||||
})
|
||||
|
||||
const fontColorValue=computed(()=>{
|
||||
console.log("padding:",props.padding)
|
||||
return props.fontColor;
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
width: v-bind(widthValue);
|
||||
height:v-bind(heightValue);
|
||||
// margin-bottom: 8px;
|
||||
padding: v-bind(paddingValue);
|
||||
font-size: v-bind(fontSizeValue);
|
||||
color:v-bind(fontColorValue);
|
||||
background-color: white;
|
||||
border: 1px dashed gray;
|
||||
cursor: move;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,163 @@
|
||||
<template>
|
||||
<div :ref="setRef" class="card" :style="{ opacity }" :data-handler-id="handlerId">
|
||||
加零租赁
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, unref, useCssVars } from 'vue'
|
||||
import { useDrag, useDrop } from 'vue3-dnd'
|
||||
// import { ItemTypes } from './ItemTypes'
|
||||
import type { XYCoord, Identifier } from 'dnd-core'
|
||||
import { toRefs } from '@vueuse/core'
|
||||
import {Box} from "./boxType";
|
||||
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
id: any
|
||||
index: number
|
||||
width: number
|
||||
height: number
|
||||
padding:number
|
||||
fontSize:number
|
||||
fontColor:string
|
||||
moveCard?: (dragIndex: number, hoverIndex: number) => void
|
||||
}>(), {
|
||||
width: 60,
|
||||
height: 40,
|
||||
padding:2,
|
||||
fontSize:12,
|
||||
fontColor:"black"
|
||||
})
|
||||
|
||||
interface DragItem {
|
||||
index: number
|
||||
id: string
|
||||
type: string
|
||||
}
|
||||
|
||||
const BOXTYPE:TBoxType = "Box2"
|
||||
const card = ref<HTMLDivElement>()
|
||||
const [dropCollect, drop] = useDrop<
|
||||
DragItem,
|
||||
void,
|
||||
{ handlerId: Identifier | null }
|
||||
>({
|
||||
accept: 'card',
|
||||
collect(monitor) {
|
||||
return {
|
||||
handlerId: monitor.getHandlerId(),
|
||||
}
|
||||
},
|
||||
hover(item: DragItem, monitor) {
|
||||
if (!card.value) {
|
||||
return
|
||||
}
|
||||
const dragIndex = item.index
|
||||
const hoverIndex = props.index
|
||||
// Don't replace items with themselves
|
||||
if (dragIndex === hoverIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine rectangle on screen
|
||||
const hoverBoundingRect = card.value?.getBoundingClientRect()
|
||||
|
||||
// Get vertical middle
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
|
||||
|
||||
// Determine mouse position
|
||||
const clientOffset = monitor.getClientOffset()
|
||||
|
||||
// Get pixels to the top
|
||||
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top
|
||||
|
||||
// Only perform the move when the mouse has crossed half of the items height
|
||||
// When dragging downwards, only move when the cursor is below 50%
|
||||
// When dragging upwards, only move when the cursor is above 50%
|
||||
|
||||
// Dragging downwards
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Dragging upwards
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Time to actually perform the action
|
||||
props.moveCard(dragIndex, hoverIndex)
|
||||
|
||||
// Note: we're mutating the monitor item here!
|
||||
// Generally it's better to avoid mutations,
|
||||
// but it's good here for the sake of performance
|
||||
// to avoid expensive index searches.
|
||||
item.index = hoverIndex
|
||||
},
|
||||
})
|
||||
|
||||
const dragId = ref();
|
||||
|
||||
const [collect, drag] = useDrag({
|
||||
type: 'card',
|
||||
item: ():IBox => {
|
||||
const {width,height,padding,index,fontColor,fontSize}=props;
|
||||
const box=new Box(width,height,padding,BOXTYPE);
|
||||
box.fontSize=fontSize;
|
||||
box.fontColor=fontColor;
|
||||
box.index=index;
|
||||
return box;
|
||||
},
|
||||
collect: (monitor: any) => {
|
||||
return ({
|
||||
isDragging: monitor.isDragging(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const { handlerId } = toRefs(dropCollect)
|
||||
const { isDragging } = toRefs(collect)
|
||||
const opacity = computed(() => (unref(isDragging) ? 0 : 1))
|
||||
|
||||
const setRef = (el: HTMLDivElement) => {
|
||||
// card.value = drag(drop(el)) as HTMLDivElement
|
||||
card.value = drag(el) as HTMLDivElement
|
||||
}
|
||||
|
||||
const widthValue = computed(() => {
|
||||
return props.width + "px";
|
||||
})
|
||||
|
||||
const heightValue = computed(() => {
|
||||
return props.height + "px";
|
||||
})
|
||||
|
||||
const paddingValue=computed(()=>{
|
||||
console.log("padding:",props.padding)
|
||||
return props.padding+"px";
|
||||
})
|
||||
|
||||
const fontSizeValue=computed(()=>{
|
||||
console.log("padding:",props.padding)
|
||||
return props.fontSize+"px";
|
||||
})
|
||||
|
||||
const fontColorValue=computed(()=>{
|
||||
console.log("padding:",props.padding)
|
||||
return props.fontColor;
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
width: v-bind(widthValue);
|
||||
height:v-bind(heightValue);
|
||||
// margin-bottom: 8px;
|
||||
padding: v-bind(paddingValue);
|
||||
font-size: v-bind(fontSizeValue);
|
||||
color:v-bind(fontColorValue);
|
||||
background-color: white;
|
||||
border: 1px dashed gray;
|
||||
cursor: move;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div :ref="setRef" class="card" :style="{ opacity }" :data-handler-id="handlerId">
|
||||
<span>加</span><span>零</span><span>租</span><span>赁</span>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref, unref, useCssVars } from 'vue'
|
||||
import { useDrag, useDrop } from 'vue3-dnd'
|
||||
// import { ItemTypes } from './ItemTypes'
|
||||
import type { XYCoord, Identifier } from 'dnd-core'
|
||||
import { toRefs } from '@vueuse/core'
|
||||
import {Box} from "./boxType";
|
||||
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
id: any
|
||||
index: number
|
||||
width: number
|
||||
height: number
|
||||
padding:number
|
||||
fontSize:number
|
||||
fontColor:string
|
||||
moveCard?: (dragIndex: number, hoverIndex: number) => void
|
||||
}>(), {
|
||||
width: 60,
|
||||
height: 40,
|
||||
padding:2,
|
||||
fontSize:12,
|
||||
fontColor:"black"
|
||||
})
|
||||
|
||||
interface DragItem {
|
||||
index: number
|
||||
id: string
|
||||
type: string
|
||||
}
|
||||
|
||||
const BOXTYPE:TBoxType = "Box3"
|
||||
const card = ref<HTMLDivElement>()
|
||||
const [dropCollect, drop] = useDrop<
|
||||
DragItem,
|
||||
void,
|
||||
{ handlerId: Identifier | null }
|
||||
>({
|
||||
accept: 'card',
|
||||
collect(monitor) {
|
||||
return {
|
||||
handlerId: monitor.getHandlerId(),
|
||||
}
|
||||
},
|
||||
hover(item: DragItem, monitor) {
|
||||
if (!card.value) {
|
||||
return
|
||||
}
|
||||
const dragIndex = item.index
|
||||
const hoverIndex = props.index
|
||||
// Don't replace items with themselves
|
||||
if (dragIndex === hoverIndex) {
|
||||
return
|
||||
}
|
||||
|
||||
// Determine rectangle on screen
|
||||
const hoverBoundingRect = card.value?.getBoundingClientRect()
|
||||
|
||||
// Get vertical middle
|
||||
const hoverMiddleY = (hoverBoundingRect.bottom - hoverBoundingRect.top) / 2
|
||||
|
||||
// Determine mouse position
|
||||
const clientOffset = monitor.getClientOffset()
|
||||
|
||||
// Get pixels to the top
|
||||
const hoverClientY = (clientOffset as XYCoord).y - hoverBoundingRect.top
|
||||
|
||||
// Only perform the move when the mouse has crossed half of the items height
|
||||
// When dragging downwards, only move when the cursor is below 50%
|
||||
// When dragging upwards, only move when the cursor is above 50%
|
||||
|
||||
// Dragging downwards
|
||||
if (dragIndex < hoverIndex && hoverClientY < hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Dragging upwards
|
||||
if (dragIndex > hoverIndex && hoverClientY > hoverMiddleY) {
|
||||
return
|
||||
}
|
||||
|
||||
// Time to actually perform the action
|
||||
props.moveCard(dragIndex, hoverIndex)
|
||||
|
||||
// Note: we're mutating the monitor item here!
|
||||
// Generally it's better to avoid mutations,
|
||||
// but it's good here for the sake of performance
|
||||
// to avoid expensive index searches.
|
||||
item.index = hoverIndex
|
||||
},
|
||||
})
|
||||
|
||||
const dragId = ref();
|
||||
|
||||
const [collect, drag] = useDrag({
|
||||
type: 'card',
|
||||
item: ():IBox => {
|
||||
const {width,height,padding,index,fontColor,fontSize}=props;
|
||||
const box=new Box(width,height,padding,BOXTYPE);
|
||||
box.fontSize=fontSize;
|
||||
box.fontColor=fontColor;
|
||||
box.index=index;
|
||||
return box;
|
||||
},
|
||||
collect: (monitor: any) => {
|
||||
return ({
|
||||
isDragging: monitor.isDragging(),
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
const { handlerId } = toRefs(dropCollect)
|
||||
const { isDragging } = toRefs(collect)
|
||||
const opacity = computed(() => (unref(isDragging) ? 0 : 1))
|
||||
|
||||
const setRef = (el: HTMLDivElement) => {
|
||||
// card.value = drag(drop(el)) as HTMLDivElement
|
||||
card.value = drag(el) as HTMLDivElement
|
||||
}
|
||||
|
||||
const widthValue = computed(() => {
|
||||
return props.width + "px";
|
||||
})
|
||||
|
||||
const heightValue = computed(() => {
|
||||
return props.height + "px";
|
||||
})
|
||||
|
||||
const paddingValue=computed(()=>{
|
||||
console.log("padding:",props.padding)
|
||||
return props.padding+"px";
|
||||
})
|
||||
|
||||
const fontSizeValue=computed(()=>{
|
||||
console.log("padding:",props.padding)
|
||||
return props.fontSize+"px";
|
||||
})
|
||||
|
||||
const fontColorValue=computed(()=>{
|
||||
console.log("padding:",props.padding)
|
||||
return props.fontColor;
|
||||
})
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.card {
|
||||
width: v-bind(widthValue);
|
||||
height:v-bind(heightValue);
|
||||
// margin-bottom: 8px;
|
||||
padding: v-bind(paddingValue);
|
||||
font-size: v-bind(fontSizeValue);
|
||||
color:v-bind(fontColorValue);
|
||||
background-color: white;
|
||||
border: 1px dashed gray;
|
||||
cursor: move;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,81 @@
|
||||
<template>
|
||||
<div ref="watermarkContainer" class="watermark-container"></div>
|
||||
</template>
|
||||
<script>
|
||||
import { onMounted,onUnmounted,ref } from 'vue';
|
||||
|
||||
export default{
|
||||
name:'Watermark',
|
||||
setup(){
|
||||
const watermarkContainer=ref(null);
|
||||
|
||||
const createWatermark=()=>{
|
||||
if(!watermarkContainer.value)return;
|
||||
|
||||
const canvas=document.createElement('canvas');
|
||||
const ctx=canvas.getContext('2d');
|
||||
|
||||
const text='Your watermark'
|
||||
|
||||
const fontSize=4;
|
||||
ctx.font=`${fontSize}px sans-serif`;
|
||||
ctx.fillStyle='rgba(128,128,128,0.3)';
|
||||
ctx.textAlign='center';
|
||||
ctx.textBaseline='middle';
|
||||
|
||||
const width=watermarkContainer.value.clientWidth;
|
||||
const heigth=watermarkContainer.value.clientHeight;
|
||||
|
||||
const padding=20;
|
||||
const lineHeight=fontSize+padding;
|
||||
const columns=Math.ceil(width/lineHeight);
|
||||
const rows=Math.ceil(height/lineHeight);
|
||||
|
||||
canvas.width=width;
|
||||
canvas.height=height;
|
||||
|
||||
for(let i=0;i<rows;i++){
|
||||
for(let j=0;j<columns;j++){
|
||||
ctx.fillText(text,j*lineHeight+lineHeight/2,i*lineHeight+lineHeight/2);
|
||||
}
|
||||
}
|
||||
|
||||
const watermarkImage=new Image();
|
||||
watermarkImage.src=canvas.toDataURL();
|
||||
watermarkImage.onload=()=>{
|
||||
const watermarkDiv=document.createElement('div');
|
||||
watermarkDiv.style.backgroundImage=`url(${watermarkImage.src})`;
|
||||
watermarkDiv.style.backgroundRepeat='repeat';
|
||||
watermarkDiv.style.position='absolute';
|
||||
watermarkDiv.style.top=0;
|
||||
watermarkDiv.style.left=0;
|
||||
watermarkDiv.style.width='100%';
|
||||
watermarkDiv.style.height='100%';
|
||||
|
||||
watermarkContainer.value.appendChild(watermarkDiv);
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(()=>{
|
||||
createWatermark();
|
||||
window.addEventListener('resize',createWatermark);
|
||||
})
|
||||
|
||||
onUnmounted(()=>{
|
||||
window.removeEventListener('resize',createWatermark)
|
||||
})
|
||||
|
||||
|
||||
return {
|
||||
watermarkContainer
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.watermark-container{
|
||||
position:relative;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,36 @@
|
||||
export class Box implements IBox {
|
||||
type: TBoxType = "Box1";
|
||||
width: number = 0;
|
||||
height: number = 0;
|
||||
padding: number = 10;
|
||||
borderWidth: number = 1;
|
||||
fontSize:number=18;
|
||||
fontColor:string="black";
|
||||
sort: number = 1;
|
||||
index: number = 1
|
||||
|
||||
constructor(_width: string, _height: string, _padding: string | number = 10, type: TBoxType = "Box1") {
|
||||
this.type = type;
|
||||
this.width = parseFloat(_width);
|
||||
this.height = parseFloat(_height);
|
||||
this.padding = parseFloat(_padding.toString());
|
||||
}
|
||||
|
||||
getOffsetWidth() {
|
||||
return this.width + this.padding * 2 + this.borderWidth * 2;
|
||||
}
|
||||
|
||||
getOffsetHeight() {
|
||||
return this.height + this.padding * 2 + this.borderWidth * 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class Template implements ITemplate {
|
||||
id: string = "";
|
||||
width: number = 100;
|
||||
height: number = 100;
|
||||
padding: number = 10;
|
||||
fontSize: number = 18;
|
||||
fontColor: string = "#000000";
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
declare type TBoxType = "Box1" | "Box2" | "Box3" | "Box4";
|
||||
|
||||
declare interface IBox {
|
||||
type: TBoxType;
|
||||
width: number;
|
||||
height: number;
|
||||
padding: number;
|
||||
fontSize:number;
|
||||
fontColor:string;
|
||||
sort: number;
|
||||
index: number;
|
||||
getOffsetWidth: () => number;
|
||||
getOffsetHeight: () => number;
|
||||
}
|
||||
|
||||
declare interface ITemplate {
|
||||
type:TBoxType;
|
||||
width: number;
|
||||
height: number;
|
||||
padding: number;
|
||||
fontSize: number;
|
||||
fontColor: string;
|
||||
}
|
||||
@@ -0,0 +1,162 @@
|
||||
<template>
|
||||
<div :class="$style['login']">
|
||||
<div class="login-form">
|
||||
<!-- <img src="/logo.png" :style="{ marginLeft: '45px', marginBottom: '20px' }"> -->
|
||||
<h1>出行管理</h1>
|
||||
<el-form ref="formRef" :model="form" :rules="rules">
|
||||
<el-form-item prop="user">
|
||||
<el-input placeholder="请输入用户名" :prefix-icon="User" size="large" v-model.trim="form.user" />
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input placeholder="请输入密码" :prefix-icon="Lock" show-password size="large"
|
||||
v-model.trim="form.password" @keyup.enter="onSubmit" />
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button class="login-btn" :loading="loading" round size="large" type="primary" @click="onSubmit">
|
||||
登 录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { reactive, ref, onBeforeMount, onBeforeUnmount, computed } from "vue";
|
||||
import { ElMessage, FormItemRule } from "element-plus";
|
||||
import md5 from "md5";
|
||||
import * as api from "@/common/api";
|
||||
import Cookies from "js-cookie";
|
||||
import { User, Lock } from "@element-plus/icons-vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useAppStore } from "@/store/app";
|
||||
|
||||
const router = useRouter();
|
||||
const formRef = ref();
|
||||
const form = reactive({
|
||||
user: "",
|
||||
password: ""
|
||||
});
|
||||
const appStore = useAppStore();
|
||||
|
||||
const rules = ref<Record<string, FormItemRule | FormItemRule[]>>({
|
||||
user: {
|
||||
required: true,
|
||||
message: "请输入用户名",
|
||||
},
|
||||
password: {
|
||||
required: true,
|
||||
message: "请输入密码",
|
||||
},
|
||||
});
|
||||
|
||||
const onSubmit = async () => {
|
||||
formRef.value?.validate(async (valid) => {
|
||||
if (valid) {
|
||||
const { code, data } = await api.employeeLogin({
|
||||
userName: form.user,
|
||||
pwd: form.password
|
||||
});
|
||||
|
||||
if (code == 0 && data) {
|
||||
// 保存登录token信息并设置cookie 过期时间为3天
|
||||
// Cookies.set("Authorization", token, { expires: 3 });
|
||||
Cookies.set("UserInfo", JSON.stringify(data), { expires: 3 });
|
||||
|
||||
appStore.saveUserInfo(data);
|
||||
router.push("/app/TravelInformation");
|
||||
// router.push("/app/system/info");
|
||||
}
|
||||
else {
|
||||
ElMessage.error("登录失败");
|
||||
}
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
|
||||
html,
|
||||
body,
|
||||
#app {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
:local(.login) {
|
||||
background-image: url(/login_bg.png);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
position: relative;
|
||||
|
||||
.login-form {
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
// padding: 120px 160px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: 0px;
|
||||
bottom: 1px;
|
||||
left: 1px;
|
||||
margin: auto;
|
||||
|
||||
.el-input__wrapper {
|
||||
background-color: transparent;
|
||||
height: 40px;
|
||||
border-radius: 50px;
|
||||
}
|
||||
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: #FFFF;
|
||||
font-size: 40px;
|
||||
font-weight: 400;
|
||||
margin-top: 0;
|
||||
margin-bottom: 60px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
width: 234px;
|
||||
margin: 0px auto 30px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.el-input {
|
||||
font-size: 16px;
|
||||
|
||||
.el-input__inner {
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-input__inner) {
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.login-btn {
|
||||
width: 100%;
|
||||
margin-top: 20px;
|
||||
height: 50px;
|
||||
font-size: 30px;
|
||||
background: linear-gradient(180deg, #039ad0 0%, #0250bc 100%);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,110 @@
|
||||
<template>
|
||||
<div :class="$style['device-list']">
|
||||
<h2>通知列表</h2>
|
||||
<um-table-class-enhance class="role-right" :target="Notice" :data="[]"
|
||||
:other-params="{ userId: appStore.userInfo?.id }" @data-change="onDataChange"
|
||||
@fire="onFire">
|
||||
</um-table-class-enhance>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// import UmTableClassEnhance from "@/components/UmTable/UmByClassEnhance.vue";
|
||||
import { UmByClassEnhance as UmTableClassEnhance } from "funjiaui";
|
||||
// import UmDialog from "@/components/UmDialog/index.vue";
|
||||
import Notice from "@/containers/viewModel/Notice";
|
||||
import ImgPreview from "@/components/ImgPreview.vue";
|
||||
import { ref, reactive, watch } from "vue";
|
||||
import * as api from "@/common/api";
|
||||
// import { fDT } from "marsLib/date";
|
||||
import CaptureList from "@/containers/CaptureList.vue";
|
||||
import { useAppStore } from "@/store/app";
|
||||
|
||||
const visible = ref(false);
|
||||
const imgIndex = ref(0);
|
||||
const ipcImgList = ref([]);
|
||||
const imgPreviewInfo = reactive({
|
||||
id: "",
|
||||
imgUrl: "",
|
||||
position: "",
|
||||
time: "",
|
||||
deviceName: ""
|
||||
});
|
||||
const currentDeviceId = ref(null);
|
||||
const appStore = useAppStore();
|
||||
const traveInfomation = new Notice();
|
||||
const formData = reactive<Notice>(traveInfomation)
|
||||
// debugger;
|
||||
Notice.userId = appStore.userInfo?.id;
|
||||
|
||||
watch(() => appStore.userInfo, (uI) => {
|
||||
// Notice.userId
|
||||
Notice.userId = uI?.id;
|
||||
})
|
||||
|
||||
// Ipc.singleListen = (rowData) => {
|
||||
// initImgPreviewInfo(rowData);
|
||||
// imgIndex.value = (ipcImgList.value || []).findIndex(item => rowData.id == item.id) || 0;
|
||||
// visible.value = true;
|
||||
// }
|
||||
|
||||
const initImgPreviewInfo = (data) => {
|
||||
imgPreviewInfo.id = data?.["id"];
|
||||
imgPreviewInfo.imgUrl = data?.["imgUrl"];
|
||||
imgPreviewInfo.position = data?.["position"];
|
||||
imgPreviewInfo.time = data?.["time"];
|
||||
imgPreviewInfo.deviceName = data?.["deviceName"];
|
||||
}
|
||||
|
||||
watch(imgIndex, (i) => {
|
||||
initImgPreviewInfo(ipcImgList.value?.[i]);
|
||||
})
|
||||
|
||||
watch(visible, () => {
|
||||
if (!visible) {
|
||||
imgIndex.value = 0;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const onDataChange = (data) => {
|
||||
// ipcImgList.value = data || [];
|
||||
}
|
||||
|
||||
const onImgIdxChange = (idx: TImgPreviewIdx) => {
|
||||
let tempIdx = imgIndex.value;
|
||||
tempIdx += idx;
|
||||
|
||||
if (tempIdx >= 0 && tempIdx < ipcImgList.value?.length) {
|
||||
imgIndex.value = tempIdx;
|
||||
}
|
||||
}
|
||||
|
||||
const onFire = async (key: string, value, data: any) => {
|
||||
// imgIndex.value = 0;
|
||||
// const resData = await api.getCaputreImgListByIpcId({ id: data })
|
||||
// ipcImgList.value = resData.data?.objectList || [];
|
||||
// initImgPreviewInfo(ipcImgList.value?.[0]);
|
||||
currentDeviceId.value = value;
|
||||
visible.value = true;
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.device-list) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
:local(.img-preview-dialog) {
|
||||
:local(.footer) {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
>div {
|
||||
margin-left: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,99 @@
|
||||
<template>
|
||||
<div :class="$style['device-list']">
|
||||
<h2>问答列表</h2>
|
||||
<um-table-class-enhance class="role-right" :target="QA" :data="[]" @data-change="onDataChange" @fire="onFire">
|
||||
</um-table-class-enhance>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// import UmTableClassEnhance from "@/components/UmTable/UmByClassEnhance.vue";
|
||||
// import { UmByClassEnhance as UmTableClassEnhance } from "../../lib/funjiaui.es.js";
|
||||
import { UmByClassEnhance as UmTableClassEnhance } from "funjiaui";
|
||||
// import UmDialog from "@/components/UmDialog/index.vue";
|
||||
import QA from "@/containers/viewModel/QA";
|
||||
|
||||
import { ref, reactive, watch } from "vue";
|
||||
import * as api from "@/common/api";
|
||||
// import { fDT } from "marsLib/date";
|
||||
import CaptureList from "@/containers/CaptureList.vue";
|
||||
// import "funjiaui/style.css";
|
||||
|
||||
const visible = ref(false);
|
||||
const imgIndex = ref(0);
|
||||
const ipcImgList = ref([]);
|
||||
const imgPreviewInfo = reactive({
|
||||
id: "",
|
||||
imgUrl: "",
|
||||
position: "",
|
||||
time: "",
|
||||
deviceName: ""
|
||||
});
|
||||
const currentDeviceId = ref(null);
|
||||
|
||||
// Ipc.singleListen = (rowData) => {
|
||||
// initImgPreviewInfo(rowData);
|
||||
// imgIndex.value = (ipcImgList.value || []).findIndex(item => rowData.id == item.id) || 0;
|
||||
// visible.value = true;
|
||||
// }
|
||||
|
||||
const initImgPreviewInfo = (data) => {
|
||||
imgPreviewInfo.id = data?.["id"];
|
||||
imgPreviewInfo.imgUrl = data?.["imgUrl"];
|
||||
imgPreviewInfo.position = data?.["position"];
|
||||
imgPreviewInfo.time = data?.["time"];
|
||||
imgPreviewInfo.deviceName = data?.["deviceName"];
|
||||
}
|
||||
|
||||
watch(imgIndex, (i) => {
|
||||
initImgPreviewInfo(ipcImgList.value?.[i]);
|
||||
})
|
||||
|
||||
watch(visible, () => {
|
||||
if (!visible) {
|
||||
imgIndex.value = 0;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const onDataChange = (data) => {
|
||||
// ipcImgList.value = data || [];
|
||||
}
|
||||
|
||||
const onImgIdxChange = (idx: TImgPreviewIdx) => {
|
||||
let tempIdx = imgIndex.value;
|
||||
tempIdx += idx;
|
||||
|
||||
if (tempIdx >= 0 && tempIdx < ipcImgList.value?.length) {
|
||||
imgIndex.value = tempIdx;
|
||||
}
|
||||
}
|
||||
|
||||
const onFire = async (key: string, value, data: any) => {
|
||||
// imgIndex.value = 0;
|
||||
// const resData = await api.getCaputreImgListByIpcId({ id: data })
|
||||
// ipcImgList.value = resData.data?.objectList || [];
|
||||
// initImgPreviewInfo(ipcImgList.value?.[0]);
|
||||
currentDeviceId.value = value;
|
||||
visible.value = true;
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.device-list) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
:local(.img-preview-dialog) {
|
||||
:local(.footer) {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
>div {
|
||||
margin-left: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,311 @@
|
||||
<template>
|
||||
<div :class="$style['sett']">
|
||||
<h2>重启</h2>
|
||||
<div>
|
||||
<el-button @click="onRestartSystemConfirm">重启系统</el-button>
|
||||
<el-button @click="onRestartAppConfirm">重启程序</el-button>
|
||||
</div>
|
||||
<h2>系统升级</h2>
|
||||
<upgrade-system></upgrade-system>
|
||||
<um-dialog :model-value="confirmDialog.visible" :title="confirmDialog.title" width="500px"
|
||||
:show-close="confirmDialog.showClose" @on-ok="confirmMethod" :footer="confirmDialog.showFooter"
|
||||
:close-on-press-escape="false" @update:model-value="onModelValueChange">
|
||||
<div>{{ confirmDialog.content }}</div>
|
||||
<component :is="confirmDialog.progress" :text-inside="progressParams.textInside"
|
||||
:stroke-width="progressParams.strokeWidth" :percentage="progressParams.percentage"></component>
|
||||
</um-dialog>
|
||||
<h2>机器码</h2>
|
||||
<el-link v-if="downloadFileName" type="primary" :icon="Download" href="/api/system/license/download/hostid.conf"
|
||||
:download="downloadFileName">下载机器码</el-link>
|
||||
<h2>更新授权信息</h2>
|
||||
<license-update />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import * as api from "@/common/api";
|
||||
import { ElMessage, ElProgress, UploadFiles, UploadProgressEvent, UploadRawFile } from "element-plus";
|
||||
import {
|
||||
UploadFilled
|
||||
} from '@element-plus/icons-vue'
|
||||
import { UmDialog } from "funjiaui";
|
||||
import { h, reactive, ref, onBeforeUnmount, onBeforeMount } from "vue";
|
||||
import { toFixed } from "@/common/tool";
|
||||
import { merge } from "lodash-es";
|
||||
import CountDownMessage from "@/components/CountDownMessage.vue";
|
||||
import { useRouter, onBeforeRouteLeave } from "vue-router";
|
||||
import UpgradeSystem from "@/containers/UpgradeSystem.vue";
|
||||
import LicenseUpdate from "@/containers/LicenseUpdate.vue";
|
||||
import { Download } from '@element-plus/icons-vue';
|
||||
|
||||
const PINGURL = "/api/get/system";
|
||||
// 请求成功时得到的步长,基本上4次请求就可以确定程序重启成功了
|
||||
const PONGSTEP = 1 / 4;
|
||||
|
||||
const confirmDialog = reactive({
|
||||
visible: false,
|
||||
content: "",
|
||||
progress: null,
|
||||
showClose: true,
|
||||
title: "提示",
|
||||
showFooter: true
|
||||
});
|
||||
const progressParams = reactive({
|
||||
textInside: true,
|
||||
strokeWidth: 26,
|
||||
percentage: 0,
|
||||
maxTimeSpan: 30,// 单位:秒
|
||||
startTime: null
|
||||
});
|
||||
// 二次确认框回调事件
|
||||
const confirmMethod = ref<any>(null);
|
||||
const coutDownTOV = ref();
|
||||
const router = useRouter();
|
||||
const downloadFileName = ref();
|
||||
|
||||
const closeWindow = (event) => {
|
||||
// Cancel the event as stated by the standard.
|
||||
event.preventDefault();
|
||||
// Chrome requires returnValue to be set.
|
||||
event.returnValue = '12313';
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
api.getSytemInfo().then((res) => {
|
||||
downloadFileName.value = res?.sn + ".conf";
|
||||
})
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 程序关闭前,清除定时器
|
||||
clearTimeout(coutDownTOV.value);
|
||||
window.removeEventListener('beforeunload', closeWindow);
|
||||
})
|
||||
|
||||
onBeforeRouteLeave(() => {
|
||||
if (!confirmDialog.progress) {
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
ElMessage.warning('任务进行中,请不要操作系统');
|
||||
}
|
||||
return false;
|
||||
})
|
||||
|
||||
const clear = () => {
|
||||
merge(confirmDialog, {
|
||||
visible: false,
|
||||
content: "",
|
||||
progress: null,
|
||||
showClose: true
|
||||
});
|
||||
merge(progressParams, {
|
||||
textInside: true,
|
||||
strokeWidth: 26,
|
||||
percentage: 0,
|
||||
maxTimeSpan: 30,// 单位:秒
|
||||
startTime: null
|
||||
});
|
||||
confirmMethod.value = null;
|
||||
clearTimeout(coutDownTOV.value);
|
||||
}
|
||||
|
||||
const onRestartSystemConfirm = () => {
|
||||
confirmDialog.visible = true;
|
||||
confirmDialog.content = "确定要重启系统吗!";
|
||||
|
||||
confirmMethod.value = onRestartSystem;
|
||||
}
|
||||
|
||||
const onRestartSystem = async () => {
|
||||
const data = await api.restartSystem();
|
||||
if (data) {
|
||||
confirmDialog.title = "重启系统中...";
|
||||
confirmDialog.showFooter = false;
|
||||
enableProgress('重启系统程序成功');
|
||||
}
|
||||
}
|
||||
|
||||
const onRestartAppConfirm = () => {
|
||||
confirmDialog.visible = true;
|
||||
confirmDialog.content = "确定要重启程序吗!";
|
||||
|
||||
confirmMethod.value = onRestartApp;
|
||||
}
|
||||
|
||||
const onRestartApp = async () => {
|
||||
const data = await api.restartApp();
|
||||
if (data) {
|
||||
confirmDialog.title = "重启系统程序中...";
|
||||
confirmDialog.showFooter = false;
|
||||
enableProgress('重启系统程序成功');
|
||||
// ElMessage.success("操作成功");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 启用进度条
|
||||
*/
|
||||
const enableProgress = (succMsg) => {
|
||||
window.removeEventListener('beforeunload', closeWindow);
|
||||
window.addEventListener('beforeunload', closeWindow);
|
||||
|
||||
// let total=30;
|
||||
confirmDialog.content = "";
|
||||
confirmDialog.progress = ElProgress;
|
||||
confirmDialog.showClose = false;
|
||||
|
||||
progressParams.startTime = new Date();
|
||||
enableCountDown(succMsg);
|
||||
}
|
||||
|
||||
const enableCountDown = (succMsg) => {
|
||||
clearTimeout(coutDownTOV.value);
|
||||
coutDownTOV.value = setTimeout(async () => {
|
||||
const timeSpan = new Date().getTime() - (progressParams.startTime || new Date())?.getTime();
|
||||
if ((timeSpan / progressParams.maxTimeSpan / 1000) > 4 / 5) {
|
||||
progressParams.percentage = progressParams.percentage < 80 ? 80 : progressParams.percentage;
|
||||
}
|
||||
else {
|
||||
const percentage = toFixed(timeSpan / 1000 / progressParams.maxTimeSpan);
|
||||
progressParams.percentage = progressParams.percentage < percentage ? percentage : progressParams.percentage;
|
||||
}
|
||||
|
||||
const pong = await pingService();
|
||||
if (pong) {
|
||||
// 只要能正确获取返回值,一般4秒内就可以判定服务已经启动成功
|
||||
progressParams.percentage = toFixed((parseFloat(progressParams.percentage) / 100 + PONGSTEP));
|
||||
}
|
||||
|
||||
if (progressParams.percentage < 100) {
|
||||
enableCountDown(succMsg);
|
||||
}
|
||||
else {
|
||||
progressParams.percentage = 100;
|
||||
showMsg();
|
||||
}
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
/** ping接口服务,如果ping通代表服务当前是启动状态 */
|
||||
const pingService = async (): Promise<boolean> => {
|
||||
try {
|
||||
// 用于终止Fetch请求的控制器。
|
||||
const controller = new AbortController();
|
||||
const { signal } = controller;
|
||||
|
||||
// Promise.race 可以处理异步请求的先后问题,这里用于
|
||||
// 处理接口是否按预期请求完成
|
||||
const data = await Promise.race([
|
||||
fetch(PINGURL, { signal }),
|
||||
new Promise((resolve) => {
|
||||
setTimeout(() => {
|
||||
// 终止目标请求
|
||||
controller.abort();
|
||||
resolve({ error: true });
|
||||
}, 2000)
|
||||
})
|
||||
]).catch((err) => {
|
||||
return { error: true };
|
||||
})
|
||||
|
||||
// 数据中未返回error字段或字段不为true时,
|
||||
// 或者接口返回值中status是200|401 则表明请求是成功的
|
||||
return !!!data?.error || [200, 401].includes(data?.status);
|
||||
} catch (error) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const verifyFileType = (fileName: string) => {
|
||||
const fileTypes = [".bin", ".zip", ".gz"];
|
||||
const fileEnd = fileName.substring(fileName.lastIndexOf("."));
|
||||
if (!fileTypes.includes(fileEnd)) {
|
||||
ElMessage.warning("不支持该类型文件");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const verifyFileSize = (size: number) => {
|
||||
const maxMb = 1024;
|
||||
const minKb = 14;
|
||||
const fileMaxSize = 1024 * maxMb;//300M
|
||||
const fileMinSize = 14;
|
||||
if (size > fileMaxSize) {
|
||||
ElMessage.warning("附件大小不能大于" + maxMb + "GB!");
|
||||
return false;
|
||||
}
|
||||
else if (size < fileMinSize) {
|
||||
ElMessage.warning("附件大小不能小于" + minKb + "KB!");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const onBeforeUpload = (rawFile: UploadRawFile) => {
|
||||
const fileName = rawFile.name, fileSize = rawFile.size;
|
||||
const methodMap = [[verifyFileType, verifyFileSize], [fileName, fileSize]];
|
||||
let result = true;
|
||||
let method: any, params;
|
||||
for (let i = 0; i < methodMap[0].length; i++) {
|
||||
method = methodMap[0][i];
|
||||
params = methodMap[1][i];
|
||||
|
||||
if (result && method) {
|
||||
result = method?.(params);
|
||||
}
|
||||
else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
const onError = () => {
|
||||
ElMessage.error("文件上传失败");
|
||||
}
|
||||
|
||||
const onSuccess = (response: any) => {
|
||||
if (response.status == "200") {
|
||||
ElMessage.success("系统升级成功");
|
||||
}
|
||||
else {
|
||||
ElMessage.error(response.reason || "文件上传失败");
|
||||
}
|
||||
}
|
||||
|
||||
const showMsg = () => {
|
||||
const msgInstance = ElMessage({
|
||||
type: "success",
|
||||
message: h(CountDownMessage, {
|
||||
getMsgTempalte(value: number) {
|
||||
return `操作成功,${value}秒倒计时后将跳转到登录页!`
|
||||
},
|
||||
onNext() {
|
||||
msgInstance.close();
|
||||
clear();
|
||||
router.push('/login');
|
||||
}
|
||||
}),
|
||||
duration: 10 * 1000
|
||||
});
|
||||
}
|
||||
|
||||
const onModelValueChange = (value) => {
|
||||
if (!confirmDialog.progress) {
|
||||
confirmDialog.visible = value;
|
||||
}
|
||||
else {
|
||||
ElMessage.warning('任务进行中,请不要操作系统');
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.sett) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<SystemInfoFormDetail></SystemInfoFormDetail>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import SystemInfoFormDetail from "@/containers/SystemInfoFormDetail.vue"
|
||||
</script>
|
||||
@@ -0,0 +1,17 @@
|
||||
<template>
|
||||
<div :class="$style['system-log']">
|
||||
<h2>系统日志</h2>
|
||||
<log-info></log-info>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import LogInfo from "@/containers/LogInfo.vue";
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.system-log) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,109 @@
|
||||
<template>
|
||||
<div :class="$style['device-list']">
|
||||
<h2>出行列表</h2>
|
||||
<um-table-class-enhance class="role-right" :target="TravelInformation" :data="[]"
|
||||
:other-params="{ userId: appStore.userInfo?.id }" @data-change="onDataChange"
|
||||
@fire="onFire">
|
||||
</um-table-class-enhance>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { UmByClassEnhance as UmTableClassEnhance } from "funjiaui";
|
||||
// import UmDialog from "@/components/UmDialog/index.vue";
|
||||
import TravelInformation from "@/containers/viewModel/TravelInformation";
|
||||
import ImgPreview from "@/components/ImgPreview.vue";
|
||||
import { ref, reactive, watch } from "vue";
|
||||
import * as api from "@/common/api";
|
||||
// import { fDT } from "marsLib/date";
|
||||
import CaptureList from "@/containers/CaptureList.vue";
|
||||
import { useAppStore } from "@/store/app";
|
||||
|
||||
const visible = ref(false);
|
||||
const imgIndex = ref(0);
|
||||
const ipcImgList = ref([]);
|
||||
const imgPreviewInfo = reactive({
|
||||
id: "",
|
||||
imgUrl: "",
|
||||
position: "",
|
||||
time: "",
|
||||
deviceName: ""
|
||||
});
|
||||
const currentDeviceId = ref(null);
|
||||
const appStore = useAppStore();
|
||||
const traveInfomation = new TravelInformation();
|
||||
const formData = reactive<TravelInformation>(traveInfomation)
|
||||
// debugger;
|
||||
TravelInformation.userId = appStore.userInfo?.id;
|
||||
|
||||
watch(() => appStore.userInfo, (uI) => {
|
||||
// TravelInformation.userId
|
||||
TravelInformation.userId = uI?.id;
|
||||
})
|
||||
|
||||
// Ipc.singleListen = (rowData) => {
|
||||
// initImgPreviewInfo(rowData);
|
||||
// imgIndex.value = (ipcImgList.value || []).findIndex(item => rowData.id == item.id) || 0;
|
||||
// visible.value = true;
|
||||
// }
|
||||
|
||||
const initImgPreviewInfo = (data) => {
|
||||
imgPreviewInfo.id = data?.["id"];
|
||||
imgPreviewInfo.imgUrl = data?.["imgUrl"];
|
||||
imgPreviewInfo.position = data?.["position"];
|
||||
imgPreviewInfo.time = data?.["time"];
|
||||
imgPreviewInfo.deviceName = data?.["deviceName"];
|
||||
}
|
||||
|
||||
watch(imgIndex, (i) => {
|
||||
initImgPreviewInfo(ipcImgList.value?.[i]);
|
||||
})
|
||||
|
||||
watch(visible, () => {
|
||||
if (!visible) {
|
||||
imgIndex.value = 0;
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
const onDataChange = (data) => {
|
||||
// ipcImgList.value = data || [];
|
||||
}
|
||||
|
||||
const onImgIdxChange = (idx: TImgPreviewIdx) => {
|
||||
let tempIdx = imgIndex.value;
|
||||
tempIdx += idx;
|
||||
|
||||
if (tempIdx >= 0 && tempIdx < ipcImgList.value?.length) {
|
||||
imgIndex.value = tempIdx;
|
||||
}
|
||||
}
|
||||
|
||||
const onFire = async (key: string, value, data: any) => {
|
||||
// imgIndex.value = 0;
|
||||
// const resData = await api.getCaputreImgListByIpcId({ id: data })
|
||||
// ipcImgList.value = resData.data?.objectList || [];
|
||||
// initImgPreviewInfo(ipcImgList.value?.[0]);
|
||||
currentDeviceId.value = value;
|
||||
visible.value = true;
|
||||
}
|
||||
</script>
|
||||
<style module lang="scss">
|
||||
:global {
|
||||
:local(.device-list) {
|
||||
padding: 20px;
|
||||
background-color: #FFF;
|
||||
}
|
||||
|
||||
:local(.img-preview-dialog) {
|
||||
:local(.footer) {
|
||||
margin-top: 20px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
>div {
|
||||
margin-left: 40px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,6 @@
|
||||
<template>
|
||||
<up-server />
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import UpServer from "@/containers/UpServer.vue"
|
||||
</script>
|
||||
Reference in New Issue
Block a user