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
+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 = "";
}