Browse Source

Merge remote-tracking branch 'origin/master'

master
修宁 6 months ago
parent
commit
08977be70b
  1. 7
      package.json
  2. 26
      pnpm-lock.yaml
  3. 1
      src/assets/images/rotate.svg
  4. 10
      src/components/data-form/DataForm.vue
  5. 2
      src/components/data-form/DataFormTypes.ts
  6. 61
      src/config.ts
  7. 24
      src/currentUser.ts
  8. 263
      src/editor/CatalogDefine.vue
  9. 5
      src/editor/ModelMain.vue
  10. 39
      src/main.ts
  11. 146
      src/router/index.ts
  12. 74
      src/views/Header.vue
  13. 152
      src/views/HomeView.vue
  14. 181
      src/views/Login.vue
  15. 85
      src/views/Sidebar.vue
  16. 58
      src/views/dashboard/EChartWrapper.vue
  17. 281
      src/views/dashboard/index.vue
  18. 12
      src/views/device/chargers.vue
  19. 12
      src/views/device/locations.vue
  20. 12
      src/views/device/points.vue
  21. 12
      src/views/device/vehicles.vue
  22. 12
      src/views/inventory/account.vue
  23. 12
      src/views/inventory/query.vue
  24. 12
      src/views/log/device.vue
  25. 12
      src/views/log/upstream.vue
  26. 12
      src/views/modelingSimulation/index.vue
  27. 12
      src/views/taskManagement/automatedPresentation.vue
  28. 12
      src/views/taskManagement/taskQuery.vue
  29. 12
      src/views/user/roles.vue
  30. 12
      src/views/user/users.vue

7
package.json

@ -13,11 +13,12 @@
"format": "prettier --write src/" "format": "prettier --write src/"
}, },
"dependencies": { "dependencies": {
"@vueuse/core": "^13.2.0" "@vueuse/core": "^13.2.0",
"echarts": "^5.6.0"
}, },
"devDependencies": { "devDependencies": {
"@ease-forge/runtime": "^1.0.12", "@ease-forge/runtime": "^1.0.15",
"@ease-forge/shared": "^1.0.12", "@ease-forge/shared": "^1.0.15",
"@element-plus/icons-vue": "^2.3.1", "@element-plus/icons-vue": "^2.3.1",
"@guolao/vue-monaco-editor": "^1.5.5", "@guolao/vue-monaco-editor": "^1.5.5",
"@rolldown/pluginutils": "1.0.0-beta.8-commit.56abf23", "@rolldown/pluginutils": "1.0.0-beta.8-commit.56abf23",

26
pnpm-lock.yaml

@ -13,11 +13,11 @@ importers:
version: 13.2.0(vue@3.5.14(typescript@5.8.3)) version: 13.2.0(vue@3.5.14(typescript@5.8.3))
devDependencies: devDependencies:
'@ease-forge/runtime': '@ease-forge/runtime':
specifier: ^1.0.12 specifier: ^1.0.15
version: 1.0.12(typescript@5.8.3) version: 1.0.15(typescript@5.8.3)
'@ease-forge/shared': '@ease-forge/shared':
specifier: ^1.0.12 specifier: ^1.0.15
version: 1.0.12 version: 1.0.15
'@element-plus/icons-vue': '@element-plus/icons-vue':
specifier: ^2.3.1 specifier: ^2.3.1
version: 2.3.1(vue@3.5.14(typescript@5.8.3)) version: 2.3.1(vue@3.5.14(typescript@5.8.3))
@ -327,11 +327,11 @@ packages:
'@dimforge/rapier3d-compat@0.12.0': '@dimforge/rapier3d-compat@0.12.0':
resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==} resolution: {integrity: sha512-uekIGetywIgopfD97oDL5PfeezkFpNhwlzlaEYNOA0N6ghdsOvh/HYjSMek5Q2O1PYvRSDFcqFVJl4r4ZBwOow==}
'@ease-forge/runtime@1.0.12': '@ease-forge/runtime@1.0.15':
resolution: {integrity: sha512-L8X+uLb4/uxJrk9N7FRA5qCc6mXwQWGyQA1DN0FLF4Yqc4j/ErOfnWpmy45EkDqSbDNF1PS5CGMBTXW9dmw2XQ==} resolution: {integrity: sha512-PPVRGNRk4+7rGARL56772crYub2YGl86nnKuIUSH6fha44jpXJXAqOqrXlZ/mn7YcpQo5GNofKvVkGnTULA5DA==}
'@ease-forge/shared@1.0.12': '@ease-forge/shared@1.0.15':
resolution: {integrity: sha512-+in1kiZnVP/QETMGC6i7znJKBs6WUMz8f9yHmGlmWvunTf6Ap7B60fvK7W12NlPpAnqH8WIRtLR5cSmgNeUQsQ==} resolution: {integrity: sha512-R2FcwWCyrCeKN+f9iyNpBkvO9HNXpfX3ssMbGVTpKG9vPd+rYOK6xyBHgnjnVCYR8PptRAAtgY4cZkSMiohWrQ==}
'@element-plus/icons-vue@2.3.1': '@element-plus/icons-vue@2.3.1':
resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==} resolution: {integrity: sha512-XxVUZv48RZAd87ucGS48jPf6pKu0yV5UCg9f4FFwtrYxXOwWuVJo6wOvSLKEoMQKjv8GsX/mhP6UsC1lRwbUWg==}
@ -1199,7 +1199,7 @@ packages:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
gsap@3.13.0: gsap@3.13.0:
resolution: {integrity: sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==, tarball: https://registry.npmmirror.com/gsap/-/gsap-3.13.0.tgz} resolution: {integrity: sha512-QL7MJ2WMjm1PHWsoFrAQH/J8wUeqZvMtHO58qdekHpCfhvhSL4gSiz6vJf5EeMP0LOn3ZCprL2ki/gjED8ghVw==}
has-symbols@1.1.0: has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
@ -1695,7 +1695,7 @@ packages:
engines: {node: '>=16'} engines: {node: '>=16'}
three-csg-ts@3.2.0: three-csg-ts@3.2.0:
resolution: {integrity: sha512-oTYg8kdal6qgHDbso/6VzA12Udf2ic2uXhf0XlJzuSP+Gs0OUR5gTHSZ7GotAE+M/QcVlw41eOwiWZVnJG5/8w==, tarball: https://registry.npmmirror.com/three-csg-ts/-/three-csg-ts-3.2.0.tgz} resolution: {integrity: sha512-oTYg8kdal6qgHDbso/6VzA12Udf2ic2uXhf0XlJzuSP+Gs0OUR5gTHSZ7GotAE+M/QcVlw41eOwiWZVnJG5/8w==}
peerDependencies: peerDependencies:
'@types/three': '>= 0.154.0' '@types/three': '>= 0.154.0'
three: '>= 0.154.0' three: '>= 0.154.0'
@ -2100,9 +2100,9 @@ snapshots:
'@dimforge/rapier3d-compat@0.12.0': {} '@dimforge/rapier3d-compat@0.12.0': {}
'@ease-forge/runtime@1.0.12(typescript@5.8.3)': '@ease-forge/runtime@1.0.15(typescript@5.8.3)':
dependencies: dependencies:
'@ease-forge/shared': 1.0.12 '@ease-forge/shared': 1.0.15
'@vue/shared': 3.5.13 '@vue/shared': 3.5.13
json5: 2.2.3 json5: 2.2.3
lodash: 4.17.21 lodash: 4.17.21
@ -2112,7 +2112,7 @@ snapshots:
- debug - debug
- typescript - typescript
'@ease-forge/shared@1.0.12': '@ease-forge/shared@1.0.15':
dependencies: dependencies:
axios: 1.9.0 axios: 1.9.0
csstype: 3.1.3 csstype: 3.1.3

1
src/assets/images/rotate.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1750385271646" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16160" xmlns:xlink="http://www.w3.org/1999/xlink" width="128" height="128"><path d="M875.638497 150.923785a33.27639 33.27639 0 0 1-0.204778 47.150084 33.583556 33.583556 0 0 1-47.354862-0.204778A444.316592 444.316592 0 0 0 512.055546 66.708922c-104.43666899 0-203.293143 35.938501-280.852728 99.317224l-35.580139 32.81564a33.583556 33.583556 0 0 1-47.303668-1.791806 33.27639 33.27639 0 0 1 1.791806-47.09889l37.064778-34.146695A511.176537 511.176537 0 0 1 512.055546 0.00256a511.43251 511.43251 0 0 1 363.582951 150.921225z m-863.49671 248.497838a33.481167 33.481167 0 0 1 65.426501 14.385639 443.139119 443.139119 0 0 0 299.129144 518.394954A33.327584 33.327584 0 1 1 356.424432 995.734523a509.58951 509.58951 0 0 1-344.180256-596.3129zM748.625078 885.359299a443.036731 443.036731 0 0 0 198.788032-467.456481 33.481167 33.481167 0 0 1 65.52889-13.771306A509.999065 509.999065 0 0 1 744.375939 964.403522C695.17807701 992.560467 592.584409 1028.191801 516.663046 1023.379523c-8.549472 1.689417-17.96925 0-25.597223-5.938555-6.4505-5.119445-9.982917-12.081889-11.10919401-20.273001-0.255972-2.713306 0.409556-5.221833 1.02388901-8.037528a22.832723 22.832723 0 0 1 7.781555-12.133083L682.737827 760.240074a28.208139 28.208139 0 0 1 29.999945-6.91125 30.204723 30.204723 0 0 1 20.836139 23.703028l15.051167 108.378641z m4.965861-785.322793c2.969278 0.46075 7.269611-31.740556 34.402668-17.918056 48.225168 24.624528 138.225003 97.781391 174.93142 168.736892a31.331001 31.331001 0 0 1 8.037528 24.931695c-1.023889 8.191111-5.221833 14.846389-11.62114 19.965834-2.20136101 1.638222-4.658695 2.354944-7.423194 3.276444a22.832723 22.832723 0 0 1-14.385639-0.409555l-285.7162-54.675668c-10.392472-1.945389-18.839556-10.750834-21.399278-22.116a30.204723 30.204723 0 0 1 9.573361-30.102334l113.651669-91.689252z m-716.671041 436.688619c-1.126278-2.815694-30.921445 10.085306-33.020417-20.273-3.737195-54.061334 12.542639-168.890475 54.522084-236.825504a31.331001 31.331001 0 0 1 17.201334-19.812251 29.999945 29.999945 0 0 1 23.13988901-0.307167c2.559722 1.023889 4.402722 2.815694 6.65527799 4.709889a22.832723 22.832723 0 0 1 7.013639 12.54264l100.341113 273.071171a28.208139 28.208139 0 0 1-7.986333 29.692779 30.204723 30.204723 0 0 1-30.716668 7.320805l-137.149919-50.170556z" fill="#337ECC" p-id="16161"></path></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

10
src/components/data-form/DataForm.vue

@ -104,10 +104,10 @@ function toDataFormItems(formFields?: Array<FormField>, formData?: FormData) {
* @param formData 表单数据 * @param formData 表单数据
*/ */
function toDataFormItem(formField: FormField, formData?: FormData) { function toDataFormItem(formField: FormField, formData?: FormData) {
const { dataPath, widthCount, label, input, inputProps, format, transform, extInputs, hidden, watchValues, rawProps } = formField; const { dataPath, widthCount, label, inputRef, input, inputProps, format, transform, extInputs, hidden, watchValues, rawProps } = formField;
const item: DataFormItem = { const item: DataFormItem = {
widthCount: widthCount ?? 1, widthCount: widthCount ?? 1,
inputRef: dataPath?.replaceAll(/[.\[\]]/g, '_') + '_' + lodash.uniqueId(), inputRef: inputRef ?? dataPath?.replaceAll(/[.\[\]]/g, '_') + '_' + lodash.uniqueId(),
formItemProps: { ...rawProps }, formItemProps: { ...rawProps },
dataPath, dataPath,
format, format,
@ -115,9 +115,7 @@ function toDataFormItem(formField: FormField, formData?: FormData) {
hidden, hidden,
}; };
if (dataPath) { if (dataPath) {
item.formItemProps.name = dataPathToNamePath(dataPath); item.formItemProps.prop = dataPathToNamePath(dataPath);
} else {
item.formItemProps.autoLink = false;
} }
if (dataPath && Typeof.isFunction(label)) { if (dataPath && Typeof.isFunction(label)) {
const dataValue = Expression.getKeyPathValue(dataPath, formData); const dataValue = Expression.getKeyPathValue(dataPath, formData);
@ -485,7 +483,7 @@ export type {
} }
.data-form-item-no-label :deep(> .el-form-item__content), .data-form-item-no-label :deep(> .el-form-item__content),
.data-form-item-ext-input :deep(> .el-form-item__content){ .data-form-item-ext-input :deep(> .el-form-item__content) {
margin-left: 0 !important; margin-left: 0 !important;
} }

2
src/components/data-form/DataFormTypes.ts

@ -111,6 +111,8 @@ interface FormField {
label?: Label; label?: Label;
/** 字段输入组件,组件名或者组件对象 */ /** 字段输入组件,组件名或者组件对象 */
input?: any; input?: any;
/** 输入组件ref */
inputRef?: string;
/** 字段输入组件props */ /** 字段输入组件props */
inputProps?: FormInputBaseProps; inputProps?: FormInputBaseProps;
/** 表单数据绑定到表单组件上时的格式化操作 */ /** 表单数据绑定到表单组件上时的格式化操作 */

61
src/config.ts

@ -0,0 +1,61 @@
import type { InternalAxiosRequestConfig } from "axios";
import { Constant, initGlobalConfig } from "@ease-forge/shared";
import { initGlobalConfigWithRuntime } from "@ease-forge/runtime";
import router from "@/router";
function globalConfig() {
window.globalConfig.customAxios = axiosInstance => {
// 全局请求拦截
axiosInstance.interceptors.request.clear();
axiosInstance.interceptors.request.use(
(request: InternalAxiosRequestConfig) => request,
(error: any) => {
const err: AxiosInterceptorError = {
rawError: error,
title: "系统错误",
message: "发送请求给服务端失败,请检查电脑网络,再重试",
status: -1,
};
return Promise.reject(err);
},
);
// 全局拦截配置
axiosInstance.interceptors.response.clear();
axiosInstance.interceptors.response.use(
response => response,
(error: any) => {
const { response } = error;
const err: AxiosInterceptorError = {
rawError: error,
status: response?.status ?? -1,
title: "系统错误",
message: "",
};
if (!error || !response) {
err.message = "请求服务端异常";
} else if (response?.status === 401) {
err.title = "当前用户未登录";
err.message = "当前用户未登录,请先登录系统";
router.push({ name: "login" }).finally();
} else {
err.title = "操作失败";
const { data: { message, validMessageList } } = response;
if (validMessageList) {
err.message = "请求参数校验失败";
} else if (message) {
err.message = message ?? Constant.defHttpErrorMsg[response.status] ?? "服务器异常";
}
}
system.msg(err.message);
return Promise.reject(err);
},
);
return axiosInstance;
};
initGlobalConfig();
initGlobalConfigWithRuntime();
}
export {
globalConfig,
}

24
src/currentUser.ts

@ -0,0 +1,24 @@
import lodash from "lodash";
import { Request } from "@ease-forge/shared";
async function getCurrentUser() {
return Request.request.post("/api/current_user").then(res => {
const userInfo = res.userInfo;
const { loginName, userId, userName } = userInfo;
window.globalConfig.user = {
uid: lodash.toString(userId),
loginName: loginName,
nickname: userName,
};
window.globalConfig.security.roles.length = 0;
window.globalConfig.security.permissions.length = 0;
window.globalConfig.security.roles.push(...res.roles);
window.globalConfig.security.permissions.push(...res.permissions);
}).catch(err => {
console.log("未登录", err);
});
}
export {
getCurrentUser
}

263
src/editor/CatalogDefine.vue

@ -0,0 +1,263 @@
<script setup lang="ts">
import { computed, createVNode, reactive, useTemplateRef } from "vue";
import { ElButton, ElSpace, ElTree } from "element-plus";
import YvSrcEditor from "@/components/YvSrcEditor.vue";
import DataForm from "@/components/data-form/DataForm.vue";
import lodash from "lodash";
defineOptions({
name: 'CatalogDefine',
});
//
// const emit = defineEmits<{
// /** */
// "event01": [param01: string];
// }>();
// Props
interface CatalogDefineProps {
}
// props
const props = withDefaults(defineProps<CatalogDefineProps>(), {});
// State
interface CatalogDefineState {
// forceUpdateForCatalog: number;
}
// state
const state = reactive<CatalogDefineState>({
// forceUpdateForCatalog: Number.MIN_VALUE,
});
// Data
interface CatalogDefineData {
}
//
const data: CatalogDefineData = {};
const tree = useTemplateRef<InstanceType<typeof ElTree>>("treeRef");
const worldModel = computed(() => window['worldModel']);
const catalog = computed<Array<any>>(() => {
// state.forceUpdateForCatalog;
return worldModel.value?.state?.catalog;
});
const catalogTree = computed(() => {
const array = catalog.value;
const tree: Array<any> = [];
if (array) {
for (let item of array) {
const node: any = {
id: item.label,
label: item.label,
data: item,
};
tree.push(node);
if (item.items && item.items.length > 0) {
node.children = [];
for (let row of item.items) {
const child = {
pid: item.label,
id: `${item.label}_${row.catalogCode}`,
label: row.label,
data: row,
};
node.children.push(child);
}
}
}
}
return tree;
});
const catalogJson = computed(() => {
if (catalog.value) return JSON.stringify(catalog.value, null, 4);
return "";
});
function addCatalog() {
const data = {
label: "",
};
system.showDialog(createVNode(DataForm, {
style: {
paddingRight: "12px",
},
data: data,
formFields: [
{
dataPath: 'label', label: '目录名称', input: 'Input',
inputProps: {
placeholder: '目录名称',
},
},
],
columnCount: 1,
labelWidth: "80px",
}), {
title: '添加目录',
width: 480,
height: 150,
showClose: true,
showMax: false,
showCancelButton: true,
showOkButton: true,
okButtonText: "确定",
cancelButtonText: "取消",
}).then(() => {
const label = lodash.trim(data.label);
if (!label) {
system.msg("目录名称不能为空");
return;
}
catalog.value.push({ label, items: [] });
}).finally();
}
function addItem() {
const node = tree.value?.getCurrentNode();
const catalogData = node?.data;
if (!catalogData?.items) {
system.msg("必须先选择一个目录");
return;
}
// console.log("node", node);
const data = {
label: "",
catalogCode: "",
};
system.showDialog(createVNode(DataForm, {
style: {
paddingRight: "12px",
},
data: data,
formFields: [
{
dataPath: 'catalogCode', label: '楼层编码', input: 'Input',
inputProps: {
placeholder: '楼层唯一编码',
},
},
{
dataPath: 'label', label: '楼层名称', input: 'Input',
inputProps: {
placeholder: '楼层名称',
},
},
],
columnCount: 1,
labelWidth: "80px",
}), {
title: '添加楼层',
width: 480,
height: 150,
showClose: true,
showMax: false,
showCancelButton: true,
showOkButton: true,
okButtonText: "确定",
cancelButtonText: "取消",
}).then(() => {
const catalogCode = lodash.trim(data.catalogCode);
const label = lodash.trim(data.label);
if (!catalogCode) {
system.msg("楼层编码不能为空");
return;
}
if (!label) {
system.msg("楼层名称不能为空");
return;
}
catalogData.items.push({ label, catalogCode });
}).finally();
}
function del() {
const node = tree.value?.getCurrentNode();
if (!node) {
system.msg("必须先选择一个节点");
return;
}
// console.log("node", node);
const nodeData = node.data;
if (node.pid) {
let index = catalog.value.findIndex(item => item.label === node.pid);
if (index >= 0) {
const catalogData = catalog.value[index];
index = catalogData.items.findIndex(item => item.catalogCode === nodeData.catalogCode);
if (index >= 0) {
catalogData.items.splice(index, 1);
// state.forceUpdateForCatalog++;
}
}
} else {
const index = catalog.value.findIndex(item => item.label === nodeData.label);
if (index >= 0) {
catalog.value.splice(index, 1);
}
}
}
interface CatalogDefineExpose {
state: CatalogDefineState;
data: CatalogDefineData;
}
const expose: CatalogDefineExpose = {
state,
data,
};
//
defineExpose(expose);
export type {
CatalogDefineProps,
CatalogDefineState,
}
</script>
<template>
<div class="flex-row-container root">
<div class="flex-item-fixed flex-column-container left">
<ElSpace class="flex-item-fixed tools-top">
<ElButton @click="addCatalog" :disabled="!catalog">添加目录</ElButton>
<ElButton @click="addItem" :disabled="!catalog">添加楼层</ElButton>
<ElButton @click="del" :disabled="!catalog">删除</ElButton>
</ElSpace>
<div class="catalog-tree">
<ElTree
ref="treeRef"
:data="catalogTree"
nodeKey="id"
:expandOnClickNode="false"
:highlightCurrent="true"
:defaultExpandAll="true"
/>
</div>
</div>
<div class="flex-item-fill">
<YvSrcEditor ref="editorRef" language="json" :modelValue="catalogJson"/>
</div>
</div>
</template>
<style scoped>
.root {
height: 100%;
}
.left {
width: 300px;
border-right: 1px solid #ece2e2;
}
.tools-top {
padding: 8px;
border-bottom: 1px solid #ece2e2;
}
.catalog-tree {
}
</style>

5
src/editor/ModelMain.vue

@ -60,7 +60,7 @@
<Model3DViewer /> <Model3DViewer />
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="楼层定义" name="ModelFile" lazy> <el-tab-pane label="楼层定义" name="ModelFile" lazy>
<el-empty description="暂无数据" /> <CatalogDefine/>
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="模型定义" name="ModelDataSet" lazy> <el-tab-pane label="模型定义" name="ModelDataSet" lazy>
<el-empty description="暂无数据" /> <el-empty description="暂无数据" />
@ -112,13 +112,14 @@ import { getAllWidget, getWidgetByName, getWidgetBySide } from '@/runtime/Define
import Model2DEditor from './Model2DEditor.vue' import Model2DEditor from './Model2DEditor.vue'
import Model3DViewer from './Model3DViewer.vue' import Model3DViewer from './Model3DViewer.vue'
import { normalizeShortKey } from '@/utils/webutils.ts' import { normalizeShortKey } from '@/utils/webutils.ts'
import CatalogDefine from './CatalogDefine.vue'
import Logo from '@/assets/images/logo.png' import Logo from '@/assets/images/logo.png'
import './ModelMain.less' import './ModelMain.less'
import EventBus from '@/runtime/EventBus.js' import EventBus from '@/runtime/EventBus.js'
export default { export default {
components: { Model2DEditor, Model3DViewer, Split, SplitArea }, components: { Model2DEditor, Model3DViewer, Split, SplitArea, CatalogDefine },
created() { created() {
ModelMainInit() ModelMainInit()
}, },

39
src/main.ts

@ -1,34 +1,31 @@
import { createApp } from 'vue' import { createApp } from 'vue'
import { createPinia } from 'pinia' import { createPinia } from 'pinia'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import * as webIndex from '@/components/webindex' import * as webIndex from '@/components/webindex'
import { directive, menusEvent, Vue3Menus } from 'vue3-menus' import { directive, menusEvent, Vue3Menus } from 'vue3-menus'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import { initGlobalConfig } from "@ease-forge/shared"; import { globalConfig } from "@/config.ts";
import { initGlobalConfigWithRuntime } from "@ease-forge/runtime";
import System from '@/runtime/System' import System from '@/runtime/System'
import { getCurrentUser } from "@/currentUser.ts";
import 'ag-grid-community/styles/ag-grid.css' import 'ag-grid-community/styles/ag-grid.css'
import 'ag-grid-community/styles/ag-theme-alpine.css' import 'ag-grid-community/styles/ag-theme-alpine.css'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import './main.less' import './main.less'
initGlobalConfig(); async function main() {
initGlobalConfigWithRuntime(); const app = createApp(App)
app.use(createPinia())
const app = createApp(App) app.use(ElementPlus)
app.component('vue3-menus', Vue3Menus)
app.use(createPinia()) app.directive('menus', directive)
app.use(ElementPlus) app.config.globalProperties.$menusEvent = menusEvent
app.component('vue3-menus', Vue3Menus) window['system'] = new System(app)
app.directive('menus', directive) app.use(router)
app.config.globalProperties.$menusEvent = menusEvent app.use(webIndex)
app.mount('#app')
window['system'] = new System(app) globalConfig();
await getCurrentUser();
app.use(router) }
app.use(webIndex)
main().finally();
app.mount('#app')

146
src/router/index.ts

@ -7,7 +7,151 @@ const router = createRouter({
path: '/', path: '/',
name: 'home', name: 'home',
// 自动引导到 /editor // 自动引导到 /editor
redirect: '/editor' component: () => import('@/views/HomeView.vue'),
children: [
{
path: '/dashboard',
name: 'dashboard',
meta: {
title: '仪表盘'
},
component: () => import('@/views/dashboard/index.vue')
},
{
path: '/modelingSimulation',
name: 'modelingSimulation',
meta: {
title: '建模仿真控制平台'
},
component: () => import('@/views/modelingSimulation/index.vue')
},
{
path: '/taskManagement',
name: 'taskManagement',
children:[
{
path: '/taskQuery',
name: 'taskQuery',
meta: {
title: '任务查询'
},
component: () => import('@/views/taskManagement/taskQuery.vue')
},
{
path: '/automatedPresentation',
name: 'automatedPresentation',
meta: {
title: '自动演示管理'
},
component: () => import('@/views/taskManagement/automatedPresentation.vue')
}
]
},
{
path: '/log',
name: 'log',
children:[
{
path: '/upstream',
name: 'upstream',
meta: {
title: '上游接口日志'
},
component: () => import('@/views/log/upstream.vue')
},
{
path: '/device',
name: 'device',
meta: {
title: '设备报文日志'
},
component: () => import('@/views/log/device.vue')
}
]
},
{
path: '/device',
name: 'device',
children:[
{
path: '/points',
name: 'points',
meta: {
title: '点位管理'
},
component: () => import('@/views/device/points.vue')
},
{
path: '/locations',
name: 'locations',
meta: {
title: '货位管理'
},
component: () => import('@/views/device/locations.vue')
},
{
path: '/vehicles',
name: 'vehicles',
meta: {
title: '车辆管理'
},
component: () => import('@/views/device/vehicles.vue')
},
{
path: '/chargers',
name: 'chargers',
meta: {
title: '充电位管理'
},
component: () => import('@/views/device/chargers.vue')
}
]
},
{
path: '/inventory',
name: 'inventory',
children:[
{
path: '/query',
name: 'query',
meta: {
title: '库存查询'
},
component: () => import('@/views/inventory/query.vue')
},
{
path: '/account',
name: 'account',
meta: {
title: '帐页查询'
},
component: () => import('@/views/inventory/account.vue')
}
]
},
{
path: '/user',
name: 'user',
children:[
{
path: '/users',
name: 'users',
meta: {
title: '用户管理'
},
component: () => import('@/views/user/users.vue')
},
{
path: '/roles',
name: 'roles',
meta: {
title: '角色管理'
},
component: () => import('@/views/user/roles.vue')
}
]
}
]
}, },
{ {
path: '/tp', path: '/tp',

74
src/views/Header.vue

@ -0,0 +1,74 @@
<template>
<div class="header-wrapper">
<div class="left">
<div class="logo"><img :src="Logo" alt="" style="height: 30px;width: 169px"></div>
<span class="menu-icon" @click="handleToggle">
<!-- 使用 props.collapsed -->
<component v-if="!props.collapsed" :is="renderIcon('antd MenuFoldOutlined')"></component>
<component v-else :is="renderIcon('antd MenuUnfoldOutlined')"></component>
</span>
</div>
<div class="user">
<span>
<component :is="renderIcon('element User')"></component>
</span>
</div>
</div>
</template>
<script setup>
import {ref} from 'vue'
import { renderIcon } from '@/utils/webutils.js'
import Logo from '@/assets/images/logo.png'
// props
const props = defineProps({
isMobile: Boolean,
collapsed: Boolean //
})
const emit = defineEmits(['toggle-collapse'])
function handleToggle() {
emit('toggle-collapse')
}
</script>
<style lang="less">
.header-wrapper {
display: flex;
flex-direction: row;
overflow: hidden;
justify-content: space-between;
align-items: center;
height: 100%;
.left{
flex:1;
display: flex;
flex-direction: row;
.logo {
display: flex;
align-items: center;
margin: 0 20px 0 10px;
}
.menu-icon{
display: inline-flex;
padding:10px;
cursor: pointer;
.el-icon{
font-size: 20px;
color:#fff;
}
}
}
.user {
display: flex;
flex-direction: row;
align-items: center;
& > span {
display: inline-flex;
padding: 5px;
background: #f4c521;
border-radius: 15px;
color: #fff;
}
}
}
</style>

152
src/views/HomeView.vue

@ -1,50 +1,126 @@
<template> <template>
<div id="sence" ref="threeDomElement" tabindex="1"></div> <div class="layout">
<el-container style="height: 100%;overflow: hidden;">
<!-- 头部 -->
<el-header class="header">
<Header @toggle-collapse="toggleCollapse" :collapsed="collapsed" :is-mobile="isMobile" />
</el-header>
<el-container style="height: 100%;overflow: hidden;">
<!-- 侧边栏 -->
<el-aside v-show="!isMobile || !collapsed" :width="collapsed ? '64px' : '200px'" class="sidebar">
<Sidebar :collapsed="collapsed" />
</el-aside>
<!-- 内容 -->
<el-main class="main">
<el-breadcrumb separator="/">
<el-breadcrumb-item>
{{route.meta?.title}}
</el-breadcrumb-item>
</el-breadcrumb>
<div class="content">
<router-view />
</div>
</el-main>
</el-container>
</el-container>
</div>
<!-- 移动端遮罩层 -->
<div v-if="isMobile" v-show="!collapsed" class="mask" @click="toggleCollapse"></div>
</template> </template>
<script setup>
import { onMounted, ref } from 'vue'
import * as THREE from 'three'
import { GUI } from 'dat.gui'
import _ from 'lodash'
const threeDomElement = ref(null)
onMounted(() => {
// lodash <script setup>
const arr = [1, 2, 3] import { ref, computed, onMounted} from 'vue'
console.log(_.reverse(arr)) import {useRoute} from "vue-router";
import Sidebar from './Sidebar.vue'
import Header from './Header.vue'
const route = useRoute()
console.log(route.meta?.title)
const collapsed = ref(false)
const isMobile = ref(false)
// Three.js // /
const scene = new THREE.Scene() function toggleCollapse() {
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000) collapsed.value = !collapsed.value
}
const renderer = new THREE.WebGLRenderer() //
threeDomElement.value.appendChild(renderer.domElement) function checkIsMobile() {
renderer.setSize(window.innerWidth, window.innerHeight) isMobile.value = window.innerWidth < 768
if (isMobile.value) {
collapsed.value = true //
}
}
const geometry = new THREE.BoxGeometry() onMounted(() => {
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 }) checkIsMobile()
const cube = new THREE.Mesh(geometry, material) window.addEventListener('resize', checkIsMobile)
scene.add(cube) })
camera.position.z = 5 </script>
function animate() { <style scoped lang="less">
requestAnimationFrame(animate) .layout {
cube.rotation.x += 0.01 height: 100vh;
cube.rotation.y += 0.01 }
renderer.render(scene, camera) .header {
height: 50px;
background: #545c64;
flex-shrink: 0;
padding:0 10px;
}
.sidebar {
transition: width 0.3s ease;
}
.main {
padding: 10px;
background: #f6f7fb;
display: flex;
flex-direction: column;
overflow: hidden;
width: 100%;
height: 100%;
.el-breadcrumb{
margin:0 0 10px 10px;
} }
&>.content{
flex: 1;
overflow: hidden;
}
}
.mask {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,.4);
z-index: 999;
}
@media (max-width: 768px) {
.layout {
flex-direction: column;
}
.el-aside {
position: fixed;
top: 0;
left: 0;
height: 100%;
z-index: 1000;
transform: translateX(0);
transition: transform 0.6s ease;
background: #fff;
animate() &.open {
transform: translateX(0);
}
}
// Dat.GUI .el-aside[style*='transform: translateX(0px)'] {
const gui = new GUI() transform: translateX(0);
const cubeFolder = gui.addFolder('Cube') }
cubeFolder.add(cube.rotation, 'x', 0, Math.PI * 2)
cubeFolder.add(cube.rotation, 'y', 0, Math.PI * 2)
cubeFolder.open()
})
</script> .el-aside[style*='transform: translateX(-100%)'] {
transform: translateX(-100%);
}
}
</style>

181
src/views/Login.vue

@ -0,0 +1,181 @@
<script setup lang="ts">
import { markRaw, reactive, useTemplateRef } from "vue";
import { useRouter } from "vue-router";
import { ElButton, type FormRules } from "element-plus";
import { Lock, User } from "@element-plus/icons-vue";
import { Request } from "@ease-forge/shared";
import DataForm from "../components/data-form/DataForm.vue";
import { type FormField } from "../components/data-form/DataFormTypes.ts";
import { getCurrentUser } from "@/currentUser.ts";
defineOptions({
name: 'Login',
});
// Props
interface LoginProps {
}
// props
const props = withDefaults(defineProps<LoginProps>(), {});
// State
interface LoginState {
loginName?: string;
password?: string;
loading?: boolean;
errMsg?: string;
}
// state
const state = reactive<LoginState>({});
// Data
interface LoginData {
formFields: Array<FormField>;
rules: FormRules,
}
//
const data: LoginData = {
formFields: [
{
dataPath: 'loginName', label: '', input: 'Input', inputRef: "loginName",
inputProps: {
placeholder: '用户名',
clearable: true,
prefixIcon: markRaw(User),
},
},
{
dataPath: 'password', label: '', input: 'Input', inputRef: "password",
inputProps: {
placeholder: '登录密码',
type: "password",
showPassword: true,
prefixIcon: markRaw(Lock),
},
},
],
rules: {
loginName: [
{ required: true, message: "用户名必填", trigger: 'blur' },
],
password: [
{ required: true, message: "登录密码必填", trigger: 'blur' },
],
},
};
const form = useTemplateRef<InstanceType<typeof DataForm>>("formRef");
const router = useRouter();
async function login() {
await form.value.data.formRef.validate(valid => {
if (!valid) return;
doLogin().finally();
});
}
async function doLogin() {
state.loading = true;
state.errMsg = "";
try {
const res = await Request.request.post("/api/login", {
loginName: state.loginName,
password: state.password,
});
const userInfo = res.userInfo;
if (userInfo) {
await getCurrentUser();
await router.push({ name: "editor" });
system.msg("登录成功");
} else {
state.errMsg = res.message ?? "登录失败,请重试!";
}
} catch (err) {
state.errMsg = "登录失败,请重试!";
} finally {
state.loading = false
}
}
interface LoginExpose {
state: LoginState;
data: LoginData;
}
const expose: LoginExpose = {
state,
data,
};
//
defineExpose(expose);
export type {
LoginProps,
LoginState,
}
</script>
<template>
<div class="login-container content-center">
<div class="login-form-container">
<div class="login-title">登录</div>
<DataForm
ref="formRef"
class="login-form"
:size="'large'"
:data="state"
:formFields="data.formFields"
:rules="data.rules"
:showMessage="true"
:columnCount="1"
labelWidth="60px"
inputWidth=""
>
<template #submit>
<ElButton class="login-button" :loading="state.loading" type="primary" @click="login">
登录
</ElButton>
<div class="login-error">{{ state.errMsg }}</div>
</template>
</DataForm>
</div>
</div>
</template>
<style scoped>
.login-container {
height: 100%;
background-color: #f2f5f7;
}
.login-form-container {
width: 396px;
height: 360px;
padding: 32px 48px 24px 48px;
background-color: #ffffff;
}
.login-title {
font-size: 40px;
color: #409eff;
font-weight: 500;
margin-bottom: 40px;
}
.login-form {
height: 200px;
}
.login-error {
color: #ff4d4f;
width: 100%;
height: 24px;
line-height: 24px;
}
.login-button {
width: 100%;
}
</style>

85
src/views/Sidebar.vue

@ -0,0 +1,85 @@
<template>
<el-menu
default-active="dashboard"
class="menu"
:collapse="collapsed"
:collapse-transition="false"
router
>
<el-menu-item index="/dashboard">
<component :is="renderIcon('antd DashboardOutlined')"></component>
<!-- antd BarChartOutlined-->
<span>仪表盘</span>
</el-menu-item>
<el-menu-item index="/modelingSimulation">
<component :is="renderIcon('antd BankFilled')"></component>
<span>建模仿真控制平台</span>
</el-menu-item>
<el-sub-menu index="/taskManagement">
<template #title>
<component :is="renderIcon('fa Tasks')"></component><span>任务管理</span></template>
<el-menu-item index="/taskQuery">
<component :is="renderIcon('antd FileSearchOutlined')"></component>任务查询</el-menu-item>
<el-menu-item index="/automatedPresentation">
<component :is="renderIcon('fa ChalkboardTeacher')"></component>自动演示管理</el-menu-item>
</el-sub-menu>
<el-sub-menu index="/log">
<template #title>
<component :is="renderIcon('antd FileSearchOutlined')"></component>
<span>日志查询</span>
</template>
<el-menu-item index="/upstream">
<component :is="renderIcon('element Memo')"></component>上游接口日志</el-menu-item>
<el-menu-item index="/device">
<component :is="renderIcon('element MessageBox')"></component>设备报文日志</el-menu-item>
</el-sub-menu>
<el-sub-menu index="/device">
<template #title>
<component :is="renderIcon('antd DatabaseOutlined')"></component>
<span>设备管理</span>
</template>
<el-menu-item index="/points">
<component :is="renderIcon('antd EnvironmentOutlined')"></component>点位管理</el-menu-item>
<el-menu-item index="/locations">
<component :is="renderIcon('antd BorderOuterOutlined')"></component>货位管理</el-menu-item>
<el-menu-item index="/vehicles">
<component :is="renderIcon('antd CarOutlined')"></component>车辆管理</el-menu-item>
<el-menu-item index="/chargers">
<component :is="renderIcon('antd ThunderboltOutlined')"></component>充电位管理</el-menu-item>
</el-sub-menu>
<el-sub-menu index="/inventory">
<template #title>
<component :is="renderIcon('fa EditRegular')"></component>
<span>库存管理</span>
</template>
<el-menu-item index="/query">
<component :is="renderIcon('antd FileSearchOutlined')"></component>库存查询</el-menu-item>
<el-menu-item index="/account">
<component :is="renderIcon('antd SearchOutlined')"></component>帐页查询</el-menu-item>
</el-sub-menu>
<el-sub-menu index="/user">
<template #title>
<component :is="renderIcon('element User')"></component>
<span>用户管理</span>
</template>
<el-menu-item index="/users">
<component :is="renderIcon('antd UserOutlined')"></component>用户管理</el-menu-item>
<el-menu-item index="/roles">
<component :is="renderIcon('antd UserSwitchOutlined')"></component>角色管理</el-menu-item>
</el-sub-menu>
</el-menu>
</template>
<script setup>
import { renderIcon } from '@/utils/webutils.js'
defineProps({
collapsed: Boolean
})
</script>
<style scoped>
.menu {
border-right: none;
height: 100%;
}
</style>

58
src/views/dashboard/EChartWrapper.vue

@ -0,0 +1,58 @@
<template>
<div ref="chart" class="chart"></div>
</template>
<script setup>
import * as echarts from 'echarts';
import { ref, onMounted, watch,nextTick} from 'vue';
import { useResizeObserver } from '@vueuse/core';
const props=defineProps({
options:{
type:Object,
required:true
}
})
const chart =ref(null);
let chartInstance=null;//
//
const initChart=()=>{
if(chartInstance) chartInstance.dispose()
chartInstance=echarts.init(chart.value)
chartInstance.setOption(props.options)
}
//
const updateChart = (newOptions) => {
if (chartInstance) {
chartInstance.setOption(newOptions);
}
};
//
const handleResize=()=>{
if(chartInstance) chartInstance.resize();
}
//options
watch(()=>props.options,(newOptions)=>{
if(chartInstance) chartInstance.setOption(newOptions, true)
},{ deep: true })
onMounted(() => {
nextTick(() => {
initChart();
useResizeObserver(chart, handleResize);
});
});
//
defineExpose({
updateChart
});
</script>
<style scoped>
.chart {
height: 100%;
}
</style>

281
src/views/dashboard/index.vue

@ -0,0 +1,281 @@
<template>
<div class="dashboard">
<div class="stat-row">
<div class="stat-card purple">
<div class="title">穿梭板数</div>
<div class="number">11</div>
</div>
<div class="stat-card blue">
<div class="title">提升机数</div>
<div class="number">16</div>
</div>
<div class="stat-card red">
<div class="title">使用库位数/总库位数</div>
<div class="number">1864/8404</div>
</div>
<div class="stat-card green">
<div class="title">存储率</div>
<div class="number">22%</div>
</div>
</div>
<el-row :gutter="10">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-card class="chart-card">
<template #header>
<div class="card-header">
<span>设备任务状态占比</span>
</div>
</template>
<div class="chart-wrap">
<EChartWrapper :options="option1"></EChartWrapper>
</div>
</el-card>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12">
<el-card class="chart-card">
<template #header>
<div class="card-header">
<span>业务任务趋势</span>
</div>
</template>
<div class="chart-wrap">
<EChartWrapper :options="option2"></EChartWrapper>
</div>
</el-card>
</el-col>
</el-row>
</div>
</template>
<script setup>
import * as echarts from 'echarts';
import { ref, onMounted, watch,nextTick} from 'vue';
import EChartWrapper from './EChartWrapper.vue'
const option1=ref({
tooltip: {
trigger: 'axis',
formatter: (params) => {
// params
let result = `${params[0].name}<br/>`; // x
params.forEach((item) => {
result += `${item.seriesName}: ${item.value || 0}单<br/>`;
});
return result;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: (() => {
const dates = []
for (let i = 6; i >= 0; i--) {
const date = new Date()
date.setDate(date.getDate() - i)
dates.push(date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' }))
}
return dates
})()
},
yAxis: {
type: 'value',
name: '单',
nameTextStyle: {
padding: [0, 0, 0, 30]
},
},
series: [{
name: '1F',
type: 'bar',
data: [120, 150, 80, 160, 10, 270, 110],
itemStyle: {
color: '#409EFF'
},
},{
name: '2F',
type: 'bar',
data: [110, 50, 10, 160, 140, 120, 130],
itemStyle: {
color: '#1bc042'
},
},{
name: '3F',
type: 'bar',
data: [20, 100, 120, 160, 40, 170, 100],
itemStyle: {
color: '#ffa640'
},
}]
})
const option2=ref({
tooltip: {
trigger: 'axis',
formatter: (params) => {
// params
let result = `${params[0].name}<br/>`; // x
params.forEach((item) => {
result += `${item.seriesName}: ${item.value || 0}单<br/>`;
});
return result;
}
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
top: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: (() => {
const dates = []
for (let i = 6; i >= 0; i--) {
const date = new Date()
date.setDate(date.getDate() - i)
dates.push(date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' }))
}
return dates
})()
},
yAxis: {
type: 'value',
name: '单',
nameTextStyle: {
padding: [0, 0, 0, 30]
},
},
series: [{
name: '1F',
type: 'bar',
data: [120, 150, 80, 160, 10, 270, 110],
itemStyle: {
color: '#409EFF'
},
},{
name: '2F',
type: 'bar',
data: [110, 50, 10, 160, 140, 120, 130],
itemStyle: {
color: '#1bc042'
},
},{
name: '3F',
type: 'bar',
data: [20, 100, 120, 160, 40, 170, 100],
itemStyle: {
color: '#ffa640'
},
}]
})
</script>
<style lang="less">
.dashboard{
height: 100%;
overflow-x: hidden;
overflow-y: auto;
.stat-row{
height: 120px;
display: flex;
flex-direction: row;
margin-bottom: 20px;
gap: 20px;
.stat-card {
flex:1;
height: 120px;
padding: 20px;
border-radius: 8px;
color: #fff;
display: flex;
flex-direction: column;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.1);
transition: all 0.3s;
&:hover {
transform: translateY(-5px);
box-shadow: 0 5px 15px 0 rgba(0,0,0,0.15);
}
.title {
font-size: 14px;
opacity: 0.9;
}
.number {
font-size: 28px;
font-weight: bold;
margin-top: 10px;
}
&.purple {
background: linear-gradient(135deg, #5e71fc 0%, #aa7bfc 100%);
}
&.blue {
background: linear-gradient(135deg, #3da2f5 0%, #6885fa 100%);
}
&.red {
background: linear-gradient(135deg, #ea6274 0%, #f09165 100%);
}
&.green {
background: linear-gradient(135deg, #46c0ab 0%, #7ceba6 100%);
}
&.orange {
background: linear-gradient(135deg, #efa03b 0%, #fad46f 100%);
}
}
}
.chart-card {
background-color: #fff;
border-radius: 4px;
:deep(.el-card__header){
padding:0;
border-bottom: 0;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 20px;
span {
font-size: 14px;
}
}
.chart {
height: 300px;
}
:deep(.date-picker){
width: 100px;
}
.chart-wrap {
height: 300px;
}
}
.el-card__header{
padding:0;
border: none;
}
}
@media (max-width: 768px) {
.stat-row {
overflow: auto;
}
.el-col-xs-24 + .el-col-xs-24{
margin-top:20px;
}
}
</style>

12
src/views/device/chargers.vue

@ -0,0 +1,12 @@
<template>
<div class="modeling-simulation">
chargers
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/device/locations.vue

@ -0,0 +1,12 @@
<template>
<div class="modeling-simulation">
locations
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/device/points.vue

@ -0,0 +1,12 @@
<template>
<div class="modeling-simulation">
points
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/device/vehicles.vue

@ -0,0 +1,12 @@
<template>
<div class="modeling-simulation">
vehicles
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/inventory/account.vue

@ -0,0 +1,12 @@
<template>
<div class="dashboard">
dashboard
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/inventory/query.vue

@ -0,0 +1,12 @@
<template>
<div class="modeling-simulation">
query
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/log/device.vue

@ -0,0 +1,12 @@
<template>
<div class="modeling-simulation">
device
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/log/upstream.vue

@ -0,0 +1,12 @@
<template>
<div class="modeling-simulation">
upstream
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/modelingSimulation/index.vue

@ -0,0 +1,12 @@
<template>
<div class="modeling-simulation">
modelingSimulation
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/taskManagement/automatedPresentation.vue

@ -0,0 +1,12 @@
<template>
<div class="modeling-simulation">
automatedPresentation.vue
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/taskManagement/taskQuery.vue

@ -0,0 +1,12 @@
<template>
<div class="modeling-simulation">
taskQuery.vue
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/user/roles.vue

@ -0,0 +1,12 @@
<template>
<div class="roles">
roles
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>

12
src/views/user/users.vue

@ -0,0 +1,12 @@
<template>
<div class="dashboard">
users
</div>
</template>
<script setup>
</script>
<style lang="less">
.dashboard{
}
</style>
Loading…
Cancel
Save