Commit c7dcd1d2f724e6333d84b3d7319d6465da1e7376

Authored by xp.Huang
2 parents 53e42e29 8306e549

Merge branch 'main_dev' into 'main'

thingskit1.1.0 version merge

See merge request yunteng/thingskit-front!579
Showing 57 changed files with 1321 additions and 715 deletions

Too many changes to show.

To preserve performance only 57 of 162 files are displayed.

... ... @@ -39,3 +39,6 @@ VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME = 500000
39 39
40 40 # Alarm Notify Auto Close Time Unit is Second
41 41 VITE_GLOB_ALARM_NOTIFY_DURATION = 5
  42 +
  43 +# Should Disabled Task Center Execute Interval Unit (Second)
  44 +VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND = false
... ...
1   -# Whether to open mock
2   -VITE_GLOB_USE_MOCK = false
3   -
4   -# public path
5   -VITE_GLOB_PUBLIC_PATH = /
6   -
7   -# Delete console
8   -VITE_GLOB_DROP_CONSOLE = true
9   -
10   -# Whether to enable gzip or brotli compression
11   -# Optional: gzip | brotli | none
12   -# If you need multiple forms, you can use `,` to separate
13   -VITE_GLOB_BUILD_COMPRESS = 'gzip'
14   -
15   -# Whether to delete origin files when using compress, default false
16   -VITE_GLOB_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
17   -
18   -# Basic interface address SPA
19   -VITE_GLOB_API_URL=http://122.9.141.195:8080/api
20   -
21   -# File upload address, optional
22   -# It can be forwarded by nginx or write the actual address directly
23   -VITE_GLOB_UPLOAD_URL=http://122.9.141.195:8080/api/yt/oss/upload
24   -
25   -# Interface prefix
26   -VITE_GLOB_API_URL_PREFIX=/yt
27   -
28   -# Whether to enable image compression
29   -VITE_GLOB_USE_IMAGEMIN= false
30   -
31   -# use pwa
32   -VITE_USE_PWA = false
33   -
34   -# Is it compatible with older browsers
35   -VITE_LEGACY = true
36   -
37   -# 实时数据的ws地址
38   -VITE_GLOB_WEB_SOCKET = ws://122.9.141.195:8080/api/ws/plugins/telemetry?token=
39   -
40   -#configuration
41   -VITE_GLOB_CONFIGURATION = /thingskit-scada
42   -
43   -# 大屏设计器
44   -VITE_GLOB_LARGE_DESIGNER = /large-designer
45   -
46   -# Content Security Policy
47   -VITE_GLOB_CONTENT_SECURITY_POLICY = false
48   -
49   -# Alarm Notify Polling Interval Time
50   -VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME = 60000
51   -
52   -# Alarm Notify Auto Close Time Unit is Second
53   -VITE_GLOB_ALARM_NOTIFY_DURATION = 5
  1 +# Whether to open mock
  2 +VITE_GLOB_USE_MOCK = false
  3 +
  4 +# public path
  5 +VITE_GLOB_PUBLIC_PATH = /
  6 +
  7 +# Delete console
  8 +VITE_GLOB_DROP_CONSOLE = true
  9 +
  10 +# Whether to enable gzip or brotli compression
  11 +# Optional: gzip | brotli | none
  12 +# If you need multiple forms, you can use `,` to separate
  13 +VITE_GLOB_BUILD_COMPRESS = 'gzip'
  14 +
  15 +# Whether to delete origin files when using compress, default false
  16 +VITE_GLOB_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
  17 +
  18 +# Basic interface address SPA
  19 +VITE_GLOB_API_URL=http://localhost:8080/api
  20 +
  21 +# File upload address, optional
  22 +# It can be forwarded by nginx or write the actual address directly
  23 +VITE_GLOB_UPLOAD_URL=http://localhost:8080/api/yt/oss/upload
  24 +
  25 +# Interface prefix
  26 +VITE_GLOB_API_URL_PREFIX=/yt
  27 +
  28 +# Whether to enable image compression
  29 +VITE_GLOB_USE_IMAGEMIN= false
  30 +
  31 +# use pwa
  32 +VITE_USE_PWA = false
  33 +
  34 +# Is it compatible with older browsers
  35 +VITE_LEGACY = true
  36 +
  37 +# 实时数据的ws地址
  38 +VITE_GLOB_WEB_SOCKET = ws://localhost:8080/api/ws/plugins/telemetry?token=
  39 +
  40 +#configuration
  41 +VITE_GLOB_CONFIGURATION = /thingskit-scada
  42 +
  43 +# 大屏设计器
  44 +VITE_GLOB_LARGE_DESIGNER = /large-designer
  45 +
  46 +# Content Security Policy
  47 +VITE_GLOB_CONTENT_SECURITY_POLICY = false
  48 +
  49 +# Alarm Notify Polling Interval Time
  50 +VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME = 60000
  51 +
  52 +# Alarm Notify Auto Close Time Unit is Second
  53 +VITE_GLOB_ALARM_NOTIFY_DURATION = 5
  54 +
  55 +# Should Disabled Task Center Execute Interval Unit (Second)
  56 +VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND = false
... ...
... ... @@ -16,6 +16,7 @@ import {
16 16 UpdateDataComponentParams,
17 17 } from './model';
18 18 import { defHttp } from '/@/utils/http/axios';
  19 +import { isShareMode } from '/@/views/sys/share/hook';
19 20
20 21 enum DataBoardUrl {
21 22 GET_DATA_BOARD = '/data_board',
... ... @@ -224,7 +225,7 @@ export const sendCommandOneway = (params: SendCommandParams) => {
224 225 url: `${SendCommand.ONEWAY}/${params.deviceId}`,
225 226 params: params.value,
226 227 },
227   - { joinPrefix: false }
  228 + { joinPrefix: false, withShareToken: isShareMode() }
228 229 );
229 230 };
230 231
... ...
... ... @@ -86,6 +86,12 @@ export interface DataSource {
86 86 deviceName: string;
87 87 deviceProfileId: string;
88 88 tbDeviceId: string;
  89 + customCommand: {
  90 + transportType?: string;
  91 + commandType?: string;
  92 + command?: string;
  93 + service?: string;
  94 + };
89 95
90 96 // front usage
91 97 uuid?: string;
... ... @@ -93,6 +99,7 @@ export interface DataSource {
93 99 height?: number;
94 100 radio?: RadioRecord;
95 101 deviceType?: DeviceTypeEnum;
  102 + [key: string]: any;
96 103 }
97 104
98 105 export interface DataComponentRecord {
... ...
... ... @@ -50,6 +50,8 @@ export interface DeviceModel {
50 50 deviceType: DeviceTypeEnum;
51 51 organizationId: string;
52 52 customerId?: string;
  53 + alias?: string;
  54 + tbDeviceId?: string;
53 55 }
54 56
55 57 export interface DeviceProfileModel {
... ...
... ... @@ -10,6 +10,8 @@ export type SchedueParam = {
10 10 data?: any;
11 11 code?: number;
12 12 jobId?: string;
  13 + startTime?: number;
  14 + endTime?: number;
13 15 };
14 16
15 17 export interface ReportModel {
... ...
... ... @@ -30,6 +30,38 @@ export const getMenuList = (args?: number) => {
30 30 } catch (e) {}
31 31 };
32 32
  33 +/**
  34 + * @description 获取自身的权限
  35 + * @param args
  36 + * @returns
  37 + */
  38 +export const getMeMenuList = (args?: number) => {
  39 + try {
  40 + return defHttp.get<getMenuListResultModel>({
  41 + url: Api.GetMenuList,
  42 + params: {
  43 + needButton: args == 1 ? false : null,
  44 + },
  45 + });
  46 + } catch (e) {}
  47 +};
  48 +
  49 +/**
  50 + * @description 获取超级管理员菜单
  51 + * @param args
  52 + * @returns
  53 + */
  54 +export const getAdminMenuList = (args?: number) => {
  55 + try {
  56 + return defHttp.get<getMenuListResultModel>({
  57 + url: Api.SysAdminMenuList,
  58 + params: {
  59 + needButton: args == 1 ? false : null,
  60 + },
  61 + });
  62 + } catch (e) {}
  63 +};
  64 +
33 65 export const delMenu = (menuIds: string[]) => {
34 66 const url = Api.BaseMenuUrl;
35 67 return defHttp.delete({ url: url, data: menuIds });
... ...
... ... @@ -42,7 +42,7 @@ export interface MenuRecord {
42 42 component: string;
43 43 meta: Meta;
44 44 disabled?: boolean;
45   - isDictCompareDisabled?: boolean;
  45 + show?: boolean;
46 46 icon?: string;
47 47 title?: string;
48 48 key?: string;
... ...
1 1 import { defHttp } from '/@/utils/http/axios';
2 2 import { ViewTypeEnum } from '/@/views/sys/share/config/config';
  3 +import { isShareMode } from '/@/views/sys/share/hook';
3 4
4 5 enum Api {
5 6 CHECK = '/share/check',
... ... @@ -8,9 +9,14 @@ enum Api {
8 9 }
9 10
10 11 export const checkShareAccessToken = (type: ViewTypeEnum, id: string) => {
11   - return defHttp.get<Record<'data', boolean>>({
12   - url: `${Api.CHECK}/${type}/${id}`,
13   - });
  12 + return defHttp.get<Record<'data', boolean>>(
  13 + {
  14 + url: `${Api.CHECK}/${type}/${id}`,
  15 + },
  16 + {
  17 + withShareToken: isShareMode(),
  18 + }
  19 + );
14 20 };
15 21
16 22 export const sharePageLogin = (publicId: string) => {
... ... @@ -21,14 +27,20 @@ export const sharePageLogin = (publicId: string) => {
21 27 },
22 28 {
23 29 joinPrefix: false,
  30 + withShareToken: isShareMode(),
24 31 }
25 32 );
26 33 };
27 34
28 35 export const getShareContent = (record: Record<'accessCredentials' | 'id', string>) => {
29 36 const { id, accessCredentials } = record;
30   - return defHttp.get({
31   - url: `${Api.SHARE_CONTENT}/${ViewTypeEnum.DATA_BOARD}/share_data/${id}`,
32   - params: { accessCredentials },
33   - });
  37 + return defHttp.get(
  38 + {
  39 + url: `${Api.SHARE_CONTENT}/${ViewTypeEnum.DATA_BOARD}/share_data/${id}`,
  40 + params: { accessCredentials },
  41 + },
  42 + {
  43 + withShareToken: isShareMode(),
  44 + }
  45 + );
34 46 };
... ...
... ... @@ -81,11 +81,8 @@ export const genModbusCommand = (data: GenModbusCommandType) => {
81 81 };
82 82
83 83 export const immediateExecute = (data: ImmediateExecuteTaskType) => {
84   - return defHttp.post<Record<'data', boolean>>(
85   - {
86   - url: Api.IMMEDIATE_EXECUTE,
87   - params: data,
88   - },
89   - { joinParamsToUrl: true }
90   - );
  84 + return defHttp.post<Record<'data', boolean>>({
  85 + url: Api.IMMEDIATE_EXECUTE,
  86 + params: data,
  87 + });
91 88 };
... ...
... ... @@ -47,6 +47,7 @@ export interface TaskRecordType extends CreateTaskRecordType {
47 47 enabled: boolean;
48 48 state: number;
49 49 lastExecuteTime?: number;
  50 + lastExecuteStr?: string;
50 51 tkDeviceTaskCenter?: {
51 52 allowState: number;
52 53 taskCenterId: string;
... ...
... ... @@ -34,6 +34,9 @@ enum Api {
34 34 getTenantProfile = '/tenant_profiles',
35 35 deleteTenantProfile = '/tenantProfile',
36 36 setTenantProfile = '/tenantProfile',
  37 + getTenantPageList = '/admin/all/tenants',
  38 + getTenantAllPageList = '/admin/',
  39 + deleteTenantProfileByCheckPass = '/tenant_profiles/allow/',
37 40 }
38 41
39 42 export async function deleteTenantProfileApi(ids: string) {
... ... @@ -155,3 +158,20 @@ export function getTenantRoles(tenantCode: string) {
155 158 url: Api.getTenantRoles,
156 159 });
157 160 }
  161 +
  162 +export function getTenantPageList() {
  163 + return defHttp.get({
  164 + url: Api.getTenantPageList,
  165 + });
  166 +}
  167 +
  168 +export function getTenantAllPageLists(tenantId) {
  169 + return defHttp.get({
  170 + url: `${Api.getTenantAllPageList}${tenantId}/all/tenant_admin`,
  171 + });
  172 +}
  173 +export function deleteTenantProfileByCheckPassApi(tenantProfileId) {
  174 + return defHttp.get({
  175 + url: `${Api.deleteTenantProfileByCheckPass}${tenantProfileId}/delete`,
  176 + });
  177 +}
... ...
... ... @@ -39,6 +39,7 @@ export interface TenantPageRequestParams extends BaseQueryParams {
39 39 }
40 40
41 41 export interface TenantAdminPageRequestParams extends BaseQueryParams {
  42 + [x: string]: any;
42 43 realName?: string;
43 44 tenantId?: string;
44 45 items?: string[];
... ...
1   -<svg t="1671442803003" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4002" width="200" height="200"><path d="M0 0m178.086957 0l667.826086 0q178.086957 0 178.086957 178.086957l0 667.826086q0 178.086957-178.086957 178.086957l-667.826086 0q-178.086957 0-178.086957-178.086957l0-667.826086q0-178.086957 178.086957-178.086957Z" fill="#2E7BFC" p-id="4003"></path><path d="M758.717217 394.373565v268.866783l-238.191304 134.455652v-268.911304l124.14887-70.054957v94.497391l51.956869-29.985391V429.412174l62.107826-35.038609z m-493.434434 0l238.191304 134.433392v268.866782l-238.191304-134.433391V394.373565zM512 226.304l246.717217 139.241739L512 504.787478l-246.717217-139.241739L512 226.326261z" fill="#FFFFFF" p-id="4004"></path></svg>
  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="1682599359349" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="30418" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="400"><path d="M482.7904 529.6384H256a10.24 10.24 0 0 0-10.24 10.24V768a10.24 10.24 0 0 0 10.24 10.24h226.7904a10.24 10.24 0 0 0 10.24-10.24v-228.1216a10.24 10.24 0 0 0-10.24-10.24z m-9.0624 218.7264a10.24 10.24 0 0 1-10.24 10.24h-187.8528a10.24 10.24 0 0 1-10.24-10.24v-188.5184a10.24 10.24 0 0 1 10.24-10.24h187.8528a10.24 10.24 0 0 1 10.24 10.24v188.5184zM768 529.6384h-226.7904a10.24 10.24 0 0 0-10.24 10.24V768a10.24 10.24 0 0 0 10.24 10.24H768a10.24 10.24 0 0 0 10.24-10.24v-228.1216a10.24 10.24 0 0 0-10.24-10.24z m-9.3952 218.7264a10.24 10.24 0 0 1-10.24 10.24h-187.8528a10.24 10.24 0 0 1-10.24-10.24v-188.5184a10.24 10.24 0 0 1 10.24-10.24h187.8528a10.24 10.24 0 0 1 10.24 10.24v188.5184zM482.7904 245.76H256a10.24 10.24 0 0 0-10.24 10.24v228.1216a10.24 10.24 0 0 0 10.24 10.24h226.7904a10.24 10.24 0 0 0 10.24-10.24V256a10.24 10.24 0 0 0-10.24-10.24z m-9.0624 218.3936a10.24 10.24 0 0 1-10.24 10.24h-187.8528a10.24 10.24 0 0 1-10.24-10.24v-188.5184a10.24 10.24 0 0 1 10.24-10.24h187.8528a10.24 10.24 0 0 1 10.24 10.24v188.5184z" fill="#608DF5" p-id="30419"></path><path d="M768 245.76h-226.7904a10.24 10.24 0 0 0-10.24 10.24v228.1216a10.24 10.24 0 0 0 10.24 10.24H768a10.24 10.24 0 0 0 10.24-10.24V256a10.24 10.24 0 0 0-10.24-10.24z m-9.3952 218.3936a10.24 10.24 0 0 1-10.24 10.24h-187.8528a10.24 10.24 0 0 1-10.24-10.24v-188.5184a10.24 10.24 0 0 1 10.24-10.24h187.8528a10.24 10.24 0 0 1 10.24 10.24v188.5184z" fill="#E9895D" p-id="30420"></path></svg>
\ No newline at end of file
... ...
... ... @@ -3,24 +3,32 @@ import { Rule } from '/@/components/Form';
3 3 export { default as JSONEditor } from './index.vue';
4 4
5 5 export const parseStringToJSON = <T = Recordable>(value: string) => {
  6 + let valid = false;
6 7 try {
7 8 const json = JSON.parse(value) as T;
8   - return { json, valid: true };
  9 + typeof json === 'object' ? (valid = true) : (valid = false);
  10 + return { json, valid };
9 11 } catch (error) {
10   - return { json: null, valid: false };
  12 + return { json: null, valid };
11 13 }
12 14 };
13 15
14   -export const JSONEditorValidator = (message = 'json格式校验失败'): Rule[] => {
  16 +export const JSONEditorValidator = (
  17 + message = 'JSON格式校验失败',
  18 + noEmpty = false,
  19 + emptyMessage = 'JSON不能为空对象'
  20 +): Rule[] => {
15 21 return [
16 22 {
17 23 validateTrigger: 'blur',
18 24 validator(_rule: Rule, value: any, _callback: Fn) {
19   - const { valid } = parseStringToJSON(value);
  25 + const { valid, json } = parseStringToJSON(value);
20 26 if (valid) {
  27 + if (noEmpty && json && !Object.keys(json).length) return Promise.reject(emptyMessage);
21 28 return Promise.resolve();
  29 + } else {
  30 + return Promise.reject(message);
22 31 }
23   - return Promise.reject(message);
24 32 },
25 33 },
26 34 ];
... ...
1 1 <script lang="ts" setup>
2   - import { ref } from 'vue';
  2 + import { ref, watch } from 'vue';
3 3 import JSONEditor, { JSONEditorOptions } from 'jsoneditor';
4 4 import 'jsoneditor/dist/jsoneditor.min.css';
5 5 import { unref } from 'vue';
... ... @@ -18,6 +18,7 @@
18 18 defineProps<{
19 19 value?: string;
20 20 options?: JSONEditorOptions;
  21 + height?: number;
21 22 }>(),
22 23 {
23 24 options: () =>
... ... @@ -26,6 +27,7 @@
26 27 mainMenuBar: false,
27 28 statusBar: false,
28 29 } as JSONEditorOptions),
  30 + height: 150,
29 31 }
30 32 );
31 33
... ... @@ -40,6 +42,8 @@
40 42
41 43 const editoreRef = ref<JSONEditor>();
42 44
  45 + const isFocus = ref(false);
  46 +
43 47 const handleChange = (value: any) => {
44 48 emit(EventEnum.UPDATE_VALUE, value, unref(editoreRef));
45 49 emit(EventEnum.CHANGE, value, unref(editoreRef));
... ... @@ -54,8 +58,14 @@
54 58 return {
55 59 ...options,
56 60 onChangeText: handleChange,
57   - onBlur: (event: Event) => handleEmit(event, EventEnum.BLUR),
58   - onFocus: (event: Event) => handleEmit(event, EventEnum.FOCUS),
  61 + onBlur: (event: Event) => {
  62 + isFocus.value = false;
  63 + handleEmit(event, EventEnum.BLUR);
  64 + },
  65 + onFocus: (event: Event) => {
  66 + isFocus.value = true;
  67 + handleEmit(event, EventEnum.FOCUS);
  68 + },
59 69 } as JSONEditorOptions;
60 70 });
61 71
... ... @@ -63,15 +73,16 @@
63 73 editoreRef.value = new JSONEditor(unref(jsonEditorElRef), unref(getOptions));
64 74 };
65 75
66   - // watch(
67   - // () => props.value,
68   - // (target) => {
69   - // unref(editoreRef)?.setText(target || '');
70   - // },
71   - // {
72   - // immediate: true,
73   - // }
74   - // );
  76 + watch(
  77 + () => props.value,
  78 + (target) => {
  79 + if (unref(isFocus)) return;
  80 + unref(editoreRef)?.setText(target || '');
  81 + },
  82 + {
  83 + immediate: true,
  84 + }
  85 + );
75 86
76 87 const get = (): string => {
77 88 return unref(editoreRef)?.getText() || '';
... ... @@ -97,7 +108,7 @@
97 108 </script>
98 109
99 110 <template>
100   - <div class="p-2 bg-gray-200">
  111 + <div class="p-2 bg-gray-200" :style="{ height: `${height}px` }">
101 112 <div ref="jsonEditorElRef" class="jsoneditor"></div>
102 113 </div>
103 114 </template>
... ... @@ -108,6 +119,11 @@
108 119
109 120 :deep(.jsoneditor) {
110 121 border: none !important;
  122 +
  123 + .ace-jsoneditor,
  124 + textarea.jsoneditor-text {
  125 + min-height: auto;
  126 + }
111 127 }
112 128 }
113 129 </style>
... ...
... ... @@ -67,8 +67,8 @@
67 67 initVal();
68 68 });
69 69
70   - function initVal() {
71   - if (props?.type) getOptions(props?.type);
  70 + async function initVal() {
  71 + if (props?.type) await getOptions(props?.type);
72 72 if (props?.value) for (let i in props.value) Reflect.set(valueObj, i, props.value[i]);
73 73 }
74 74
... ...
... ... @@ -279,7 +279,6 @@
279 279 setSelectedOptions,
280 280 setSelectedTotal,
281 281 reloadPending,
282   - reloadPending,
283 282 reloadSelected,
284 283 };
285 284
... ... @@ -408,7 +407,7 @@
408 407 :key="item.value"
409 408 >
410 409 <span>
411   - {{ item.label }}
  410 + {{ item.alias || item.name }}
412 411 </span>
413 412 </Tag>
414 413 <Tag class="!px-2 !py-1 !bg-gray-50 !border-gray-100" v-if="getSurplusOptionsLength">
... ...
... ... @@ -13,6 +13,7 @@
13 13 import { Divider, Button } from 'ant-design-vue';
14 14 import { OpenModalMode, OpenModalParams, StructRecord } from './type';
15 15 import { cloneDeep } from 'lodash-es';
  16 + import { isArray } from '/@/utils/is';
16 17
17 18 const emit = defineEmits(['update:value']);
18 19
... ... @@ -30,7 +31,8 @@
30 31
31 32 const getValue = computed<StructRecord[]>(() => {
32 33 const { value } = props;
33   - return value.map((item) => {
  34 +
  35 + return (isArray(value) ? value : []).map((item) => {
34 36 return {
35 37 ...(item as StructRecord),
36 38 ...((item as StructRecord).id ? {} : { id: buildUUID() }),
... ...
... ... @@ -60,7 +60,7 @@
60 60 });
61 61
62 62 const validateRepeat = (value: StructRecord, valueList: StructRecord[]) => {
63   - return valueList.filter((item) => item.identifier === value.identifier).length >= 1;
  63 + return valueList.filter((item) => item.identifier === value.identifier).length > 1;
64 64 };
65 65
66 66 const handleSubmit = async () => {
... ...
... ... @@ -20,7 +20,7 @@ export const validateValueRange = (_rule, value: Record<'min' | 'max', number>,
20 20 return Promise.resolve();
21 21 };
22 22
23   -export const validateJSON = (_rule, value: ModelOfMatterParams[], _callback) => {
  23 +export const validateJSON = (_rule, value = [] as ModelOfMatterParams[], _callback) => {
24 24 if (value.length) {
25 25 return Promise.resolve();
26 26 }
... ... @@ -65,7 +65,7 @@ export const formSchemas = (hasStructForm: boolean): FormSchema[] => {
65 65 },
66 66 defaultValue: 'INT',
67 67 componentProps: ({ formActionType }) => {
68   - const { updateSchema } = formActionType;
  68 + const { updateSchema, setFieldsValue } = formActionType;
69 69 return {
70 70 placeholder: '请选择数据类型',
71 71 api: async (params: Recordable) => {
... ... @@ -86,13 +86,15 @@ export const formSchemas = (hasStructForm: boolean): FormSchema[] => {
86 86 valueField: 'itemValue',
87 87 getPopupContainer: () => document.body,
88 88 onChange: (value: string) => {
89   - value === DataTypeEnum.IS_STRUCT &&
  89 + if (value == DataTypeEnum.IS_STRUCT) {
90 90 updateSchema({
91 91 field: FormField.SPECS_LIST,
92 92 componentProps: {
93 93 hasStructForm: true,
94 94 },
95 95 });
  96 + setFieldsValue({ [FormField.SPECS_LIST]: [] });
  97 + }
96 98 },
97 99 };
98 100 },
... ...
... ... @@ -127,4 +127,6 @@ export type ComponentType =
127 127 | 'ProductPicker'
128 128 | 'PollCommandInput'
129 129 | 'RegisterAddressInput'
130   - | 'ControlGroup';
  130 + | 'ControlGroup'
  131 + | 'JSONEditor'
  132 + | 'OrgTreeSelect';
... ...
  1 +<script lang="ts" setup>
  2 + import { ReloadOutlined } from '@ant-design/icons-vue';
  3 + import { Button, List, Space, Tooltip } from 'ant-design-vue';
  4 + import { reactive } from 'vue';
  5 + import { ref } from 'vue';
  6 + import { BasicForm, useForm } from '../../Form';
  7 + import { basicListProps } from './props';
  8 + import { ExtractPropTypes } from 'vue';
  9 + defineProps<ExtractPropTypes<typeof basicListProps>>();
  10 +
  11 + const [registerForm] = useForm({
  12 + schemas: [
  13 + {
  14 + field: '123',
  15 + label: 'test',
  16 + component: 'Input',
  17 + },
  18 + ],
  19 + labelWidth: 100,
  20 + layout: 'inline',
  21 + baseColProps: { span: 8 },
  22 + showAdvancedButton: true,
  23 + compact: true,
  24 + });
  25 +
  26 + const listElRef = ref<Nullable<ComponentElRef>>(null);
  27 +
  28 + const pagination = reactive({ size: 'small' });
  29 +
  30 + const loading = ref(false);
  31 +
  32 + const dataSource = ref([]);
  33 +
  34 + const getDataSource = () => {};
  35 +</script>
  36 +
  37 +<template>
  38 + <section class="basic-list-container">
  39 + <section class="mb-4 bg-light-50 p-2 x dark:text-gray-300 dark:bg-dark-900">
  40 + <BasicForm @register="registerForm" />
  41 + </section>
  42 + <section class="bg-light-50 p-2 x dark:text-gray-300 dark:bg-dark-900">
  43 + <List
  44 + ref="listElRef"
  45 + :dataSource="dataSource"
  46 + :pagination="pagination"
  47 + :grid="{ gutter: 16, xs: 1, sm: 1, md: 1, lg: 2, xl: 2, xxl: 3, column: 3 }"
  48 + :loading="loading"
  49 + >
  50 + <template #header>
  51 + <section class="flex px-5 justify-between gap-4 min-h-12 items-center">
  52 + <div class="text-lg font-semibold">
  53 + <span>任务列表</span>
  54 + </div>
  55 + <Space>
  56 + <slot name="toolbar"></slot>
  57 + <Tooltip title="刷新">
  58 + <Button type="primary" @click="getDataSource">
  59 + <ReloadOutlined :spin="loading" />
  60 + </Button>
  61 + </Tooltip>
  62 + </Space>
  63 + </section>
  64 + </template>
  65 + <template #renderItem="{ item }">
  66 + <List.Item :key="item.id">
  67 + <slot name="item" :item="item"></slot>
  68 + </List.Item>
  69 + </template>
  70 + </List>
  71 + </section>
  72 + </section>
  73 +</template>
  74 +
  75 +<style lang="less" scoped>
  76 + .basic-list-container {
  77 + :deep(.ant-list-header) {
  78 + padding-top: 0;
  79 + padding-bottom: 8px;
  80 + }
  81 + }
  82 +</style>
... ...
  1 +import type { ComputedRef, Slots } from 'vue';
  2 +import type { BasicTableProps, FetchParams } from '../types/table';
  3 +import { unref, computed } from 'vue';
  4 +import type { FormProps } from '/@/components/Form';
  5 +import { isFunction } from '/@/utils/is';
  6 +
  7 +export function useTableForm(
  8 + propsRef: ComputedRef<BasicTableProps>,
  9 + slots: Slots,
  10 + fetch: (opt?: FetchParams | undefined) => Promise<void>,
  11 + getLoading: ComputedRef<boolean | undefined>
  12 +) {
  13 + const getFormProps = computed((): Partial<FormProps> => {
  14 + const { formConfig } = unref(propsRef);
  15 + const { submitButtonOptions } = formConfig || {};
  16 + return {
  17 + showAdvancedButton: true,
  18 + ...formConfig,
  19 + submitButtonOptions: { loading: unref(getLoading), ...submitButtonOptions },
  20 + compact: true,
  21 + };
  22 + });
  23 +
  24 + const getFormSlotKeys: ComputedRef<string[]> = computed(() => {
  25 + const keys = Object.keys(slots);
  26 + return keys
  27 + .map((item) => (item.startsWith('form-') ? item : null))
  28 + .filter((item) => !!item) as string[];
  29 + });
  30 +
  31 + function replaceFormSlotKey(key: string) {
  32 + if (!key) return '';
  33 + return key?.replace?.(/form\-/, '') ?? '';
  34 + }
  35 +
  36 + function handleSearchInfoChange(info: Recordable) {
  37 + const { handleSearchInfoFn } = unref(propsRef);
  38 + if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) {
  39 + info = handleSearchInfoFn(info) || info;
  40 + }
  41 + fetch({ searchInfo: info, page: 1 });
  42 + }
  43 +
  44 + return {
  45 + getFormProps,
  46 + replaceFormSlotKey,
  47 + getFormSlotKeys,
  48 + handleSearchInfoChange,
  49 + };
  50 +}
... ...
  1 +import { FormProps } from '../../Form';
  2 +
  3 +export const basicListProps = {
  4 + immediate: {
  5 + type: Boolean,
  6 + default: true,
  7 + },
  8 + searchInfo: {
  9 + type: Object as PropType<Recordable>,
  10 + },
  11 + formConfig: {
  12 + type: Object as PropType<Partial<FormProps>>,
  13 + default: null,
  14 + },
  15 + title: {
  16 + type: String,
  17 + },
  18 + titleHelpMessage: {
  19 + type: [String, Array] as PropType<string | string[]>,
  20 + },
  21 + autoCreateKey: {
  22 + type: Boolean,
  23 + default: true,
  24 + },
  25 + api: {
  26 + type: Function as PropType<Fn<any, Promise<any>>>,
  27 + },
  28 + beforeFetch: {
  29 + type: Function as PropType<Fn>,
  30 + },
  31 + afterFetch: {
  32 + type: Function as PropType<Fn>,
  33 + },
  34 + handleSearchInfoFn: {
  35 + type: Function as PropType<Fn>,
  36 + },
  37 +};
... ...
  1 +import { FormProps } from 'ant-design-vue/es/form/Form';
  2 +
  3 +export interface BasicListProps {
  4 + /**
  5 + * @description 立即执行
  6 + */
  7 + immediate?: boolean;
  8 +
  9 + /**
  10 + * @description 额外的请求参数
  11 + */
  12 + searchInfo?: Recordable;
  13 +
  14 + /**
  15 + * @description 搜索表单
  16 + */
  17 + formConfig?: Partial<FormProps>;
  18 +
  19 + /**
  20 + * @description 列表名
  21 + */
  22 + title?: string;
  23 +
  24 + /**
  25 + * @description 列表标题帮助信息
  26 + */
  27 + titleHelpMessage?: string | string[];
  28 +
  29 + /**
  30 + * @description 自动创建key
  31 + */
  32 + autoCreateKey?: boolean;
  33 +
  34 + /**
  35 + * @description 请求接口
  36 + * @param args
  37 + * @returns
  38 + */
  39 + api?: (...args: any) => Promise<any>;
  40 +
  41 + /**
  42 + * @description 请求前对参数处理
  43 + */
  44 + beforeFetch?: Fn;
  45 +
  46 + /**
  47 + * @description 请求后对结果处理
  48 + */
  49 + afterFetch?: Fn;
  50 +
  51 + /**
  52 + * @description 请求前处理搜索参数
  53 + */
  54 + handleSearchInfoFn?: Fn;
  55 +}
... ...
... ... @@ -31,6 +31,6 @@
31 31 <Icon
32 32 v-bind="getBindProps"
33 33 class="justify-center items-center"
34   - :class="getHasPermission ? '' : '!cursor-not-allowed !text-gray-200'"
  34 + :class="getHasPermission ? '' : '!cursor-not-allowed !text-gray-200 !dark:text-gray-700'"
35 35 />
36 36 </template>
... ...
... ... @@ -5,6 +5,10 @@ export const JWT_TOKEN_KEY = 'JWT_TOKEN';
5 5
6 6 export const REFRESH_TOKEN_KEY = 'REFRESH_TOKEN';
7 7
  8 +export const SHARE_JWT_TOKEN_KEY = 'SHARE_JWT_TOKEN';
  9 +
  10 +export const SHARE_REFRESH_TOKEN_KEY = 'SHARE_REFRESH_TOKEN';
  11 +
8 12 export const LOCALE_KEY = 'LOCALE__';
9 13
10 14 // user info key
... ...
... ... @@ -7,4 +7,14 @@ export enum DictEnum {
7 7 DATA_VALIDATE = 'data_validate',
8 8 // 设备类型
9 9 DEVICE_TYPE = 'device_type',
  10 + // 平台管理员启用的权限
  11 + ENABLED_PLATFORM_ADMIN_AUTH = 'enabled_platform_admin_auth',
  12 + // 平台管理员禁用的权限
  13 + DISABLED_PLATFORM_ADMIN_AUTH = 'disabled_platform_admin_auth',
  14 + // 系统管理员启用的权限
  15 + ENABLED_SYSADMIN_AUTH = 'enabled_sysadmin_auth',
  16 + // 租户禁用的权限
  17 + DISABLED_TENANT_AUTH = 'disabled_tenant_auth',
  18 + // 客户禁用的权限
  19 + DISABLE_CUSTOMER_AUTH = 'disabled_customer_auth',
10 20 }
... ...
... ... @@ -5,6 +5,13 @@ export enum DataActionModeEnum {
5 5 DELETE = 'DELETE',
6 6 }
7 7
  8 +export enum DataActionModeNameEnum {
  9 + CREATE = '创建',
  10 + READ = '查看',
  11 + UPDATE = '编辑',
  12 + DELETE = '删除',
  13 +}
  14 +
8 15 export enum TimeUnitEnum {
9 16 SECOND = 'SECOND',
10 17 MINUTE = 'MINUTE',
... ... @@ -16,3 +23,8 @@ export enum TimeUnitNameEnum {
16 23 MINUTE = '分',
17 24 HOUR = '时',
18 25 }
  26 +
  27 +export enum BooleanStringEnum {
  28 + TRUE = 'true',
  29 + FALSE = 'false',
  30 +}
... ...
  1 +import { computed, unref } from 'vue';
  2 +import { useUserStore } from '/@/store/modules/user';
  3 +import { RoleEnum } from '/@/enums/roleEnum';
  4 +
  5 +export const useRole = () => {
  6 + const userStore = useUserStore();
  7 +
  8 + const getRole = computed(() => {
  9 + return userStore.userInfo?.roles![0] as RoleEnum;
  10 + });
  11 +
  12 + const isPlatformAdmin = computed(() => {
  13 + return unref(getRole) === RoleEnum.PLATFORM_ADMIN;
  14 + });
  15 +
  16 + const isCustomerUser = computed(() => {
  17 + return unref(getRole) === RoleEnum.CUSTOMER_USER;
  18 + });
  19 +
  20 + const isTenantAdmin = computed(() => {
  21 + return unref(getRole) === RoleEnum.TENANT_ADMIN;
  22 + });
  23 +
  24 + const isSysadmin = computed(() => {
  25 + return unref(getRole) === RoleEnum.SYS_ADMIN;
  26 + });
  27 +
  28 + return { getRole, isPlatformAdmin, isCustomerUser, isTenantAdmin, isSysadmin };
  29 +};
... ...
1   -import type { GlobConfig } from '/#/config';
2   -
3   -import { warn } from '/@/utils/log';
4   -import { getAppEnvConfig } from '/@/utils/env';
5   -
6   -export const useGlobSetting = (): Readonly<GlobConfig> => {
7   - const {
8   - VITE_GLOB_APP_TITLE,
9   - VITE_GLOB_API_URL,
10   - VITE_GLOB_APP_SHORT_NAME,
11   - VITE_GLOB_API_URL_PREFIX,
12   - VITE_GLOB_UPLOAD_URL,
13   - VITE_GLOB_CONFIGURATION,
14   - VITE_GLOB_WEB_SOCKET,
15   - VITE_GLOB_CONTENT_SECURITY_POLICY,
16   - VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME,
17   - VITE_GLOB_ALARM_NOTIFY_DURATION,
18   - VITE_GLOB_LARGE_DESIGNER,
19   - } = getAppEnvConfig();
20   -
21   - if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
22   - warn(
23   - `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`
24   - );
25   - }
26   -
27   - // Take global configuration
28   - const glob: Readonly<GlobConfig> = {
29   - title: VITE_GLOB_APP_TITLE,
30   - apiUrl: VITE_GLOB_API_URL,
31   - shortName: VITE_GLOB_APP_SHORT_NAME,
32   - urlPrefix: VITE_GLOB_API_URL_PREFIX,
33   - uploadUrl: VITE_GLOB_UPLOAD_URL,
34   - configurationPrefix: VITE_GLOB_CONFIGURATION,
35   - largeDesignerPrefix: VITE_GLOB_LARGE_DESIGNER,
36   - socketUrl: VITE_GLOB_WEB_SOCKET,
37   - securityPolicy: VITE_GLOB_CONTENT_SECURITY_POLICY,
38   - alarmNotifyDuration: VITE_GLOB_ALARM_NOTIFY_DURATION,
39   - alarmPollingInterval: VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME,
40   - };
41   - return glob as Readonly<GlobConfig>;
42   -};
  1 +import type { GlobConfig } from '/#/config';
  2 +
  3 +import { warn } from '/@/utils/log';
  4 +import { getAppEnvConfig } from '/@/utils/env';
  5 +
  6 +export const useGlobSetting = (): Readonly<GlobConfig> => {
  7 + const {
  8 + VITE_GLOB_APP_TITLE,
  9 + VITE_GLOB_API_URL,
  10 + VITE_GLOB_APP_SHORT_NAME,
  11 + VITE_GLOB_API_URL_PREFIX,
  12 + VITE_GLOB_UPLOAD_URL,
  13 + VITE_GLOB_CONFIGURATION,
  14 + VITE_GLOB_WEB_SOCKET,
  15 + VITE_GLOB_CONTENT_SECURITY_POLICY,
  16 + VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME,
  17 + VITE_GLOB_ALARM_NOTIFY_DURATION,
  18 + VITE_GLOB_LARGE_DESIGNER,
  19 + VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND,
  20 + } = getAppEnvConfig();
  21 +
  22 + if (!/[a-zA-Z\_]*/.test(VITE_GLOB_APP_SHORT_NAME)) {
  23 + warn(
  24 + `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`
  25 + );
  26 + }
  27 +
  28 + // Take global configuration
  29 + const glob: Readonly<GlobConfig> = {
  30 + title: VITE_GLOB_APP_TITLE,
  31 + apiUrl: VITE_GLOB_API_URL,
  32 + shortName: VITE_GLOB_APP_SHORT_NAME,
  33 + urlPrefix: VITE_GLOB_API_URL_PREFIX,
  34 + uploadUrl: VITE_GLOB_UPLOAD_URL,
  35 + configurationPrefix: VITE_GLOB_CONFIGURATION,
  36 + largeDesignerPrefix: VITE_GLOB_LARGE_DESIGNER,
  37 + socketUrl: VITE_GLOB_WEB_SOCKET,
  38 + securityPolicy: VITE_GLOB_CONTENT_SECURITY_POLICY,
  39 + alarmNotifyDuration: VITE_GLOB_ALARM_NOTIFY_DURATION,
  40 + alarmPollingInterval: VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME,
  41 + disabledTaskCenterExecuteIntervalUnitSecond:
  42 + VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND,
  43 + };
  44 + return glob as Readonly<GlobConfig>;
  45 +};
... ...
... ... @@ -17,7 +17,7 @@ function joinParentPath(menus: Menu[], parentPath = '') {
17 17 // https://next.router.vuejs.org/guide/essentials/nested-routes.html
18 18 // Note that nested paths that start with / will be treated as a root path.
19 19 // This allows you to leverage the component nesting without having to use a nested URL.
20   - if (!(menu.path.startsWith('/') || isUrl(menu.path))) {
  20 + if (!(menu?.path?.startsWith('/') || isUrl(menu.path))) {
21 21 // path doesn't start with /, nor is it a url, join parent path
22 22 menu.path = `${parentPath}/${menu.path}`;
23 23 }
... ...
... ... @@ -224,7 +224,7 @@ export const usePermissionStore = defineStore({
224 224 // !Simulate to obtain permission codes from the background,
225 225 // this function may only need to be executed once, and the actual project can be put at the right time by itself
226 226 let routeList: AppRouteRecordRaw[] = [];
227   - const userInfo: any = getAuthCache(USER_INFO_KEY);
  227 + const userInfo: any = getAuthCache(USER_INFO_KEY) || { roles: [] };
228 228 const filterMenu = (allMenuList, menuIdsList) => {
229 229 return allMenuList
230 230 .filter((item) => {
... ...
... ... @@ -4,7 +4,14 @@ import { defineStore } from 'pinia';
4 4 import { store } from '/@/store';
5 5 import { RoleEnum } from '/@/enums/roleEnum';
6 6 import { PageEnum } from '/@/enums/pageEnum';
7   -import { JWT_TOKEN_KEY, REFRESH_TOKEN_KEY, ROLES_KEY, USER_INFO_KEY } from '/@/enums/cacheEnum';
  7 +import {
  8 + JWT_TOKEN_KEY,
  9 + REFRESH_TOKEN_KEY,
  10 + ROLES_KEY,
  11 + SHARE_JWT_TOKEN_KEY,
  12 + SHARE_REFRESH_TOKEN_KEY,
  13 + USER_INFO_KEY,
  14 +} from '/@/enums/cacheEnum';
8 15 import { getAuthCache, setAuthCache } from '/@/utils/auth';
9 16 import {
10 17 LoginParams,
... ... @@ -33,6 +40,8 @@ interface UserState {
33 40 lastUpdateTime: number;
34 41 jwtToken?: string;
35 42 refreshToken?: string;
  43 + shareJwtToken?: string;
  44 + shareRefreshToken?: string;
36 45 outTarget?: string;
37 46 }
38 47
... ... @@ -100,6 +109,13 @@ export const useUserStore = defineStore({
100 109 setAuthCache(JWT_TOKEN_KEY, jwtToken);
101 110 setAuthCache(REFRESH_TOKEN_KEY, refreshToken);
102 111 },
  112 +
  113 + storeShareToken(jwtToken: string, refreshToken: string) {
  114 + this.shareJwtToken = jwtToken;
  115 + this.shareRefreshToken = refreshToken;
  116 + setAuthCache(SHARE_JWT_TOKEN_KEY, jwtToken);
  117 + setAuthCache(SHARE_REFRESH_TOKEN_KEY, refreshToken);
  118 + },
103 119 setToken(info: string | undefined) {
104 120 this.jwtToken = info;
105 121 setAuthCache(JWT_TOKEN_KEY, info);
... ... @@ -188,7 +204,7 @@ export const useUserStore = defineStore({
188 204 const roleList = roles.map((item) => item) as RoleEnum[];
189 205 this.setRoleList(roleList);
190 206 try {
191   - if (roleList[0] !== 'SYS_ADMIN') {
  207 + if (roleList[0] !== RoleEnum.SYS_ADMIN && roleList[0] !== RoleEnum.PLATFORM_ADMIN) {
192 208 const res = await getEntitiesId();
193 209 const entityId = res.data[0]?.entityId;
194 210 window.localStorage.setItem('entityId', JSON.stringify(entityId));
... ... @@ -232,6 +248,18 @@ export const useUserStore = defineStore({
232 248 }
233 249 },
234 250
  251 + async doShareRefresh() {
  252 + try {
  253 + const req = { refreshToken: this.shareRefreshToken } as RefreshTokenParams;
  254 + const data = await doRefreshToken(req);
  255 + const { token, refreshToken } = data;
  256 + this.storeToken(token, refreshToken);
  257 + } catch (error) {
  258 + window.location.reload();
  259 + throw error;
  260 + }
  261 + },
  262 +
235 263 /**
236 264 * @description: Confirm before logging out
237 265 */
... ...
1 1 import { Persistent, BasicKeys } from '/@/utils/cache/persistent';
2   -import { CacheTypeEnum } from '/@/enums/cacheEnum';
  2 +import { CacheTypeEnum, SHARE_JWT_TOKEN_KEY, SHARE_REFRESH_TOKEN_KEY } from '/@/enums/cacheEnum';
3 3 import projectSetting from '/@/settings/projectSetting';
4 4 import { JWT_TOKEN_KEY, REFRESH_TOKEN_KEY } from '/@/enums/cacheEnum';
5 5
... ... @@ -26,3 +26,11 @@ export function getJwtToken() {
26 26 export function getRefreshToken() {
27 27 return getAuthCache(REFRESH_TOKEN_KEY);
28 28 }
  29 +
  30 +export function getShareJwtToken() {
  31 + return getAuthCache(SHARE_JWT_TOKEN_KEY);
  32 +}
  33 +
  34 +export function getShareRefreshToken() {
  35 + return getAuthCache(SHARE_REFRESH_TOKEN_KEY);
  36 +}
... ...
... ... @@ -17,6 +17,8 @@ import {
17 17 APP_SESSION_CACHE_KEY,
18 18 MULTIPLE_TABS_KEY,
19 19 MENU_LIST,
  20 + SHARE_JWT_TOKEN_KEY,
  21 + SHARE_REFRESH_TOKEN_KEY,
20 22 } from '/@/enums/cacheEnum';
21 23 import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
22 24 import { toRaw } from 'vue';
... ... @@ -33,6 +35,8 @@ interface BasicStore {
33 35 [PROJ_CFG_KEY]: ProjectConfig;
34 36 [MULTIPLE_TABS_KEY]: RouteLocationNormalized[];
35 37 [MENU_LIST]: any[];
  38 + [SHARE_JWT_TOKEN_KEY]: string;
  39 + [SHARE_REFRESH_TOKEN_KEY]: string;
36 40 }
37 41
38 42 type LocalStore = BasicStore;
... ...
1   -import type { GlobEnvConfig } from '/#/config';
2   -
3   -import { warn } from '/@/utils/log';
4   -import pkg from '../../package.json';
5   -import { getConfigFileName } from '../../build/getConfigFileName';
6   -
7   -export function getCommonStoragePrefix() {
8   - const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig();
9   - return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase();
10   -}
11   -
12   -// Generate cache key according to version
13   -export function getStorageShortName() {
14   - return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase();
15   -}
16   -
17   -export function getAppEnvConfig() {
18   - const ENV_NAME = getConfigFileName(import.meta.env);
19   -
20   - const ENV = (import.meta.env.DEV
21   - ? // Get the global configuration (the configuration will be extracted independently when packaging)
22   - (import.meta.env as unknown as GlobEnvConfig)
23   - : window[ENV_NAME as any]) as unknown as GlobEnvConfig;
24   - const {
25   - VITE_GLOB_APP_TITLE,
26   - VITE_GLOB_API_URL,
27   - VITE_GLOB_APP_SHORT_NAME,
28   - VITE_GLOB_API_URL_PREFIX,
29   - VITE_GLOB_UPLOAD_URL,
30   - VITE_GLOB_CONFIGURATION,
31   - VITE_GLOB_WEB_SOCKET,
32   - VITE_GLOB_CONTENT_SECURITY_POLICY,
33   - VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME,
34   - VITE_GLOB_ALARM_NOTIFY_DURATION,
35   - VITE_GLOB_LARGE_DESIGNER,
36   - } = ENV;
37   -
38   - if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
39   - warn(
40   - `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`
41   - );
42   - }
43   -
44   - return {
45   - VITE_GLOB_APP_TITLE,
46   - VITE_GLOB_API_URL,
47   - VITE_GLOB_APP_SHORT_NAME,
48   - VITE_GLOB_API_URL_PREFIX,
49   - VITE_GLOB_UPLOAD_URL,
50   - VITE_GLOB_CONFIGURATION,
51   - VITE_GLOB_WEB_SOCKET,
52   - VITE_GLOB_CONTENT_SECURITY_POLICY,
53   - VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME,
54   - VITE_GLOB_ALARM_NOTIFY_DURATION,
55   - VITE_GLOB_LARGE_DESIGNER,
56   - };
57   -}
58   -
59   -/**
60   - * @description: Development mode
61   - */
62   -export const devMode = 'development';
63   -
64   -/**
65   - * @description: Production mode
66   - */
67   -export const prodMode = 'production';
68   -
69   -/**
70   - * @description: Get environment variables
71   - * @returns:
72   - * @example:
73   - */
74   -export function getEnv(): string {
75   - return import.meta.env.MODE;
76   -}
77   -
78   -/**
79   - * @description: Is it a development mode
80   - * @returns:
81   - * @example:
82   - */
83   -export function isDevMode(): boolean {
84   - return import.meta.env.DEV;
85   -}
86   -
87   -/**
88   - * @description: Is it a production mode
89   - * @returns:
90   - * @example:
91   - */
92   -export function isProdMode(): boolean {
93   - return import.meta.env.PROD;
94   -}
  1 +import type { GlobEnvConfig } from '/#/config';
  2 +
  3 +import { warn } from '/@/utils/log';
  4 +import pkg from '../../package.json';
  5 +import { getConfigFileName } from '../../build/getConfigFileName';
  6 +
  7 +export function getCommonStoragePrefix() {
  8 + const { VITE_GLOB_APP_SHORT_NAME } = getAppEnvConfig();
  9 + return `${VITE_GLOB_APP_SHORT_NAME}__${getEnv()}`.toUpperCase();
  10 +}
  11 +
  12 +// Generate cache key according to version
  13 +export function getStorageShortName() {
  14 + return `${getCommonStoragePrefix()}${`__${pkg.version}`}__`.toUpperCase();
  15 +}
  16 +
  17 +export function getAppEnvConfig() {
  18 + const ENV_NAME = getConfigFileName(import.meta.env);
  19 +
  20 + const ENV = (import.meta.env.DEV
  21 + ? // Get the global configuration (the configuration will be extracted independently when packaging)
  22 + (import.meta.env as unknown as GlobEnvConfig)
  23 + : window[ENV_NAME as any]) as unknown as GlobEnvConfig;
  24 + const {
  25 + VITE_GLOB_APP_TITLE,
  26 + VITE_GLOB_API_URL,
  27 + VITE_GLOB_APP_SHORT_NAME,
  28 + VITE_GLOB_API_URL_PREFIX,
  29 + VITE_GLOB_UPLOAD_URL,
  30 + VITE_GLOB_CONFIGURATION,
  31 + VITE_GLOB_WEB_SOCKET,
  32 + VITE_GLOB_CONTENT_SECURITY_POLICY,
  33 + VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME,
  34 + VITE_GLOB_ALARM_NOTIFY_DURATION,
  35 + VITE_GLOB_LARGE_DESIGNER,
  36 + VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND,
  37 + } = ENV;
  38 +
  39 + if (!/^[a-zA-Z\_]*$/.test(VITE_GLOB_APP_SHORT_NAME)) {
  40 + warn(
  41 + `VITE_GLOB_APP_SHORT_NAME Variables can only be characters/underscores, please modify in the environment variables and re-running.`
  42 + );
  43 + }
  44 +
  45 + return {
  46 + VITE_GLOB_APP_TITLE,
  47 + VITE_GLOB_API_URL,
  48 + VITE_GLOB_APP_SHORT_NAME,
  49 + VITE_GLOB_API_URL_PREFIX,
  50 + VITE_GLOB_UPLOAD_URL,
  51 + VITE_GLOB_CONFIGURATION,
  52 + VITE_GLOB_WEB_SOCKET,
  53 + VITE_GLOB_CONTENT_SECURITY_POLICY,
  54 + VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME,
  55 + VITE_GLOB_ALARM_NOTIFY_DURATION,
  56 + VITE_GLOB_LARGE_DESIGNER,
  57 + VITE_GLOB_DISABLED_TASK_CENTER_EXECUTE_INTERVAL_UNIT_SECOND,
  58 + };
  59 +}
  60 +
  61 +/**
  62 + * @description: Development mode
  63 + */
  64 +export const devMode = 'development';
  65 +
  66 +/**
  67 + * @description: Production mode
  68 + */
  69 +export const prodMode = 'production';
  70 +
  71 +/**
  72 + * @description: Get environment variables
  73 + * @returns:
  74 + * @example:
  75 + */
  76 +export function getEnv(): string {
  77 + return import.meta.env.MODE;
  78 +}
  79 +
  80 +/**
  81 + * @description: Is it a development mode
  82 + * @returns:
  83 + * @example:
  84 + */
  85 +export function isDevMode(): boolean {
  86 + return import.meta.env.DEV;
  87 +}
  88 +
  89 +/**
  90 + * @description: Is it a production mode
  91 + * @returns:
  92 + * @example:
  93 + */
  94 +export function isProdMode(): boolean {
  95 + return import.meta.env.PROD;
  96 +}
... ...
... ... @@ -80,7 +80,6 @@ export class VAxios {
80 80 private refreshTokenBeforeReq(doRefreshTokenApi: () => Promise<unknown>): Promise<unknown> {
81 81 // 创建一个未完成的promise,把改变状态的resolve方法交给请求token结束后执行
82 82 const promise = new Promise((resolve) => {
83   - console.log('等待新token');
84 83 // 等待队列放的是一个回调函数,来延迟resolve的执行,以此控制promise状态的改变
85 84 this.waitingQueue.push(() => resolve(null));
86 85 });
... ... @@ -88,7 +87,6 @@ export class VAxios {
88 87 this.refreshing = true;
89 88 // 模拟请求刷新Token接口,当接口返回数据时执行then方法 TODO 添加catch捕获异常
90 89 doRefreshTokenApi().then(() => {
91   - console.log('刷新token成功,放行队列中的请求', this.waitingQueue.length);
92 90 this.refreshing = false;
93 91 this.waitingQueue.forEach((cb) => cb());
94 92 this.waitingQueue.length = 0;
... ... @@ -117,12 +115,19 @@ export class VAxios {
117 115 this.axiosInstance.interceptors.request.use(async (config: AxiosRequestConfig) => {
118 116 // If cancel repeat request is turned on, then cancel repeat request is prohibited
119 117 const userStore = useUserStore();
120   - if (userStore && userStore.jwtToken) {
  118 + if (userStore) {
121 119 try {
122   - const res = jwt_decode(userStore.jwtToken) as JwtModel;
123   - const currentTime = (new Date().getTime() + (config.timeout || 0)) / 1000;
124   - if (currentTime >= res.exp && this.isNeedTokenURL(config.url)) {
125   - await this.refreshTokenBeforeReq(userStore.doRefresh);
  120 + const { requestOptions = {} } = config;
  121 + const { withShareToken, withToken } = requestOptions;
  122 + const token = withShareToken && withToken ? userStore.shareJwtToken : userStore.jwtToken;
  123 + const doRefresh =
  124 + withShareToken && withToken ? userStore.doShareRefresh : userStore.doRefresh;
  125 + if (token) {
  126 + const res = jwt_decode(token) as JwtModel;
  127 + const currentTime = (new Date().getTime() + (config.timeout || 0)) / 1000;
  128 + if (currentTime >= res.exp && this.isNeedTokenURL(config.url!)) {
  129 + await this.refreshTokenBeforeReq(doRefresh);
  130 + }
126 131 }
127 132 } catch (error) {
128 133 userStore.logout();
... ... @@ -244,7 +249,6 @@ export class VAxios {
244 249 const { requestOptions } = this.options;
245 250
246 251 const opt: RequestOptions = Object.assign({}, requestOptions, options);
247   -
248 252 const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {};
249 253 if (beforeRequestHook && isFunction(beforeRequestHook)) {
250 254 conf = beforeRequestHook(conf, opt);
... ...
1 1 /**
2 2 * Data processing class, can be configured according to the project
3 3 */
4   -import type { AxiosRequestConfig, AxiosResponse } from 'axios';
  4 +import type { AxiosRequestConfig as OriginalAxiosRequestConfig, AxiosResponse } from 'axios';
5 5 import type { RequestOptions, Result } from '/#/axios';
6 6
7 7 export interface CreateAxiosOptions extends AxiosRequestConfig {
... ... @@ -10,6 +10,7 @@ export interface CreateAxiosOptions extends AxiosRequestConfig {
10 10 transform?: AxiosTransform;
11 11 requestOptions?: RequestOptions;
12 12 }
  13 +export type AxiosRequestConfig = OriginalAxiosRequestConfig & { requestOptions?: RequestOptions };
13 14
14 15 export abstract class AxiosTransform {
15 16 /**
... ...
... ... @@ -10,7 +10,7 @@ import { useGlobSetting } from '/@/hooks/setting';
10 10 import { useMessage } from '/@/hooks/web/useMessage';
11 11 import { RequestEnum, ContentTypeEnum } from '/@/enums/httpEnum';
12 12 import { isString } from '/@/utils/is';
13   -import { getJwtToken } from '/@/utils/auth';
  13 +import { getJwtToken, getShareJwtToken } from '/@/utils/auth';
14 14 import { setObjToUrlParams, deepMerge } from '/@/utils';
15 15 import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog';
16 16 import { useI18n } from '/@/hooks/web/useI18n';
... ... @@ -92,12 +92,23 @@ const transform: AxiosTransform = {
92 92 */
93 93 requestInterceptors: (config, options) => {
94 94 // 请求之前处理config
95   - const token = getJwtToken();
96   - if (token && (config as Recordable)?.requestOptions?.withToken !== false) {
97   - // jwt token
98   - config.headers['X-Authorization'] = options.authenticationScheme
99   - ? `${options.authenticationScheme} ${token}`
100   - : token;
  95 + const { requestOptions } = config;
  96 + const { withShareToken } = requestOptions || {};
  97 + const { requestOptions: { withToken } = {} } = options;
  98 + if (withToken !== false) {
  99 + const shareToken = getShareJwtToken();
  100 + if (withShareToken && shareToken) {
  101 + config.headers['X-Authorization'] = options.authenticationScheme
  102 + ? `${options.authenticationScheme} ${shareToken}`
  103 + : shareToken;
  104 + } else {
  105 + const token = getJwtToken();
  106 + if (token) {
  107 + config.headers['X-Authorization'] = options.authenticationScheme
  108 + ? `${options.authenticationScheme} ${token}`
  109 + : token;
  110 + }
  111 + }
101 112 }
102 113 return config;
103 114 },
... ... @@ -194,7 +205,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
194 205 ignoreCancelToken: true,
195 206 // 是否携带token
196 207 withToken: true,
197   - },
  208 + } as RequestOptions,
198 209 },
199 210 opt || {}
200 211 )
... ...
1 1 import { BasicColumn, FormSchema } from '/@/components/Table';
2   -import { getOrganizationList } from '/@/api/system/system';
3   -import { copyTransFun } from '/@/utils/fnUtils';
4 2 import { findDictItemByCode } from '/@/api/system/dict';
5   -
  3 +import { useComponentRegister } from '/@/components/Form';
  4 +import { OrgTreeSelect } from '../../common/OrgTreeSelect';
  5 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
6 6 // 表格列数据
7 7 export const columns: BasicColumn[] = [
8 8 {
... ... @@ -98,19 +98,8 @@ export const formSchema: FormSchema[] = [
98 98 {
99 99 field: 'organizationId',
100 100 label: '所属组织',
101   - component: 'ApiTreeSelect',
  101 + component: 'OrgTreeSelect',
102 102 required: true,
103   - componentProps: () => {
104   - return {
105   - maxLength: 250,
106   - placeholder: '请选择所属组织',
107   - api: async () => {
108   - const data = await getOrganizationList();
109   - copyTransFun(data as any as any[]);
110   - return data;
111   - },
112   - };
113   - },
114 103 },
115 104 {
116 105 field: 'alarmContactId',
... ...
1 1 import { BasicColumn, FormSchema } from '/@/components/Table';
2   -import { getOrganizationList } from '/@/api/system/system';
3   -import { copyTransFun } from '/@/utils/fnUtils';
4 2 import { emailRule, phoneRule } from '/@/utils/rules';
5   -
  3 +import { useComponentRegister } from '/@/components/Form';
  4 +import { OrgTreeSelect } from '../../common/OrgTreeSelect';
  5 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
6 6 // 表格列数据
7 7 export const columns: BasicColumn[] = [
8 8 {
... ... @@ -78,14 +78,7 @@ export const formSchema: FormSchema[] = [
78 78 field: 'organizationId',
79 79 label: '所属组织',
80 80 required: true,
81   - component: 'ApiTreeSelect',
82   - componentProps: {
83   - api: async () => {
84   - const data = await getOrganizationList();
85   - copyTransFun(data as any as any[]);
86   - return data;
87   - },
88   - },
  81 + component: 'OrgTreeSelect',
89 82 },
90 83 {
91 84 field: 'phone',
... ...
1   -import { BasicColumn, FormSchema } from '/@/components/Table';
2   -import { getOrganizationList } from '/@/api/system/system';
3   -import { copyTransFun } from '/@/utils/fnUtils';
4   -import type { FormSchema as QFormSchema } from '/@/components/Form/index';
5   -
6   -import { CameraVideoUrl, CameraMaxLength } from '/@/utils/rules';
7   -import { h } from 'vue';
8   -import SnHelpMessage from './SnHelpMessage.vue';
9   -
10   -export enum CameraPermission {
11   - PREVIEW = 'api:yt:video:get',
12   - CREATE = 'api:yt:video:post',
13   - UPDATE = 'api:yt:video:update',
14   - DELETE = 'api:yt:video:delete',
15   -}
16   -
17   -export enum AccessMode {
18   - ManuallyEnter = 0,
19   - Streaming = 1,
20   -}
21   -
22   -export enum PlayProtocol {
23   - HTTP = 0,
24   - HTTPS = 1,
25   -}
26   -
27   -export enum StreamType {
28   - MASTER = 0,
29   - CHILD = 1,
30   - THIRD = 2,
31   -}
32   -
33   -export enum PageMode {
34   - SPLIT_SCREEN_MODE = 'splitScreen',
35   - LIST_MODE = 'listMode',
36   - FULL_SCREEN_MODE = 'fullScreenMode',
37   -}
38   -
39   -export enum MediaType {
40   - MP4 = 'mp4',
41   - M3U8 = 'm3u8',
42   -}
43   -
44   -// 表格列数据
45   -export const columns: BasicColumn[] = [
46   - {
47   - title: '封面',
48   - dataIndex: 'avatar',
49   - width: 80,
50   - slots: { customRender: 'img' },
51   - },
52   - {
53   - title: '名字',
54   - dataIndex: 'name',
55   - width: 120,
56   - },
57   - {
58   - title: '摄像头编号/监控点编号',
59   - dataIndex: 'sn',
60   - width: 220,
61   - },
62   - {
63   - title: '视频流',
64   - dataIndex: 'videoUrl',
65   - width: 120,
66   - },
67   - {
68   - title: '所属组织',
69   - dataIndex: 'organizationName',
70   - width: 160,
71   - },
72   - {
73   - title: '获取方式',
74   - dataIndex: 'accessMode',
75   - width: 100,
76   - slots: { customRender: 'accessMode' },
77   - },
78   - {
79   - title: '创建时间',
80   - dataIndex: 'createTime',
81   - width: 140,
82   - },
83   -];
84   -
85   -// 查询字段
86   -export const searchFormSchema: FormSchema[] = [
87   - {
88   - field: 'name',
89   - label: '摄像头名字',
90   - component: 'Input',
91   - colProps: { span: 8 },
92   - componentProps: {
93   - maxLength: 36,
94   - placeholder: '请输入摄像头名字',
95   - },
96   - },
97   -];
98   -
99   -// 弹框配置项
100   -export const formSchema: QFormSchema[] = [
101   - {
102   - field: 'avatar',
103   - label: '视频封面',
104   - slot: 'iconSelect',
105   - component: 'Input',
106   - },
107   - {
108   - field: 'name',
109   - label: '视频名字',
110   - required: true,
111   - component: 'Input',
112   - componentProps: {
113   - placeholder: '请输入视频名字',
114   - maxLength: 30,
115   - },
116   - rules: [...CameraMaxLength, { required: true, message: '视频名是必填项' }],
117   - },
118   - {
119   - field: 'organizationId',
120   - label: '所属组织',
121   - required: true,
122   - component: 'ApiTreeSelect',
123   - componentProps: {
124   - api: async () => {
125   - const data = await getOrganizationList();
126   - copyTransFun(data as any as any[]);
127   - return data;
128   - },
129   - },
130   - },
131   - {
132   - label: '视频流获取方式',
133   - field: 'accessMode',
134   - component: 'RadioGroup',
135   - rules: [{ required: true, message: '视频流获取方式为必选项', type: 'number' }],
136   - defaultValue: AccessMode.ManuallyEnter,
137   - componentProps({ formActionType }) {
138   - return {
139   - defaultValue: AccessMode.ManuallyEnter,
140   - placeholder: '请选择视频流获取方式',
141   - options: [
142   - { label: '手动输入', value: AccessMode.ManuallyEnter },
143   - { label: '流媒体获取', value: AccessMode.Streaming },
144   - ],
145   - onChange() {
146   - formActionType.setFieldsValue({
147   - brand: null,
148   - sn: null,
149   - videoUrl: null,
150   - videoPlatformId: null,
151   - });
152   - },
153   - };
154   - },
155   - },
156   - {
157   - field: 'brand',
158   - label: '视频厂家',
159   - component: 'Input',
160   - ifShow({ values }) {
161   - return values.accessMode === AccessMode.ManuallyEnter;
162   - },
163   - componentProps: {
164   - maxLength: 36,
165   - placeholder: '请输入视频厂家',
166   - },
167   - },
168   - {
169   - field: 'sn',
170   - label: '摄像头编号',
171   - required: true,
172   - component: 'Input',
173   - rules: [...CameraVideoUrl, { required: true, message: '摄像头编号是必填项' }],
174   - ifShow({ values }) {
175   - return values.accessMode === AccessMode.ManuallyEnter;
176   - },
177   - componentProps: {
178   - maxLength: 36,
179   - placeholder: '请输入摄像头编号',
180   - },
181   - },
182   - {
183   - field: 'videoUrl',
184   - label: '视频流',
185   - component: 'Input',
186   - required: true,
187   - ifShow({ values }) {
188   - return values.accessMode === AccessMode.ManuallyEnter;
189   - },
190   - componentProps: {
191   - placeholder: '请输入视频流',
192   - maxLength: 255,
193   - },
194   - rules: [{ required: true, message: '视频流是必填项' }, ...CameraVideoUrl],
195   - },
196   -
197   - {
198   - field: 'videoPlatformId',
199   - label: '流媒体配置',
200   - component: 'Select',
201   - ifShow({ values }) {
202   - return values.accessMode === AccessMode.Streaming;
203   - },
204   - slot: 'videoPlatformIdSlot',
205   - componentProps: {
206   - placeholder: '请选择流媒体配置',
207   - },
208   - },
209   - {
210   - field: 'streamType',
211   - label: '码流',
212   - component: 'RadioGroup',
213   - defaultValue: StreamType.MASTER,
214   - ifShow({ values }) {
215   - return values.accessMode === AccessMode.Streaming;
216   - },
217   - componentProps: {
218   - placeholder: '请选择码流',
219   - defaultValue: StreamType.MASTER,
220   - options: [
221   - { label: '主码流', value: StreamType.MASTER },
222   - { label: '子码流', value: StreamType.CHILD },
223   - { label: '第三码流', value: StreamType.THIRD },
224   - ],
225   - },
226   - },
227   - {
228   - field: 'playProtocol',
229   - label: '播放协议',
230   - component: 'RadioGroup',
231   - defaultValue: PlayProtocol.HTTP,
232   - ifShow({ values }) {
233   - return values.accessMode === AccessMode.Streaming;
234   - },
235   - helpMessage: ['平台使用https的hls协议,需联系海康开放平台专家支持。'],
236   - componentProps: {
237   - placeholder: '请选择播放协议',
238   - defaultValue: PlayProtocol.HTTP,
239   - options: [
240   - { label: 'http', value: PlayProtocol.HTTP },
241   - { label: 'https', value: PlayProtocol.HTTPS },
242   - ],
243   - },
244   - },
245   - {
246   - field: 'sn',
247   - label: h(SnHelpMessage) as any,
248   - component: 'Input',
249   - rules: [...CameraVideoUrl, { required: true, message: '摄像头编号是必填项' }],
250   - ifShow({ values }) {
251   - return values.accessMode === AccessMode.Streaming;
252   - },
253   - componentProps: {
254   - placeholder: '请输入监控点编号',
255   - },
256   - },
257   -];
  1 +import { BasicColumn, FormSchema } from '/@/components/Table';
  2 +import { FormSchema as QFormSchema, useComponentRegister } from '/@/components/Form/index';
  3 +
  4 +import { CameraVideoUrl, CameraMaxLength } from '/@/utils/rules';
  5 +import { h } from 'vue';
  6 +import SnHelpMessage from './SnHelpMessage.vue';
  7 +import { OrgTreeSelect } from '../../common/OrgTreeSelect';
  8 +
  9 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
  10 +
  11 +export enum CameraPermission {
  12 + PREVIEW = 'api:yt:video:get',
  13 + CREATE = 'api:yt:video:post',
  14 + UPDATE = 'api:yt:video:update',
  15 + DELETE = 'api:yt:video:delete',
  16 +}
  17 +
  18 +export enum AccessMode {
  19 + ManuallyEnter = 0,
  20 + Streaming = 1,
  21 +}
  22 +
  23 +export enum PlayProtocol {
  24 + HTTP = 0,
  25 + HTTPS = 1,
  26 +}
  27 +
  28 +export enum StreamType {
  29 + MASTER = 0,
  30 + CHILD = 1,
  31 + THIRD = 2,
  32 +}
  33 +
  34 +export enum PageMode {
  35 + SPLIT_SCREEN_MODE = 'splitScreen',
  36 + LIST_MODE = 'listMode',
  37 + FULL_SCREEN_MODE = 'fullScreenMode',
  38 +}
  39 +
  40 +export enum MediaType {
  41 + MP4 = 'mp4',
  42 + M3U8 = 'm3u8',
  43 +}
  44 +
  45 +// 表格列数据
  46 +export const columns: BasicColumn[] = [
  47 + {
  48 + title: '封面',
  49 + dataIndex: 'avatar',
  50 + width: 80,
  51 + slots: { customRender: 'img' },
  52 + },
  53 + {
  54 + title: '名字',
  55 + dataIndex: 'name',
  56 + width: 120,
  57 + },
  58 + {
  59 + title: '摄像头编号/监控点编号',
  60 + dataIndex: 'sn',
  61 + width: 220,
  62 + },
  63 + {
  64 + title: '视频流',
  65 + dataIndex: 'videoUrl',
  66 + width: 120,
  67 + },
  68 + {
  69 + title: '所属组织',
  70 + dataIndex: 'organizationName',
  71 + width: 160,
  72 + },
  73 + {
  74 + title: '获取方式',
  75 + dataIndex: 'accessMode',
  76 + width: 100,
  77 + slots: { customRender: 'accessMode' },
  78 + },
  79 + {
  80 + title: '创建时间',
  81 + dataIndex: 'createTime',
  82 + width: 140,
  83 + },
  84 +];
  85 +
  86 +// 查询字段
  87 +export const searchFormSchema: FormSchema[] = [
  88 + {
  89 + field: 'name',
  90 + label: '摄像头名字',
  91 + component: 'Input',
  92 + colProps: { span: 8 },
  93 + componentProps: {
  94 + maxLength: 36,
  95 + placeholder: '请输入摄像头名字',
  96 + },
  97 + },
  98 +];
  99 +
  100 +// 弹框配置项
  101 +export const formSchema: QFormSchema[] = [
  102 + {
  103 + field: 'avatar',
  104 + label: '视频封面',
  105 + slot: 'iconSelect',
  106 + component: 'Input',
  107 + },
  108 + {
  109 + field: 'name',
  110 + label: '视频名字',
  111 + required: true,
  112 + component: 'Input',
  113 + componentProps: {
  114 + placeholder: '请输入视频名字',
  115 + maxLength: 30,
  116 + },
  117 + rules: [...CameraMaxLength, { required: true, message: '视频名是必填项' }],
  118 + },
  119 + {
  120 + field: 'organizationId',
  121 + label: '所属组织',
  122 + required: true,
  123 + component: 'OrgTreeSelect',
  124 + },
  125 + {
  126 + label: '视频流获取方式',
  127 + field: 'accessMode',
  128 + component: 'RadioGroup',
  129 + rules: [{ required: true, message: '视频流获取方式为必选项', type: 'number' }],
  130 + defaultValue: AccessMode.ManuallyEnter,
  131 + componentProps({ formActionType }) {
  132 + return {
  133 + defaultValue: AccessMode.ManuallyEnter,
  134 + placeholder: '请选择视频流获取方式',
  135 + options: [
  136 + { label: '手动输入', value: AccessMode.ManuallyEnter },
  137 + { label: '流媒体获取', value: AccessMode.Streaming },
  138 + ],
  139 + onChange() {
  140 + formActionType.setFieldsValue({
  141 + brand: null,
  142 + sn: null,
  143 + videoUrl: null,
  144 + videoPlatformId: null,
  145 + });
  146 + },
  147 + };
  148 + },
  149 + },
  150 + {
  151 + field: 'brand',
  152 + label: '视频厂家',
  153 + component: 'Input',
  154 + ifShow({ values }) {
  155 + return values.accessMode === AccessMode.ManuallyEnter;
  156 + },
  157 + componentProps: {
  158 + maxLength: 36,
  159 + placeholder: '请输入视频厂家',
  160 + },
  161 + },
  162 + {
  163 + field: 'sn',
  164 + label: '摄像头编号',
  165 + required: true,
  166 + component: 'Input',
  167 + rules: [...CameraVideoUrl, { required: true, message: '摄像头编号是必填项' }],
  168 + ifShow({ values }) {
  169 + return values.accessMode === AccessMode.ManuallyEnter;
  170 + },
  171 + componentProps: {
  172 + maxLength: 36,
  173 + placeholder: '请输入摄像头编号',
  174 + },
  175 + },
  176 + {
  177 + field: 'videoUrl',
  178 + label: '视频流',
  179 + component: 'Input',
  180 + required: true,
  181 + ifShow({ values }) {
  182 + return values.accessMode === AccessMode.ManuallyEnter;
  183 + },
  184 + componentProps: {
  185 + placeholder: '请输入视频流',
  186 + maxLength: 255,
  187 + },
  188 + rules: [{ required: true, message: '视频流是必填项' }, ...CameraVideoUrl],
  189 + },
  190 +
  191 + {
  192 + field: 'videoPlatformId',
  193 + label: '流媒体配置',
  194 + component: 'Select',
  195 + ifShow({ values }) {
  196 + return values.accessMode === AccessMode.Streaming;
  197 + },
  198 + slot: 'videoPlatformIdSlot',
  199 + componentProps: {
  200 + placeholder: '请选择流媒体配置',
  201 + },
  202 + },
  203 + {
  204 + field: 'streamType',
  205 + label: '码流',
  206 + component: 'RadioGroup',
  207 + defaultValue: StreamType.MASTER,
  208 + ifShow({ values }) {
  209 + return values.accessMode === AccessMode.Streaming;
  210 + },
  211 + componentProps: {
  212 + placeholder: '请选择码流',
  213 + defaultValue: StreamType.MASTER,
  214 + options: [
  215 + { label: '主码流', value: StreamType.MASTER },
  216 + { label: '子码流', value: StreamType.CHILD },
  217 + { label: '第三码流', value: StreamType.THIRD },
  218 + ],
  219 + },
  220 + },
  221 + {
  222 + field: 'playProtocol',
  223 + label: '播放协议',
  224 + component: 'RadioGroup',
  225 + defaultValue: PlayProtocol.HTTP,
  226 + ifShow({ values }) {
  227 + return values.accessMode === AccessMode.Streaming;
  228 + },
  229 + helpMessage: ['平台使用https的hls协议,需联系海康开放平台专家支持。'],
  230 + componentProps: {
  231 + placeholder: '请选择播放协议',
  232 + defaultValue: PlayProtocol.HTTP,
  233 + options: [
  234 + { label: 'http', value: PlayProtocol.HTTP },
  235 + { label: 'https', value: PlayProtocol.HTTPS },
  236 + ],
  237 + },
  238 + },
  239 + {
  240 + field: 'sn',
  241 + label: h(SnHelpMessage) as any,
  242 + component: 'Input',
  243 + rules: [...CameraVideoUrl, { required: true, message: '摄像头编号是必填项' }],
  244 + ifShow({ values }) {
  245 + return values.accessMode === AccessMode.Streaming;
  246 + },
  247 + componentProps: {
  248 + placeholder: '请输入监控点编号',
  249 + },
  250 + },
  251 +];
... ...
  1 +export { default as OrgTreeSelect } from './index.vue';
... ...
  1 +<script lang="ts" setup>
  2 + import { ApiTreeSelect } from '/@/components/Form';
  3 + import { Button } from 'ant-design-vue';
  4 + import { getOrganizationList } from '/@/api/system/system';
  5 + import { computed, ref, unref } from 'vue';
  6 + import OrganizationDrawer from '/@/views/system/organization/OrganizationDrawer.vue';
  7 + import { useDrawer } from '/@/components/Drawer';
  8 + import { OrganizationListItem } from '/@/api/system/model/systemModel';
  9 + import { useRole } from '/@/hooks/business/useRole';
  10 +
  11 + const [registerDrawer, { openDrawer }] = useDrawer();
  12 + const { isCustomerUser } = useRole();
  13 +
  14 + const props = withDefaults(
  15 + defineProps<{
  16 + value?: string | string[];
  17 + apiTreeSelectProps?: Recordable;
  18 + showCreate?: boolean;
  19 + }>(),
  20 + {
  21 + showCreate: true,
  22 + }
  23 + );
  24 +
  25 + const needReload = ref(true);
  26 +
  27 + const emit = defineEmits(['change']);
  28 +
  29 + const handleOpenCreate = () => {
  30 + openDrawer(true, { isUpdate: false });
  31 + };
  32 +
  33 + const timespan = ref(Date.now());
  34 +
  35 + const orgList = ref<Recordable[]>([]);
  36 +
  37 + const getBindProps = computed<Recordable>(() => {
  38 + const { value, apiTreeSelectProps = {} } = props;
  39 + const { params = {} } = apiTreeSelectProps;
  40 + return {
  41 + replaceFields: { children: 'children', key: 'id', title: 'name', value: 'id' },
  42 + getPopupContainer: () => document.body,
  43 + placeholder: '请选择所属组织',
  44 + maxLength: 250,
  45 + ...apiTreeSelectProps,
  46 + value,
  47 + api: async (params: OrganizationListItem) => {
  48 + try {
  49 + if (!unref(needReload)) return unref(orgList);
  50 + const result = ((await getOrganizationList(params)) as unknown as Recordable[]) || [];
  51 + orgList.value = result;
  52 + needReload.value = false;
  53 + return result;
  54 + } catch (error) {
  55 + return unref(orgList);
  56 + }
  57 + },
  58 + params: { ...params, _t: unref(timespan) },
  59 + onChange: (...args: any[]) => {
  60 + emit('change', ...args);
  61 + },
  62 + };
  63 + });
  64 +
  65 + const getShowCreate = computed(() => {
  66 + const { showCreate } = props;
  67 + return unref(isCustomerUser) ? false : showCreate;
  68 + });
  69 +
  70 + const handleReload = () => {
  71 + needReload.value = true;
  72 + timespan.value = Date.now();
  73 + };
  74 +</script>
  75 +
  76 +<template>
  77 + <section class="flex">
  78 + <ApiTreeSelect v-bind="getBindProps" />
  79 + <Button v-if="getShowCreate" type="link" @click="handleOpenCreate">新增组织</Button>
  80 + <OrganizationDrawer v-if="getShowCreate" @register="registerDrawer" @success="handleReload" />
  81 + </section>
  82 +</template>
... ...
... ... @@ -25,7 +25,7 @@ export const schemas: FormSchema[] = [
25 25 {
26 26 field: FieldsEnum.ACCESS_CREDENTIALS,
27 27 label: '访问凭证',
28   - component: 'Input',
  28 + component: 'InputPassword',
29 29 ifShow: ({ model }) => model[FieldsEnum.IS_SHARE],
30 30 componentProps: {
31 31 maxLength: 64,
... ...
... ... @@ -3,9 +3,13 @@
3 3 import { BasicModal, useModalInner } from '/@/components/Modal';
4 4 import { FieldsEnum, schemas, ViewTypeEnum } from './config';
5 5 import { DataBoardRecord } from '/@/api/dataBoard/model';
6   - import { Alert } from 'ant-design-vue';
  6 + import { Alert, Button, Tooltip } from 'ant-design-vue';
7 7 import { ref, unref } from 'vue';
8 8 import { nextTick } from 'vue';
  9 + import { ModalParamsType } from '/#/utils';
  10 + import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
  11 + import { useMessage } from '/@/hooks/web/useMessage';
  12 + import { computed } from 'vue';
9 13
10 14 const props = defineProps<{
11 15 shareApi?: (...args: any[]) => Promise<any>;
... ... @@ -13,15 +17,20 @@
13 17
14 18 const loading = ref(false);
15 19 const record = ref<DataBoardRecord>({} as unknown as DataBoardRecord);
  20 + const link = ref('');
16 21
17   - const [register, { closeModal }] = useModalInner(async (data: DataBoardRecord) => {
18   - record.value = data;
19   - await nextTick();
20   - setFieldsValue({
21   - [FieldsEnum.IS_SHARE]: data.viewType === ViewTypeEnum.PUBLIC_VIEW,
22   - [FieldsEnum.ACCESS_CREDENTIALS]: data.accessCredentials,
23   - });
24   - });
  22 + const [register, { closeModal }] = useModalInner(
  23 + async (data: ModalParamsType<DataBoardRecord>) => {
  24 + const { record: value, href } = data;
  25 + record.value = value;
  26 + link.value = href;
  27 + await nextTick();
  28 + setFieldsValue({
  29 + [FieldsEnum.IS_SHARE]: value.viewType === ViewTypeEnum.PUBLIC_VIEW,
  30 + [FieldsEnum.ACCESS_CREDENTIALS]: value.accessCredentials,
  31 + });
  32 + }
  33 + );
25 34
26 35 const [registerForm, { getFieldsValue, setFieldsValue }] = useForm({
27 36 schemas,
... ... @@ -32,6 +41,11 @@
32 41
33 42 const emit = defineEmits(['register', 'success']);
34 43
  44 + const handleOpenLink = () => {
  45 + window.open(unref(link));
  46 + };
  47 + const { createMessage } = useMessage();
  48 +
35 49 const handleSubmit = async () => {
36 50 try {
37 51 loading.value = true;
... ... @@ -40,10 +54,23 @@
40 54 await props?.shareApi?.({ id, ...value });
41 55 closeModal();
42 56 emit('success');
  57 + createMessage.success('操作成功');
43 58 } finally {
44 59 loading.value = false;
45 60 }
46 61 };
  62 +
  63 + const isPublicState = computed(() => {
  64 + return unref(record).viewType === ViewTypeEnum.PUBLIC_VIEW;
  65 + });
  66 +
  67 + const { clipboardRef, isSuccessRef } = useCopyToClipboard();
  68 + const handleCopy = () => {
  69 + clipboardRef.value = unref(link);
  70 + if (unref(isSuccessRef)) {
  71 + createMessage.success('复制成功~');
  72 + }
  73 + };
47 74 </script>
48 75
49 76 <template>
... ... @@ -54,5 +81,27 @@
54 81 message="私有视图只有项目成员可以浏览,公开视图拥有一个公开的 URL,任何人无需登录即可浏览."
55 82 />
56 83 <BasicForm @register="registerForm" />
  84 + <section>
  85 + <div v-if="isPublicState" class="mt-4">
  86 + <span> 设置分享后, 可通过如下 </span>
  87 + <Button type="link" class="!px-1" @click="handleOpenLink"> 链接 </Button>
  88 + <span>访问:</span>
  89 + </div>
  90 + <Tooltip v-if="isPublicState" title="点击复制链接">
  91 + <div
  92 + class="px-4 py-2 my-2 bg-gray-50 font-semibold tracking-wider text-base cursor-pointer truncate"
  93 + @click="handleCopy"
  94 + >
  95 + <span>{{ link }}</span>
  96 + </div>
  97 + </Tooltip>
  98 +
  99 + <Alert type="warning">
  100 + <template #message>
  101 + <span class="font-bold mr-1">提示:</span>
  102 + <span>不要忘记将相关设备 <span class="mx-1 font-bold">公开</span> 以访问其数据</span>
  103 + </template>
  104 + </Alert>
  105 + </section>
57 106 </BasicModal>
58 107 </template>
... ...
1 1 import { BasicColumn, FormSchema } from '/@/components/Table';
2   -import { getOrganizationList } from '/@/api/system/system';
3   -import { copyTransFun } from '/@/utils/fnUtils';
4 2 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
5 3 import { createImgPreview } from '/@/components/Preview';
6 4 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
  5 +import { useComponentRegister } from '/@/components/Form';
  6 +import { OrgTreeSelect } from '../../common/OrgTreeSelect';
  7 +
  8 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
7 9 export enum Platform {
8 10 PHONE = 'phone',
9 11 PC = 'pc',
... ... @@ -126,14 +128,7 @@ export const formSchema: FormSchema[] = [
126 128 field: 'organizationId',
127 129 label: '所属组织',
128 130 required: true,
129   - component: 'ApiTreeSelect',
130   - componentProps: {
131   - api: async () => {
132   - const data = await getOrganizationList();
133   - copyTransFun(data as any as any[]);
134   - return data;
135   - },
136   - },
  131 + component: 'OrgTreeSelect',
137 132 },
138 133 {
139 134 field: 'platform',
... ...
... ... @@ -28,11 +28,15 @@
28 28 import { ViewTypeNameEnum } from '../../common/ShareModal/config';
29 29 import { useModal } from '/@/components/Modal';
30 30 import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
  31 + import { ViewType } from '../../visual/board/config/panelDetail';
  32 + import { useRole } from '/@/hooks/business/useRole';
31 33
32 34 const listColumn = ref(5);
33 35
34 36 const { createMessage } = useMessage();
35 37
  38 + const { isCustomerUser } = useRole();
  39 +
36 40 const organizationId = ref<Nullable<number>>(null);
37 41
38 42 const pagination = reactive<PaginationProps>({
... ... @@ -153,17 +157,20 @@
153 157 getListData();
154 158 };
155 159
156   - const { clipboardRef, isSuccessRef } = useCopyToClipboard();
157   - const handleCreateShareUrl = (record: ConfigurationCenterItemsModal) => {
158   - if (!unref(getShareFlag)) return;
159   - const { origin } = location;
  160 + const createShareUrl = (record: ConfigurationCenterItemsModal) => {
160 161 const searchParams = new URLSearchParams();
161 162 isDev && searchParams.set('dev', '1');
162 163 searchParams.set('share', 'SCADA');
163 164 searchParams.set('configurationId', record.id);
164 165 searchParams.set('publicId', record.publicId || '');
165 166 searchParams.set('lightbox', '1');
166   - const url = `${origin}${configurationPrefix}/?${searchParams.toString()}`;
  167 + return `${origin}${configurationPrefix}/?${searchParams.toString()}`;
  168 + };
  169 +
  170 + const { clipboardRef, isSuccessRef } = useCopyToClipboard();
  171 + const handleCreateShareUrl = (record: ConfigurationCenterItemsModal) => {
  172 + if (!unref(getShareFlag)) return;
  173 + const url = createShareUrl(record);
167 174 clipboardRef.value = url;
168 175 if (unref(isSuccessRef)) {
169 176 createMessage.success('复制成功~');
... ... @@ -173,7 +180,7 @@
173 180 const [registerShareModal, { openModal }] = useModal();
174 181
175 182 const handleOpenShareModal = (record: ConfigurationCenterItemsModal) => {
176   - openModal(true, record);
  183 + openModal(true, { record, href: createShareUrl(record) });
177 184 };
178 185
179 186 const listEl = ref<Nullable<ComponentElRef>>(null);
... ... @@ -215,7 +222,7 @@
215 222 >
216 223 <template #header>
217 224 <div class="flex gap-3 justify-end">
218   - <Authority :value="ConfigurationPermission.CREATE">
  225 + <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE">
219 226 <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button>
220 227 </Authority>
221 228 <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />
... ... @@ -228,7 +235,13 @@
228 235 </template>
229 236 <template #renderItem="{ item }">
230 237 <List.Item>
231   - <Card hoverable class="card-container">
  238 + <Card
  239 + :style="{
  240 + '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#1890ff' : '#faad14',
  241 + }"
  242 + hoverable
  243 + class="card-container"
  244 + >
232 245 <template #cover>
233 246 <div
234 247 class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative"
... ... @@ -255,7 +268,7 @@
255 268 @click="handlePreview(item)"
256 269 />
257 270 </Tooltip>
258   - <Tooltip title="设计">
  271 + <Tooltip v-if="!isCustomerUser" title="设计">
259 272 <AuthIcon
260 273 :auth="ConfigurationPermission.DESIGN"
261 274 class="!text-lg"
... ... @@ -273,6 +286,7 @@
273 286 />
274 287 </Tooltip>
275 288 <AuthDropDown
  289 + v-if="!isCustomerUser"
276 290 :dropMenuList="[
277 291 {
278 292 text: '分享',
... ... @@ -357,6 +371,6 @@
357 371 }
358 372
359 373 .card-container:deep(.ant-card-cover) {
360   - background-color: #2db7f5;
  374 + background-color: var(--viewType);
361 375 }
362 376 </style>
... ...
... ... @@ -2,7 +2,70 @@
2 2 <div>
3 3 <!-- 首页基础信息 -->
4 4 <div class="md:flex">
5   - <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4" style="color: #666">
  5 + <Card
  6 + v-if="!isAdmin(role)"
  7 + size="small"
  8 + class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4"
  9 + style="color: #666"
  10 + >
  11 + <div class="flex" style="height: 100px">
  12 + <div class="mr-4">
  13 + <img
  14 + v-if="!isAdmin(role)"
  15 + src="/src/assets/images/product.png"
  16 + style="width: 5.625rem; height: 5.625rem"
  17 + />
  18 + <img
  19 + v-else
  20 + src="/src/assets/images/product.png"
  21 + style="width: 5.625rem; height: 5.625rem"
  22 + />
  23 + </div>
  24 + <div class="flex-auto">
  25 + <div class="flex justify-between" style="align-items: center">
  26 + <div
  27 + v-if="!isAdmin(role)"
  28 + style="font-size: 1.625rem; color: #333; font-weight: bold"
  29 + >
  30 + <CountTo
  31 + v-if="growCardList?.productInfo?.sumCount"
  32 + :end-val="growCardList.productInfo.sumCount"
  33 + />
  34 + <CountTo v-else :end-val="0" />
  35 + </div>
  36 + <div style="font-size: 1.625rem; color: #333; font-weight: bold" v-else>
  37 + <CountTo
  38 + v-if="growCardList?.productInfo?.sumCount"
  39 + :end-val="growCardList.productInfo?.sumCount"
  40 + />
  41 + <CountTo v-else :end-val="0" />
  42 + </div>
  43 + <Tooltip>
  44 + <template #title>
  45 + {{
  46 + !isAdmin(role)
  47 + ? `产品数:${growCardList?.productInfo?.sumCount} 今日新增 ${toThousands(
  48 + growCardList?.productInfo?.todayAdd
  49 + )}`
  50 + : `产品数:${growCardList?.customerInfo?.sumCount} 今日新增 ${toThousands(
  51 + growCardList?.productInfo?.todayAdd
  52 + )}`
  53 + }}
  54 + </template>
  55 + <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />
  56 + </Tooltip>
  57 + </div>
  58 + <div> {{ !isAdmin(role) ? `产品数` : '产品数' }}</div>
  59 + </div>
  60 + </div>
  61 + <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
  62 + 今日新增 {{ toThousands(growCardList?.productInfo?.todayAdd) }}</div
  63 + >
  64 + <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
  65 + 今日新增 {{ toThousands(growCardList?.productInfo?.todayAdd) }}</div
  66 + >
  67 + </Card>
  68 + <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4" style="color: #666">
6 69 <div class="flex" style="height: 100px">
7 70 <div class="mr-4"
8 71 ><img
... ... @@ -33,7 +96,7 @@
33 96 今日新增 {{ toThousands(growCardList?.deviceInfo?.todayAdd) }}
34 97 </div>
35 98 </Card>
36   - <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:mr-4" style="color: #666">
  99 + <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:ml-4" style="color: #666">
37 100 <div class="flex" style="height: 100px">
38 101 <div class="mr-4">
39 102 <img
... ... @@ -70,7 +133,7 @@
70 133 growCardList?.alarmInfo?.todayAdd
71 134 )}`
72 135 : `租户总量:${growCardList?.tenantInfo?.sumCount} 今日新增 ${toThousands(
73   - growCardList?.alarmInfo?.todayAdd
  136 + growCardList?.tenantInfo?.todayAdd
74 137 )}`
75 138 }}
76 139 </template>
... ... @@ -87,7 +150,7 @@
87 150 今日新增 {{ toThousands(growCardList?.tenantInfo?.todayAdd) }}</div
88 151 >
89 152 </Card>
90   - <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4" style="color: #666">
  153 + <Card size="small" class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:ml-4" style="color: #666">
91 154 <div class="flex" style="height: 100px">
92 155 <div class="mr-4">
93 156 <img
... ... @@ -104,8 +167,8 @@
104 167 style="font-size: 1.625rem; color: #333; font-weight: bold"
105 168 >
106 169 <CountTo
107   - v-if="growCardList?.messageInfo?.messageCount"
108   - :end-val="growCardList.messageInfo.messageCount"
  170 + v-if="growCardList?.messageInfo?.todayMessageAdd"
  171 + :end-val="growCardList.messageInfo.todayMessageAdd"
109 172 />
110 173 <CountTo v-else :end-val="0" />
111 174 </div>
... ... @@ -120,88 +183,25 @@
120 183 <template #title>
121 184 {{
122 185 !isAdmin(role)
123   - ? `消息数:${growCardList?.messageInfo?.messageCount} 今日新增 ${toThousands(
  186 + ? `今日消息数:${
124 187 growCardList?.messageInfo?.todayMessageAdd
125   - )}`
  188 + } 近30日新增 ${toThousands(growCardList?.messageInfo?.messageCount)}`
126 189 : `客户总量:${growCardList?.customerInfo?.sumCount} 今日新增 ${toThousands(
127   - growCardList?.messageInfo?.todayMessageAdd
  190 + growCardList?.customerInfo?.todayAdd
128 191 )}`
129 192 }}
130 193 </template>
131 194 <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />
132 195 </Tooltip>
133 196 </div>
134   - <div> {{ !isAdmin(role) ? `消息数` : '客户总量' }}</div>
  197 + <div> {{ !isAdmin(role) ? `今日消息数` : '客户总量' }}</div>
135 198 </div>
136 199 </div>
137 200 <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
138   - 今日新增 {{ toThousands(growCardList?.messageInfo?.todayMessageAdd) }}</div
  201 + 近30日新增 {{ toThousands(growCardList?.messageInfo?.messageCount) }}</div
139 202 >
140 203 <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
141   - 今日新增 {{ toThousands(growCardList?.customerInfo?.todayAdd) }}</div
142   - >
143   - </Card>
144   - <Card
145   - v-if="!isAdmin(role)"
146   - size="small"
147   - class="md:w-1/3 w-full !md:mt-0 !mt-4 !md:ml-4"
148   - style="color: #666"
149   - >
150   - <div class="flex" style="height: 100px">
151   - <div class="mr-4">
152   - <img
153   - v-if="!isAdmin(role)"
154   - src="/src/assets/images/product.png"
155   - style="width: 5.625rem; height: 5.625rem"
156   - />
157   - <img
158   - v-else
159   - src="/src/assets/images/product.png"
160   - style="width: 5.625rem; height: 5.625rem"
161   - />
162   - </div>
163   - <div class="flex-auto">
164   - <div class="flex justify-between" style="align-items: center">
165   - <div
166   - v-if="!isAdmin(role)"
167   - style="font-size: 1.625rem; color: #333; font-weight: bold"
168   - >
169   - <CountTo
170   - v-if="growCardList?.productInfo?.sumCount"
171   - :end-val="growCardList.productInfo.sumCount"
172   - />
173   - <CountTo v-else :end-val="0" />
174   - </div>
175   - <div style="font-size: 1.625rem; color: #333; font-weight: bold" v-else>
176   - <CountTo
177   - v-if="growCardList?.productInfo?.sumCount"
178   - :end-val="growCardList.productInfo?.sumCount"
179   - />
180   - <CountTo v-else :end-val="0" />
181   - </div>
182   - <Tooltip>
183   - <template #title>
184   - {{
185   - !isAdmin(role)
186   - ? `产品数:${growCardList?.productInfo?.sumCount} 今日新增 ${toThousands(
187   - growCardList?.productInfo?.todayAdd
188   - )}`
189   - : `产品数:${growCardList?.customerInfo?.sumCount} 今日新增 ${toThousands(
190   - growCardList?.productInfo?.todayAdd
191   - )}`
192   - }}
193   - </template>
194   - <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />
195   - </Tooltip>
196   - </div>
197   - <div> {{ !isAdmin(role) ? `产品数` : '产品数' }}</div>
198   - </div>
199   - </div>
200   - <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
201   - 今日新增 {{ toThousands(growCardList?.productInfo?.todayAdd) }}</div
202   - >
203   - <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5">
204   - 今日新增 {{ toThousands(growCardList?.productInfo?.todayAdd) }}</div
  204 + 近30日新增 {{ toThousands(growCardList?.customerInfo?.todayAdd) }}</div
205 205 >
206 206 </Card>
207 207 </div>
... ...
... ... @@ -22,9 +22,12 @@
22 22 shallowReactive,
23 23 onUnmounted,
24 24 nextTick,
  25 + computed,
  26 + watch,
25 27 } from 'vue';
26 28 import * as echarts from 'echarts';
27 29 import { seriesDataT } from './props';
  30 + import { useAppStore } from '/@/store/modules/app';
28 31
29 32 export default defineComponent({
30 33 props: {
... ... @@ -35,10 +38,29 @@
35 38 },
36 39
37 40 setup(props) {
  41 + const appStore = useAppStore();
  42 + const skinName = computed(() => {
  43 + return appStore.getDarkMode === 'light' ? '#ffffff' : '#151515';
  44 + });
  45 +
38 46 const chartsInstance = shallowReactive<{ [key: string]: echarts.ECharts }>({});
39 47
40 48 const { seriesStatusData } = toRefs(props);
41 49
  50 + watch(
  51 + () => appStore.getDarkMode,
  52 + (target) => {
  53 + const backgroundColor = target === 'light' ? '#ffffff' : '#151515';
  54 + for (const item of seriesStatusData.value) {
  55 + const { key } = item;
  56 + chartsInstance[key!]?.setOption({ backgroundColor });
  57 + }
  58 + },
  59 + {
  60 + immediate: true,
  61 + }
  62 + );
  63 +
42 64 const total = seriesStatusData.value
43 65 .map((m) => m.value)
44 66 .reduce((prev, cur) => prev! + cur!, 0);
... ... @@ -61,8 +83,9 @@
61 83 chartsInstance[key!] = echarts.init(
62 84 document.getElementById(`chartPie${key}`) as HTMLElement
63 85 );
  86 + console.log('11', skinName.value);
64 87 const option = {
65   - backgroundColor: '#ffffff',
  88 + backgroundColor: skinName.value,
66 89 tooltip: {
67 90 trigger: 'item',
68 91 formatter: `${legendKey}设备${((value! / total!) * 100).toFixed(2)}%`,
... ... @@ -101,6 +124,9 @@
101 124 ],
102 125 };
103 126 chartsInstance[key!].setOption(option);
  127 + // chartsInstance[key!].setOption({
  128 + // backgroundColor: skinName,
  129 + // });
104 130 }
105 131 });
106 132
... ...
... ... @@ -5,10 +5,11 @@
5 5 /></div>
6 6 </template>
7 7 <script lang="ts">
8   - import { defineComponent, PropType, ref, Ref, onMounted, toRefs } from 'vue';
  8 + import { defineComponent, PropType, ref, Ref, onMounted, toRefs, computed, watch } from 'vue';
9 9 import { useECharts } from '/@/hooks/web/useECharts';
10 10 import { Empty } from 'ant-design-vue';
11 11 import { seriesDataT } from './props';
  12 + import { useAppStore } from '/@/store/modules/app';
12 13
13 14 export default defineComponent({
14 15 components: { Empty },
... ... @@ -35,23 +36,22 @@
35 36 },
36 37 },
37 38 setup(props) {
  39 + const appStore = useAppStore();
38 40 const { legendData, seriesData } = toRefs(props);
39 41 const dataSeries: Ref<seriesDataT[]> = ref([]);
40 42 const legendDatas: Ref<seriesDataT[]> = ref([]);
41 43 dataSeries.value = seriesData.value as unknown as seriesDataT[];
42 44 legendDatas.value = legendData.value as unknown as seriesDataT[];
43   -
  45 + const skinName = computed(() => {
  46 + return appStore.getDarkMode === 'light' ? '#ffffff' : '#151515';
  47 + });
44 48 const chartRef = ref<HTMLDivElement | null>(null);
  49 +
45 50 const { setOptions, resize } = useECharts(chartRef as Ref<HTMLDivElement>);
46   - const labelLine = {
47   - normal: {
48   - show: true,
49   - length2: 1,
50   - },
51   - } as any;
52   - onMounted(() => {
53   - setOptions({
54   - backgroundColor: '#ffffff',
  51 +
  52 + const getOptions: any = () => {
  53 + return {
  54 + backgroundColor: skinName.value,
55 55 tooltip: {
56 56 trigger: 'item',
57 57 formatter: '{b} {d}%',
... ... @@ -72,7 +72,29 @@
72 72 labelLine,
73 73 },
74 74 ],
75   - });
  75 + };
  76 + };
  77 +
  78 + watch(
  79 + () => appStore.getDarkMode,
  80 + (target) => {
  81 + const backgroundColor = target === 'light' ? '#ffffff' : '#151515';
  82 + setOptions &&
  83 + setOptions({
  84 + ...getOptions(),
  85 + backgroundColor,
  86 + });
  87 + }
  88 + );
  89 +
  90 + const labelLine = {
  91 + normal: {
  92 + show: true,
  93 + length2: 1,
  94 + },
  95 + } as any;
  96 + onMounted(() => {
  97 + setOptions(getOptions());
76 98 //自适应
77 99 window.addEventListener('resize', () => resize());
78 100 });
... ...
... ... @@ -42,8 +42,6 @@
42 42
43 43 const role: string = userInfo?.roles[0];
44 44
45   - console.log({ role });
46   -
47 45 const loading = ref(true);
48 46
49 47 setTimeout(() => {
... ...
1 1 import { FormSchema } from '/@/components/Table';
2   -import { getOrganizationList } from '/@/api/system/system';
3   -import { copyTransFun } from '/@/utils/fnUtils';
4 2 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
5 3 import { createImgPreview } from '/@/components/Preview';
6 4 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
  5 +import { useComponentRegister } from '/@/components/Form';
  6 +import { OrgTreeSelect } from '../common/OrgTreeSelect';
  7 +
  8 +useComponentRegister('OrgTreeSelect', OrgTreeSelect);
  9 +
7 10 export enum Platform {
8 11 PHONE = 0,
9 12 PC = 1,
... ... @@ -16,6 +19,8 @@ export enum ConfigurationPermission {
16 19 SHARE = 'api:yt:dataview:center:share',
17 20 DESIGN = 'api:yt:dataview:center:get_configuration_info:design',
18 21 PREVIEW = 'api:yt:dataview:center:get_configuration_info:preview',
  22 + PUBLISH = 'api:yt:dataview:center:publish',
  23 + // CANCEL_PUBLISH = 'api:yt:dataview:center:cancel_publish',
19 24 }
20 25
21 26 // 查询字段
... ... @@ -78,29 +83,8 @@ export const formSchema: FormSchema[] = [
78 83 field: 'organizationId',
79 84 label: '所属组织',
80 85 required: true,
81   - component: 'ApiTreeSelect',
82   - componentProps: {
83   - api: async () => {
84   - const data = await getOrganizationList();
85   - copyTransFun(data as any as any[]);
86   - return data;
87   - },
88   - },
  86 + component: 'OrgTreeSelect',
89 87 },
90   - // {
91   - // field: 'state',
92   - // label: '发布状态',
93   - // required: true,
94   - // component: 'RadioGroup',
95   - // defaultValue: Platform.PC,
96   - // componentProps: {
97   - // defaultValue: Platform.PC,
98   - // options: [
99   - // { label: '未发布', value: Platform.PC },
100   - // { label: '已发布', value: Platform.PHONE },
101   - // ],
102   - // },
103   - // },
104 88 {
105 89 field: 'remark',
106 90 label: '备注',
... ...
1 1 <script setup lang="ts">
2 2 import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue';
3 3 import { ReloadOutlined } from '@ant-design/icons-vue';
4   - import { onMounted, reactive, ref, unref } from 'vue';
  4 + import { computed, onMounted, reactive, ref, unref } from 'vue';
5 5 import { OrganizationIdTree, useResetOrganizationTree } from '../common/organizationIdTree';
6 6 import {
7 7 bigScreenCancelPublish,
... ... @@ -29,6 +29,10 @@
29 29 import { ShareModal } from '/@/views/common/ShareModal';
30 30 import { ViewTypeNameEnum } from '../common/ShareModal/config';
31 31 import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
  32 + import { ViewType } from '../visual/board/config/panelDetail';
  33 + import { useUserStore } from '/@/store/modules/user';
  34 + import { RoleEnum } from '/@/enums/roleEnum';
  35 + import { useRole } from '/@/hooks/business/useRole';
32 36
33 37 const listColumn = ref(5);
34 38
... ... @@ -48,6 +52,7 @@
48 52 });
49 53
50 54 const loading = ref(false);
  55 + const { isCustomerUser } = useRole();
51 56
52 57 const dataSource = ref<BigScreenCenterItemsModel[]>([]);
53 58
... ... @@ -163,28 +168,31 @@
163 168 const getPublicApiListData = () => {};
164 169
165 170 const [registerShareModal, { openModal }] = useModal();
166   - const handleOpenShareModal = (item: BigScreenCenterItemsModel) => {
167   - openModal(true, item);
  171 + const handleOpenShareModal = (record: BigScreenCenterItemsModel) => {
  172 + openModal(true, { record, href: createShareUrl(record) });
168 173 };
169 174
  175 + const createShareUrl = (record: BigScreenCenterItemsModel) => {
  176 + const { origin } = location;
  177 + return `${origin}${largeDesignerPrefix}/#/share/preview/${record.id}/${record.publicId}`;
  178 + };
  179 +
  180 + const userStore = useUserStore();
  181 + const hasPublicInterfacePermission = computed(() => {
  182 + return userStore.getUserInfo.roles![0] !== RoleEnum.CUSTOMER_USER;
  183 + });
  184 +
170 185 const { clipboardRef, isSuccessRef } = useCopyToClipboard();
171 186 const handleCreateShareUrl = (record: BigScreenCenterItemsModel) => {
172   - const { origin } = location;
173   - const { largeDesignerPrefix } = useGlobSetting();
174   - clipboardRef.value = `${origin}${largeDesignerPrefix}/#/share/preview/${record.id}/${record.publicId}`;
  187 + clipboardRef.value = createShareUrl(record);
175 188 if (unref(isSuccessRef)) {
176 189 createMessage.success('复制成功~');
177 190 }
178 191 };
179 192
180   - const handlePublish = async ({ id }) => {
181   - await bigScreenPublish(id);
182   - createMessage.success('发布成功');
183   - getListData();
184   - };
185   - const handleCancelPublish = async ({ id }) => {
186   - await bigScreenCancelPublish(id);
187   - createMessage.success('取消发布成功');
  193 + const handlePublish = async ({ id, state }) => {
  194 + state === 0 ? await bigScreenPublish(id) : await bigScreenCancelPublish(id);
  195 + createMessage.success(state === 0 ? '发布成功' : '取消发布成功');
188 196 getListData();
189 197 };
190 198 </script>
... ... @@ -207,10 +215,10 @@
207 215 >
208 216 <template #header>
209 217 <div class="flex gap-3 justify-end">
210   - <Authority :value="ConfigurationPermission.CREATE">
211   - <Button type="primary" @click="handleCreateOrUpdate()">新增大屏</Button>
  218 + <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE">
  219 + <Button type="primary" @click="handleCreateOrUpdate()"> 新增大屏 </Button>
212 220 </Authority>
213   - <Authority :value="ConfigurationPermission.CREATE">
  221 + <Authority v-if="hasPublicInterfacePermission" :value="ConfigurationPermission.CREATE">
214 222 <Button type="primary" @click="handleCreateOrUpdatePublicApi()">公共接口管理</Button>
215 223 </Authority>
216 224 <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />
... ... @@ -223,7 +231,12 @@
223 231 </template>
224 232 <template #renderItem="{ item }">
225 233 <List.Item>
226   - <Card class="card-container">
  234 + <Card
  235 + :style="{
  236 + '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#1890ff' : '#faad14',
  237 + }"
  238 + class="card-container"
  239 + >
227 240 <template #cover>
228 241 <div class="h-full w-full relative hover-show-modal-content img-container">
229 242 <img
... ... @@ -265,7 +278,7 @@
265 278 @click="handlePreview(item)"
266 279 />
267 280 </Tooltip>
268   - <Tooltip title="设计">
  281 + <Tooltip v-if="!isCustomerUser" title="设计">
269 282 <AuthIcon
270 283 :disabled="item.state === 1"
271 284 icon="ant-design:edit-outlined"
... ... @@ -282,6 +295,7 @@
282 295 />
283 296 </Tooltip>
284 297 <AuthDropDown
  298 + v-if="!isCustomerUser"
285 299 :dropMenuList="[
286 300 {
287 301 text: '分享',
... ... @@ -291,19 +305,14 @@
291 305 onClick: handleOpenShareModal.bind(null, item),
292 306 },
293 307 {
294   - text: '发布',
295   - auth: ConfigurationPermission.UPDATE,
296   - icon: 'ant-design:node-expand-outlined',
  308 + text: item.state == 0 ? '发布' : '取消发布',
  309 + auth: ConfigurationPermission.PUBLISH,
  310 + icon:
  311 + item.state == 0
  312 + ? 'ant-design:node-expand-outlined'
  313 + : 'ant-design:node-collapse-outlined',
297 314 event: '',
298 315 onClick: handlePublish.bind(null, item),
299   - disabled: item.state === 0 ? false : true,
300   - },
301   - {
302   - text: '取消发布',
303   - icon: 'ant-design:node-collapse-outlined',
304   - event: '',
305   - onClick: handleCancelPublish.bind(null, item),
306   - disabled: item.state === 1 ? false : true,
307 316 },
308 317 {
309 318 text: '编辑',
... ... @@ -421,6 +430,6 @@
421 430 }
422 431
423 432 .card-container:deep(.ant-card-cover) {
424   - background-color: #2db7f5;
  433 + background-color: var(--viewType);
425 434 }
426 435 </style>
... ...