feat: first commit

This commit is contained in:
2025-01-24 00:49:17 +08:00
commit c2cbc1b1d6
88 changed files with 12831 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+16
View File
@@ -0,0 +1,16 @@
# Vue 3 + TypeScript + Vite
This template should help get you started developing with Vue 3 and TypeScript in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
## Recommended IDE Setup
- [VS Code](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=johnsoncodehk.volar)
## Type Support For `.vue` Imports in TS
Since TypeScript cannot handle type information for `.vue` imports, they are shimmed to be a generic Vue component type by default. In most cases this is fine if you don't really care about component prop types outside of templates. However, if you wish to get actual prop types in `.vue` imports (for example to get props validation when using manual `h(...)` calls), you can enable Volar's Take Over mode by following these steps:
1. Run `Extensions: Show Built-in Extensions` from VS Code's command palette, look for `TypeScript and JavaScript Language Features`, then right click and select `Disable (Workspace)`. By default, Take Over mode will enable itself if the default TypeScript extension is disabled.
2. Reload the VS Code window by running `Developer: Reload Window` from the command palette.
You can learn more about Take Over mode [here](https://github.com/johnsoncodehk/volar/discussions/471).
+6
View File
@@ -0,0 +1,6 @@
// Generated by 'unplugin-auto-import'
// We suggest you to commit this file into source control
declare global {
}
export {}
+39
View File
@@ -0,0 +1,39 @@
// generated by unplugin-vue-components
// We suggest you to commit this file into source control
// Read more: https://github.com/vuejs/core/pull/3399
import '@vue/runtime-core'
declare module '@vue/runtime-core' {
export interface GlobalComponents {
Angle: typeof import('./src/components/Angle.vue')['default']
Autoresponsive: typeof import('./src/components/autoresponsive.vue')['default']
CalcRectDock: typeof import('./src/components/CalcRectDock.vue')['default']
CodeMirror: typeof import('./src/components/CodeMirror.vue')['default']
CountDownMessage: typeof import('./src/components/CountDownMessage.vue')['default']
DynamicTags: typeof import('./src/components/DynamicTags.vue')['default']
ElButton: typeof import('element-plus/es')['ElButton']
ElCol: typeof import('element-plus/es')['ElCol']
ElDropdown: typeof import('element-plus/es')['ElDropdown']
ElDropdownItem: typeof import('element-plus/es')['ElDropdownItem']
ElDropdownMenu: typeof import('element-plus/es')['ElDropdownMenu']
ElForm: typeof import('element-plus/es')['ElForm']
ElFormItem: typeof import('element-plus/es')['ElFormItem']
ElIcon: typeof import('element-plus/es')['ElIcon']
ElInput: typeof import('element-plus/es')['ElInput']
ElLink: typeof import('element-plus/es')['ElLink']
ElMenu: typeof import('element-plus/es')['ElMenu']
ElMenuItem: typeof import('element-plus/es')['ElMenuItem']
ElRow: typeof import('element-plus/es')['ElRow']
ElTag: typeof import('element-plus/es')['ElTag']
ImgPreview: typeof import('./src/components/ImgPreview.vue')['default']
Layout: typeof import('./src/components/Layout.vue')['default']
ObjectRect: typeof import('./src/components/ObjectRect.vue')['default']
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
SingleUpload: typeof import('./src/components/SingleUpload.vue')['default']
SystemLayout: typeof import('./src/components/SystemLayout.vue')['default']
SystemMenu: typeof import('./src/components/SystemMenu.vue')['default']
}
}
export {}
+16
View File
@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>&lrm;</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>
+3459
View File
File diff suppressed because it is too large Load Diff
+49
View File
@@ -0,0 +1,49 @@
{
"name": "funjia-admin-template-vue",
"private": true,
"version": "0.0.1",
"scripts": {
"dev": "vite --port=4444",
"build": "vite build",
"preview": "vite preview --port=4444"
},
"dependencies": {
"@codemirror/lang-javascript": "^6.1.7",
"@kousum/vue3-window": "^0.0.3",
"@types/axios": "^0.14.0",
"@types/js-cookie": "^3.0.1",
"autoresponsive-vue3": "^2.0.4",
"axios": "^0.26.1",
"codemirror": "^6.0.1",
"dayjs": "^1.10.7",
"element-plus": "2.1.10",
"funjialib": "git+http://git.funjia.top/funjia/FunJiaLib_Package.git",
"funjiaui": "git+http://git.funjia.top/funjia/FunjiaUI_Vue.git#v1.1.2",
"html-print-element": "^0.0.5",
"html2pdf.js": "^0.10.2",
"js-cookie": "^3.0.1",
"lodash-es": "^4.17.21",
"md5": "^2.3.0",
"pinia": "2.0.13",
"printd": "^1.6.0",
"react-dnd-html5-backend": "^16.0.1",
"vue": "3.2.33",
"vue-codemirror": "^6.1.1",
"vue-cropperjs": "^5.0.0",
"vue-router": "^4.0.14",
"vue3-dnd": "^2.1.0"
},
"devDependencies": {
"@types/lodash-es": "^4.17.6",
"@types/node": "^17.0.25",
"@vitejs/plugin-vue": "^2.3.1",
"reflect-metadata": "^0.1.13",
"sass": "^1.50.1",
"typescript": "^4.5.4",
"unplugin-auto-import": "^0.7.1",
"unplugin-element-plus": "^0.4.0",
"unplugin-vue-components": "^0.19.3",
"vite": "^2.9.5",
"vue-tsc": "^0.34.7"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

+69
View File
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg width="1361px" height="609px" viewBox="0 0 1361 609" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 46.2 (44496) - http://www.bohemiancoding.com/sketch -->
<title>Group 21</title>
<desc>Created with Sketch.</desc>
<defs></defs>
<g id="Ant-Design-Pro-3.0" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="账户密码登录-校验" transform="translate(-79.000000, -82.000000)">
<g id="Group-21" transform="translate(77.000000, 73.000000)">
<g id="Group-18" opacity="0.8" transform="translate(74.901416, 569.699158) rotate(-7.000000) translate(-74.901416, -569.699158) translate(4.901416, 525.199158)">
<ellipse id="Oval-11" fill="#CFDAE6" opacity="0.25" cx="63.5748792" cy="32.468367" rx="21.7830479" ry="21.766008"></ellipse>
<ellipse id="Oval-3" fill="#CFDAE6" opacity="0.599999964" cx="5.98746479" cy="13.8668601" rx="5.2173913" ry="5.21330997"></ellipse>
<path d="M38.1354514,88.3520215 C43.8984227,88.3520215 48.570234,83.6838647 48.570234,77.9254015 C48.570234,72.1669383 43.8984227,67.4987816 38.1354514,67.4987816 C32.3724801,67.4987816 27.7006688,72.1669383 27.7006688,77.9254015 C27.7006688,83.6838647 32.3724801,88.3520215 38.1354514,88.3520215 Z" id="Oval-3-Copy" fill="#CFDAE6" opacity="0.45"></path>
<path d="M64.2775582,33.1704963 L119.185836,16.5654915" id="Path-12" stroke="#CFDAE6" stroke-width="1.73913043" stroke-linecap="round" stroke-linejoin="round"></path>
<path d="M42.1431708,26.5002681 L7.71190162,14.5640702" id="Path-16" stroke="#E0B4B7" stroke-width="0.702678964" opacity="0.7" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<path d="M63.9262187,33.521561 L43.6721326,69.3250951" id="Path-15" stroke="#BACAD9" stroke-width="0.702678964" stroke-linecap="round" stroke-linejoin="round" stroke-dasharray="1.405357899873153,2.108036953469981"></path>
<g id="Group-17" transform="translate(126.850922, 13.543654) rotate(30.000000) translate(-126.850922, -13.543654) translate(117.285705, 4.381889)" fill="#CFDAE6">
<ellipse id="Oval-4" opacity="0.45" cx="9.13482653" cy="9.12768076" rx="9.13482653" ry="9.12768076"></ellipse>
<path d="M18.2696531,18.2553615 C18.2696531,13.2142826 14.1798519,9.12768076 9.13482653,9.12768076 C4.08980114,9.12768076 0,13.2142826 0,18.2553615 L18.2696531,18.2553615 Z" id="Oval-4" transform="translate(9.134827, 13.691521) scale(-1, -1) translate(-9.134827, -13.691521) "></path>
</g>
</g>
<g id="Group-14" transform="translate(216.294700, 123.725600) rotate(-5.000000) translate(-216.294700, -123.725600) translate(106.294700, 35.225600)">
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.25" cx="29.1176471" cy="29.1402439" rx="29.1176471" ry="29.1402439"></ellipse>
<ellipse id="Oval-2" fill="#CFDAE6" opacity="0.3" cx="29.1176471" cy="29.1402439" rx="21.5686275" ry="21.5853659"></ellipse>
<ellipse id="Oval-2-Copy" stroke="#CFDAE6" opacity="0.4" cx="179.019608" cy="138.146341" rx="23.7254902" ry="23.7439024"></ellipse>
<ellipse id="Oval-2" fill="#BACAD9" opacity="0.5" cx="29.1176471" cy="29.1402439" rx="10.7843137" ry="10.7926829"></ellipse>
<path d="M29.1176471,39.9329268 L29.1176471,18.347561 C23.1616351,18.347561 18.3333333,23.1796097 18.3333333,29.1402439 C18.3333333,35.1008781 23.1616351,39.9329268 29.1176471,39.9329268 Z" id="Oval-2" fill="#BACAD9"></path>
<g id="Group-9" opacity="0.45" transform="translate(172.000000, 131.000000)" fill="#E6A1A6">
<ellipse id="Oval-2-Copy-2" cx="7.01960784" cy="7.14634146" rx="6.47058824" ry="6.47560976"></ellipse>
<path d="M0.549019608,13.6219512 C4.12262681,13.6219512 7.01960784,10.722722 7.01960784,7.14634146 C7.01960784,3.56996095 4.12262681,0.670731707 0.549019608,0.670731707 L0.549019608,13.6219512 Z" id="Oval-2-Copy-2" transform="translate(3.784314, 7.146341) scale(-1, 1) translate(-3.784314, -7.146341) "></path>
</g>
<ellipse id="Oval-10" fill="#CFDAE6" cx="218.382353" cy="138.685976" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse id="Oval-10-Copy-2" fill="#E0B4B7" opacity="0.35" cx="179.558824" cy="175.381098" rx="1.61764706" ry="1.61890244"></ellipse>
<ellipse id="Oval-10-Copy" fill="#E0B4B7" opacity="0.35" cx="180.098039" cy="102.530488" rx="2.15686275" ry="2.15853659"></ellipse>
<path d="M28.9985381,29.9671598 L171.151018,132.876024" id="Path-11" stroke="#CFDAE6" opacity="0.8"></path>
</g>
<g id="Group-10" opacity="0.799999952" transform="translate(1054.100635, 36.659317) rotate(-11.000000) translate(-1054.100635, -36.659317) translate(1026.600635, 4.659317)">
<ellipse id="Oval-7" stroke="#CFDAE6" stroke-width="0.941176471" cx="43.8135593" cy="32" rx="11.1864407" ry="11.2941176"></ellipse>
<g id="Group-12" transform="translate(34.596774, 23.111111)" fill="#BACAD9">
<ellipse id="Oval-7" opacity="0.45" cx="9.18534718" cy="8.88888889" rx="8.47457627" ry="8.55614973"></ellipse>
<path d="M9.18534718,17.4450386 C13.8657264,17.4450386 17.6599235,13.6143199 17.6599235,8.88888889 C17.6599235,4.16345787 13.8657264,0.332739156 9.18534718,0.332739156 L9.18534718,17.4450386 Z" id="Oval-7"></path>
</g>
<path d="M34.6597385,24.809694 L5.71666084,4.76878945" id="Path-2" stroke="#CFDAE6" stroke-width="0.941176471"></path>
<ellipse id="Oval" stroke="#CFDAE6" stroke-width="0.941176471" cx="3.26271186" cy="3.29411765" rx="3.26271186" ry="3.29411765"></ellipse>
<ellipse id="Oval-Copy" fill="#F7E1AD" cx="2.79661017" cy="61.1764706" rx="2.79661017" ry="2.82352941"></ellipse>
<path d="M34.6312443,39.2922712 L5.06366663,59.785082" id="Path-10" stroke="#CFDAE6" stroke-width="0.941176471"></path>
</g>
<g id="Group-19" opacity="0.33" transform="translate(1282.537219, 446.502867) rotate(-10.000000) translate(-1282.537219, -446.502867) translate(1142.537219, 327.502867)">
<g id="Group-17" transform="translate(141.333539, 104.502742) rotate(275.000000) translate(-141.333539, -104.502742) translate(129.333539, 92.502742)" fill="#BACAD9">
<circle id="Oval-4" opacity="0.45" cx="11.6666667" cy="11.6666667" r="11.6666667"></circle>
<path d="M23.3333333,23.3333333 C23.3333333,16.8900113 18.1099887,11.6666667 11.6666667,11.6666667 C5.22334459,11.6666667 0,16.8900113 0,23.3333333 L23.3333333,23.3333333 Z" id="Oval-4" transform="translate(11.666667, 17.500000) scale(-1, -1) translate(-11.666667, -17.500000) "></path>
</g>
<circle id="Oval-5-Copy-6" fill="#CFDAE6" cx="201.833333" cy="87.5" r="5.83333333"></circle>
<path d="M143.5,88.8126685 L155.070501,17.6038544" id="Path-17" stroke="#BACAD9" stroke-width="1.16666667"></path>
<path d="M17.5,37.3333333 L127.466252,97.6449735" id="Path-18" stroke="#BACAD9" stroke-width="1.16666667"></path>
<polyline id="Path-19" stroke="#CFDAE6" stroke-width="1.16666667" points="143.902597 120.302281 174.935455 231.571342 38.5 147.510847 126.366941 110.833333"></polyline>
<path d="M159.833333,99.7453842 L195.416667,89.25" id="Path-20" stroke="#E0B4B7" stroke-width="1.16666667" opacity="0.6"></path>
<path d="M205.333333,82.1372105 L238.719406,36.1666667" id="Path-24" stroke="#BACAD9" stroke-width="1.16666667"></path>
<path d="M266.723424,132.231988 L207.083333,90.4166667" id="Path-25" stroke="#CFDAE6" stroke-width="1.16666667"></path>
<circle id="Oval-5" fill="#C1D1E0" cx="156.916667" cy="8.75" r="8.75"></circle>
<circle id="Oval-5-Copy-3" fill="#C1D1E0" cx="39.0833333" cy="148.75" r="5.25"></circle>
<circle id="Oval-5-Copy-2" fill-opacity="0.6" fill="#D1DEED" cx="8.75" cy="33.25" r="8.75"></circle>
<circle id="Oval-5-Copy-4" fill-opacity="0.6" fill="#D1DEED" cx="243.833333" cy="30.3333333" r="5.83333333"></circle>
<circle id="Oval-5-Copy-5" fill="#E0B4B7" cx="175.583333" cy="232.75" r="5.25"></circle>
</g>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 KiB

+42
View File
@@ -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>
+135
View File
@@ -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";
}
+219
View File
@@ -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

+4
View File
@@ -0,0 +1,4 @@
import { createApp } from 'vue'
import CaptureObjectList from '@/views/CaptureObjectList.vue'
createApp(CaptureObjectList).mount('#app')
+271
View File
@@ -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);
};
+116
View File
@@ -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;
+147
View File
@@ -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;
}
+109
View File
@@ -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>
+13
View File
@@ -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>
+47
View File
@@ -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>
+36
View File
@@ -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>
+57
View File
@@ -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>
+230
View File
@@ -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>
+10
View File
@@ -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>
+54
View File
@@ -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>
+47
View File
@@ -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>
+51
View File
@@ -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>
+86
View File
@@ -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>
+83
View File
@@ -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;
+175
View File
@@ -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>
+176
View File
@@ -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>
+119
View File
@@ -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>
+84
View File
@@ -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>
+58
View File
@@ -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>
+117
View File
@@ -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>
+152
View File
@@ -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>
+93
View File
@@ -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>
+99
View File
@@ -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>
+154
View File
@@ -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>
+69
View File
@@ -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>
+371
View File
@@ -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 }
}
}
}
+113
View File
@@ -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 };
// }
// }
}
+216
View File
@@ -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;
}
}
+124
View File
@@ -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;
// }
}
+242
View File
@@ -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;
// }
}
+54
View File
@@ -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 = "";
}
+10
View File
@@ -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';
+4
View File
@@ -0,0 +1,4 @@
import { createApp } from 'vue'
import DeviceList from '@/views/DeviceList.vue'
createApp(DeviceList).mount('#app')
+10
View File
@@ -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')
+56
View File
@@ -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;
+4
View File
@@ -0,0 +1,4 @@
import { createApp } from 'vue'
import Sett from '@/views/Sett.vue'
createApp(Sett).mount('#app')
+24
View File
@@ -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;
}
},
});
+4
View File
@@ -0,0 +1,4 @@
import { createApp } from 'vue'
import SystemInfo from '@/views/SystemInfo.vue'
createApp(SystemInfo).mount('#app')
+4
View File
@@ -0,0 +1,4 @@
import { createApp } from 'vue'
import SystemLog from '@/views/SystemLog.vue'
createApp(SystemLog).mount('#app')
+50
View File
@@ -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 ));
+15
View File
@@ -0,0 +1,15 @@
/** 图片预览图片索引切换类型值 */
declare type TImgPreviewIdx = -1 | 1;
/**
*
* , 0, 1, 2, 3, 4|, 5, 6,789101112 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;
+4
View File
@@ -0,0 +1,4 @@
import { createApp } from 'vue'
import UpServer from '@/views/UpServer.vue'
createApp(UpServer).mount('#app')
+29
View File
@@ -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>
+103
View File
@@ -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>
+353
View File
@@ -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>
+142
View File
@@ -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>
+118
View File
@@ -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>
+166
View File
@@ -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>
+163
View File
@@ -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>
+166
View File
@@ -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>
+81
View File
@@ -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>
+36
View File
@@ -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";
}
+23
View File
@@ -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;
}
+162
View File
@@ -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) {
// tokencookie 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>
+110
View File
@@ -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>
+99
View File
@@ -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>
+311
View File
@@ -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 };
})
// errortrue,
// status200|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>
+6
View File
@@ -0,0 +1,6 @@
<template>
<SystemInfoFormDetail></SystemInfoFormDetail>
</template>
<script setup lang="ts">
import SystemInfoFormDetail from "@/containers/SystemInfoFormDetail.vue"
</script>
+17
View File
@@ -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>
+109
View File
@@ -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>
+6
View File
@@ -0,0 +1,6 @@
<template>
<up-server />
</template>
<script setup lang="ts">
import UpServer from "@/containers/UpServer.vue"
</script>
+41
View File
@@ -0,0 +1,41 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": [
"src/*"
]
},
"target": "esnext",
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": [
"esnext",
"dom"
],
"skipLibCheck": true,
"types": [
"element-plus/global"
],
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
"references": [
{
"path": "./tsconfig.node.json"
}
]
}
+10
View File
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"composite": true,
"module": "esnext",
"moduleResolution": "node"
},
"include": [
"vite.config.ts"
]
}
+62
View File
@@ -0,0 +1,62 @@
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import ElementPlus from 'unplugin-element-plus/vite'
import { resolve } from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
ElementPlus({
// options
})
],
server: {
host: "0.0.0.0",
proxy: {
"^/api": {
target: "http://localhost:4873/",
changeOrigin: true,
},
"^/set/log/level": {
target: "http://192.168.1.89:4873/",
changeOrigin: true,
},
"^/cache/image/": {
target: "http://192.168.1.102:1221/",
changeOrigin: true,
},
"^/get/service/": {
target: "http://192.168.1.102:1221/",
changeOrigin: true,
},
}
},
resolve: {
alias: {
"@": resolve(__dirname, "src"),
},
},
build: {
cssCodeSplit: true, // 如果设置为false,整个项目中的所有 CSS 将被提取到一个 CSS 文件中
sourcemap: false, // 构建后是否生成 source map 文件。如果为 true,将会创建一个独立的 source map 文件
target: 'modules', // 设置最终构建的浏览器兼容目标。默认值是一个 Vite 特有的值——'modules' 还可设置为 'es2015' 'es2016'等
chunkSizeWarningLimit: 550, // 单位kb 打包后文件大小警告的限制 (文件大于此此值会出现警告)
assetsInlineLimit: 4096, // 单位字节(1024等于1kb) 小于此阈值的导入或引用资源将内联为 base64 编码,以避免额外的 http 请求。设置为 0 可以完全禁用此项。
minify: 'terser', // 'terser' 相对较慢,但大多数情况下构建后的文件体积更小。'esbuild' 最小化混淆更快但构建后的文件相对更大。
terserOptions: {
compress: {
drop_console: true, // 生产环境去除console
drop_debugger: true // 生产环境去除debugger
}
}
}
})
+2117
View File
File diff suppressed because it is too large Load Diff