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,3 +39,6 @@ VITE_GLOB_ALARM_NOTIFY_POLLING_INTERVAL_TIME = 500000
39 39
40 # Alarm Notify Auto Close Time Unit is Second 40 # Alarm Notify Auto Close Time Unit is Second
41 VITE_GLOB_ALARM_NOTIFY_DURATION = 5 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,6 +16,7 @@ import {
16 UpdateDataComponentParams, 16 UpdateDataComponentParams,
17 } from './model'; 17 } from './model';
18 import { defHttp } from '/@/utils/http/axios'; 18 import { defHttp } from '/@/utils/http/axios';
  19 +import { isShareMode } from '/@/views/sys/share/hook';
19 20
20 enum DataBoardUrl { 21 enum DataBoardUrl {
21 GET_DATA_BOARD = '/data_board', 22 GET_DATA_BOARD = '/data_board',
@@ -224,7 +225,7 @@ export const sendCommandOneway = (params: SendCommandParams) => { @@ -224,7 +225,7 @@ export const sendCommandOneway = (params: SendCommandParams) => {
224 url: `${SendCommand.ONEWAY}/${params.deviceId}`, 225 url: `${SendCommand.ONEWAY}/${params.deviceId}`,
225 params: params.value, 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,6 +86,12 @@ export interface DataSource {
86 deviceName: string; 86 deviceName: string;
87 deviceProfileId: string; 87 deviceProfileId: string;
88 tbDeviceId: string; 88 tbDeviceId: string;
  89 + customCommand: {
  90 + transportType?: string;
  91 + commandType?: string;
  92 + command?: string;
  93 + service?: string;
  94 + };
89 95
90 // front usage 96 // front usage
91 uuid?: string; 97 uuid?: string;
@@ -93,6 +99,7 @@ export interface DataSource { @@ -93,6 +99,7 @@ export interface DataSource {
93 height?: number; 99 height?: number;
94 radio?: RadioRecord; 100 radio?: RadioRecord;
95 deviceType?: DeviceTypeEnum; 101 deviceType?: DeviceTypeEnum;
  102 + [key: string]: any;
96 } 103 }
97 104
98 export interface DataComponentRecord { 105 export interface DataComponentRecord {
@@ -50,6 +50,8 @@ export interface DeviceModel { @@ -50,6 +50,8 @@ export interface DeviceModel {
50 deviceType: DeviceTypeEnum; 50 deviceType: DeviceTypeEnum;
51 organizationId: string; 51 organizationId: string;
52 customerId?: string; 52 customerId?: string;
  53 + alias?: string;
  54 + tbDeviceId?: string;
53 } 55 }
54 56
55 export interface DeviceProfileModel { 57 export interface DeviceProfileModel {
@@ -10,6 +10,8 @@ export type SchedueParam = { @@ -10,6 +10,8 @@ export type SchedueParam = {
10 data?: any; 10 data?: any;
11 code?: number; 11 code?: number;
12 jobId?: string; 12 jobId?: string;
  13 + startTime?: number;
  14 + endTime?: number;
13 }; 15 };
14 16
15 export interface ReportModel { 17 export interface ReportModel {
@@ -30,6 +30,38 @@ export const getMenuList = (args?: number) => { @@ -30,6 +30,38 @@ export const getMenuList = (args?: number) => {
30 } catch (e) {} 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 export const delMenu = (menuIds: string[]) => { 65 export const delMenu = (menuIds: string[]) => {
34 const url = Api.BaseMenuUrl; 66 const url = Api.BaseMenuUrl;
35 return defHttp.delete({ url: url, data: menuIds }); 67 return defHttp.delete({ url: url, data: menuIds });
@@ -42,7 +42,7 @@ export interface MenuRecord { @@ -42,7 +42,7 @@ export interface MenuRecord {
42 component: string; 42 component: string;
43 meta: Meta; 43 meta: Meta;
44 disabled?: boolean; 44 disabled?: boolean;
45 - isDictCompareDisabled?: boolean; 45 + show?: boolean;
46 icon?: string; 46 icon?: string;
47 title?: string; 47 title?: string;
48 key?: string; 48 key?: string;
1 import { defHttp } from '/@/utils/http/axios'; 1 import { defHttp } from '/@/utils/http/axios';
2 import { ViewTypeEnum } from '/@/views/sys/share/config/config'; 2 import { ViewTypeEnum } from '/@/views/sys/share/config/config';
  3 +import { isShareMode } from '/@/views/sys/share/hook';
3 4
4 enum Api { 5 enum Api {
5 CHECK = '/share/check', 6 CHECK = '/share/check',
@@ -8,9 +9,14 @@ enum Api { @@ -8,9 +9,14 @@ enum Api {
8 } 9 }
9 10
10 export const checkShareAccessToken = (type: ViewTypeEnum, id: string) => { 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 export const sharePageLogin = (publicId: string) => { 22 export const sharePageLogin = (publicId: string) => {
@@ -21,14 +27,20 @@ export const sharePageLogin = (publicId: string) => { @@ -21,14 +27,20 @@ export const sharePageLogin = (publicId: string) => {
21 }, 27 },
22 { 28 {
23 joinPrefix: false, 29 joinPrefix: false,
  30 + withShareToken: isShareMode(),
24 } 31 }
25 ); 32 );
26 }; 33 };
27 34
28 export const getShareContent = (record: Record<'accessCredentials' | 'id', string>) => { 35 export const getShareContent = (record: Record<'accessCredentials' | 'id', string>) => {
29 const { id, accessCredentials } = record; 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,11 +81,8 @@ export const genModbusCommand = (data: GenModbusCommandType) => {
81 }; 81 };
82 82
83 export const immediateExecute = (data: ImmediateExecuteTaskType) => { 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,6 +47,7 @@ export interface TaskRecordType extends CreateTaskRecordType {
47 enabled: boolean; 47 enabled: boolean;
48 state: number; 48 state: number;
49 lastExecuteTime?: number; 49 lastExecuteTime?: number;
  50 + lastExecuteStr?: string;
50 tkDeviceTaskCenter?: { 51 tkDeviceTaskCenter?: {
51 allowState: number; 52 allowState: number;
52 taskCenterId: string; 53 taskCenterId: string;
@@ -34,6 +34,9 @@ enum Api { @@ -34,6 +34,9 @@ enum Api {
34 getTenantProfile = '/tenant_profiles', 34 getTenantProfile = '/tenant_profiles',
35 deleteTenantProfile = '/tenantProfile', 35 deleteTenantProfile = '/tenantProfile',
36 setTenantProfile = '/tenantProfile', 36 setTenantProfile = '/tenantProfile',
  37 + getTenantPageList = '/admin/all/tenants',
  38 + getTenantAllPageList = '/admin/',
  39 + deleteTenantProfileByCheckPass = '/tenant_profiles/allow/',
37 } 40 }
38 41
39 export async function deleteTenantProfileApi(ids: string) { 42 export async function deleteTenantProfileApi(ids: string) {
@@ -155,3 +158,20 @@ export function getTenantRoles(tenantCode: string) { @@ -155,3 +158,20 @@ export function getTenantRoles(tenantCode: string) {
155 url: Api.getTenantRoles, 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,6 +39,7 @@ export interface TenantPageRequestParams extends BaseQueryParams {
39 } 39 }
40 40
41 export interface TenantAdminPageRequestParams extends BaseQueryParams { 41 export interface TenantAdminPageRequestParams extends BaseQueryParams {
  42 + [x: string]: any;
42 realName?: string; 43 realName?: string;
43 tenantId?: string; 44 tenantId?: string;
44 items?: string[]; 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>
@@ -3,24 +3,32 @@ import { Rule } from '/@/components/Form'; @@ -3,24 +3,32 @@ import { Rule } from '/@/components/Form';
3 export { default as JSONEditor } from './index.vue'; 3 export { default as JSONEditor } from './index.vue';
4 4
5 export const parseStringToJSON = <T = Recordable>(value: string) => { 5 export const parseStringToJSON = <T = Recordable>(value: string) => {
  6 + let valid = false;
6 try { 7 try {
7 const json = JSON.parse(value) as T; 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 } catch (error) { 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 return [ 21 return [
16 { 22 {
17 validateTrigger: 'blur', 23 validateTrigger: 'blur',
18 validator(_rule: Rule, value: any, _callback: Fn) { 24 validator(_rule: Rule, value: any, _callback: Fn) {
19 - const { valid } = parseStringToJSON(value); 25 + const { valid, json } = parseStringToJSON(value);
20 if (valid) { 26 if (valid) {
  27 + if (noEmpty && json && !Object.keys(json).length) return Promise.reject(emptyMessage);
21 return Promise.resolve(); 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 <script lang="ts" setup> 1 <script lang="ts" setup>
2 - import { ref } from 'vue'; 2 + import { ref, watch } from 'vue';
3 import JSONEditor, { JSONEditorOptions } from 'jsoneditor'; 3 import JSONEditor, { JSONEditorOptions } from 'jsoneditor';
4 import 'jsoneditor/dist/jsoneditor.min.css'; 4 import 'jsoneditor/dist/jsoneditor.min.css';
5 import { unref } from 'vue'; 5 import { unref } from 'vue';
@@ -18,6 +18,7 @@ @@ -18,6 +18,7 @@
18 defineProps<{ 18 defineProps<{
19 value?: string; 19 value?: string;
20 options?: JSONEditorOptions; 20 options?: JSONEditorOptions;
  21 + height?: number;
21 }>(), 22 }>(),
22 { 23 {
23 options: () => 24 options: () =>
@@ -26,6 +27,7 @@ @@ -26,6 +27,7 @@
26 mainMenuBar: false, 27 mainMenuBar: false,
27 statusBar: false, 28 statusBar: false,
28 } as JSONEditorOptions), 29 } as JSONEditorOptions),
  30 + height: 150,
29 } 31 }
30 ); 32 );
31 33
@@ -40,6 +42,8 @@ @@ -40,6 +42,8 @@
40 42
41 const editoreRef = ref<JSONEditor>(); 43 const editoreRef = ref<JSONEditor>();
42 44
  45 + const isFocus = ref(false);
  46 +
43 const handleChange = (value: any) => { 47 const handleChange = (value: any) => {
44 emit(EventEnum.UPDATE_VALUE, value, unref(editoreRef)); 48 emit(EventEnum.UPDATE_VALUE, value, unref(editoreRef));
45 emit(EventEnum.CHANGE, value, unref(editoreRef)); 49 emit(EventEnum.CHANGE, value, unref(editoreRef));
@@ -54,8 +58,14 @@ @@ -54,8 +58,14 @@
54 return { 58 return {
55 ...options, 59 ...options,
56 onChangeText: handleChange, 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 } as JSONEditorOptions; 69 } as JSONEditorOptions;
60 }); 70 });
61 71
@@ -63,15 +73,16 @@ @@ -63,15 +73,16 @@
63 editoreRef.value = new JSONEditor(unref(jsonEditorElRef), unref(getOptions)); 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 const get = (): string => { 87 const get = (): string => {
77 return unref(editoreRef)?.getText() || ''; 88 return unref(editoreRef)?.getText() || '';
@@ -97,7 +108,7 @@ @@ -97,7 +108,7 @@
97 </script> 108 </script>
98 109
99 <template> 110 <template>
100 - <div class="p-2 bg-gray-200"> 111 + <div class="p-2 bg-gray-200" :style="{ height: `${height}px` }">
101 <div ref="jsonEditorElRef" class="jsoneditor"></div> 112 <div ref="jsonEditorElRef" class="jsoneditor"></div>
102 </div> 113 </div>
103 </template> 114 </template>
@@ -108,6 +119,11 @@ @@ -108,6 +119,11 @@
108 119
109 :deep(.jsoneditor) { 120 :deep(.jsoneditor) {
110 border: none !important; 121 border: none !important;
  122 +
  123 + .ace-jsoneditor,
  124 + textarea.jsoneditor-text {
  125 + min-height: auto;
  126 + }
111 } 127 }
112 } 128 }
113 </style> 129 </style>
@@ -67,8 +67,8 @@ @@ -67,8 +67,8 @@
67 initVal(); 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 if (props?.value) for (let i in props.value) Reflect.set(valueObj, i, props.value[i]); 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,7 +279,6 @@
279 setSelectedOptions, 279 setSelectedOptions,
280 setSelectedTotal, 280 setSelectedTotal,
281 reloadPending, 281 reloadPending,
282 - reloadPending,  
283 reloadSelected, 282 reloadSelected,
284 }; 283 };
285 284
@@ -408,7 +407,7 @@ @@ -408,7 +407,7 @@
408 :key="item.value" 407 :key="item.value"
409 > 408 >
410 <span> 409 <span>
411 - {{ item.label }} 410 + {{ item.alias || item.name }}
412 </span> 411 </span>
413 </Tag> 412 </Tag>
414 <Tag class="!px-2 !py-1 !bg-gray-50 !border-gray-100" v-if="getSurplusOptionsLength"> 413 <Tag class="!px-2 !py-1 !bg-gray-50 !border-gray-100" v-if="getSurplusOptionsLength">
@@ -13,6 +13,7 @@ @@ -13,6 +13,7 @@
13 import { Divider, Button } from 'ant-design-vue'; 13 import { Divider, Button } from 'ant-design-vue';
14 import { OpenModalMode, OpenModalParams, StructRecord } from './type'; 14 import { OpenModalMode, OpenModalParams, StructRecord } from './type';
15 import { cloneDeep } from 'lodash-es'; 15 import { cloneDeep } from 'lodash-es';
  16 + import { isArray } from '/@/utils/is';
16 17
17 const emit = defineEmits(['update:value']); 18 const emit = defineEmits(['update:value']);
18 19
@@ -30,7 +31,8 @@ @@ -30,7 +31,8 @@
30 31
31 const getValue = computed<StructRecord[]>(() => { 32 const getValue = computed<StructRecord[]>(() => {
32 const { value } = props; 33 const { value } = props;
33 - return value.map((item) => { 34 +
  35 + return (isArray(value) ? value : []).map((item) => {
34 return { 36 return {
35 ...(item as StructRecord), 37 ...(item as StructRecord),
36 ...((item as StructRecord).id ? {} : { id: buildUUID() }), 38 ...((item as StructRecord).id ? {} : { id: buildUUID() }),
@@ -60,7 +60,7 @@ @@ -60,7 +60,7 @@
60 }); 60 });
61 61
62 const validateRepeat = (value: StructRecord, valueList: StructRecord[]) => { 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 const handleSubmit = async () => { 66 const handleSubmit = async () => {
@@ -20,7 +20,7 @@ export const validateValueRange = (_rule, value: Record<'min' | 'max', number>, @@ -20,7 +20,7 @@ export const validateValueRange = (_rule, value: Record<'min' | 'max', number>,
20 return Promise.resolve(); 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 if (value.length) { 24 if (value.length) {
25 return Promise.resolve(); 25 return Promise.resolve();
26 } 26 }
@@ -65,7 +65,7 @@ export const formSchemas = (hasStructForm: boolean): FormSchema[] => { @@ -65,7 +65,7 @@ export const formSchemas = (hasStructForm: boolean): FormSchema[] => {
65 }, 65 },
66 defaultValue: 'INT', 66 defaultValue: 'INT',
67 componentProps: ({ formActionType }) => { 67 componentProps: ({ formActionType }) => {
68 - const { updateSchema } = formActionType; 68 + const { updateSchema, setFieldsValue } = formActionType;
69 return { 69 return {
70 placeholder: '请选择数据类型', 70 placeholder: '请选择数据类型',
71 api: async (params: Recordable) => { 71 api: async (params: Recordable) => {
@@ -86,13 +86,15 @@ export const formSchemas = (hasStructForm: boolean): FormSchema[] => { @@ -86,13 +86,15 @@ export const formSchemas = (hasStructForm: boolean): FormSchema[] => {
86 valueField: 'itemValue', 86 valueField: 'itemValue',
87 getPopupContainer: () => document.body, 87 getPopupContainer: () => document.body,
88 onChange: (value: string) => { 88 onChange: (value: string) => {
89 - value === DataTypeEnum.IS_STRUCT && 89 + if (value == DataTypeEnum.IS_STRUCT) {
90 updateSchema({ 90 updateSchema({
91 field: FormField.SPECS_LIST, 91 field: FormField.SPECS_LIST,
92 componentProps: { 92 componentProps: {
93 hasStructForm: true, 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,4 +127,6 @@ export type ComponentType =
127 | 'ProductPicker' 127 | 'ProductPicker'
128 | 'PollCommandInput' 128 | 'PollCommandInput'
129 | 'RegisterAddressInput' 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,6 +31,6 @@
31 <Icon 31 <Icon
32 v-bind="getBindProps" 32 v-bind="getBindProps"
33 class="justify-center items-center" 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 </template> 36 </template>
@@ -5,6 +5,10 @@ export const JWT_TOKEN_KEY = 'JWT_TOKEN'; @@ -5,6 +5,10 @@ export const JWT_TOKEN_KEY = 'JWT_TOKEN';
5 5
6 export const REFRESH_TOKEN_KEY = 'REFRESH_TOKEN'; 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 export const LOCALE_KEY = 'LOCALE__'; 12 export const LOCALE_KEY = 'LOCALE__';
9 13
10 // user info key 14 // user info key
@@ -7,4 +7,14 @@ export enum DictEnum { @@ -7,4 +7,14 @@ export enum DictEnum {
7 DATA_VALIDATE = 'data_validate', 7 DATA_VALIDATE = 'data_validate',
8 // 设备类型 8 // 设备类型
9 DEVICE_TYPE = 'device_type', 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,6 +5,13 @@ export enum DataActionModeEnum {
5 DELETE = 'DELETE', 5 DELETE = 'DELETE',
6 } 6 }
7 7
  8 +export enum DataActionModeNameEnum {
  9 + CREATE = '创建',
  10 + READ = '查看',
  11 + UPDATE = '编辑',
  12 + DELETE = '删除',
  13 +}
  14 +
8 export enum TimeUnitEnum { 15 export enum TimeUnitEnum {
9 SECOND = 'SECOND', 16 SECOND = 'SECOND',
10 MINUTE = 'MINUTE', 17 MINUTE = 'MINUTE',
@@ -16,3 +23,8 @@ export enum TimeUnitNameEnum { @@ -16,3 +23,8 @@ export enum TimeUnitNameEnum {
16 MINUTE = '分', 23 MINUTE = '分',
17 HOUR = '时', 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,7 +17,7 @@ function joinParentPath(menus: Menu[], parentPath = '') {
17 // https://next.router.vuejs.org/guide/essentials/nested-routes.html 17 // https://next.router.vuejs.org/guide/essentials/nested-routes.html
18 // Note that nested paths that start with / will be treated as a root path. 18 // Note that nested paths that start with / will be treated as a root path.
19 // This allows you to leverage the component nesting without having to use a nested URL. 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 // path doesn't start with /, nor is it a url, join parent path 21 // path doesn't start with /, nor is it a url, join parent path
22 menu.path = `${parentPath}/${menu.path}`; 22 menu.path = `${parentPath}/${menu.path}`;
23 } 23 }
@@ -224,7 +224,7 @@ export const usePermissionStore = defineStore({ @@ -224,7 +224,7 @@ export const usePermissionStore = defineStore({
224 // !Simulate to obtain permission codes from the background, 224 // !Simulate to obtain permission codes from the background,
225 // this function may only need to be executed once, and the actual project can be put at the right time by itself 225 // this function may only need to be executed once, and the actual project can be put at the right time by itself
226 let routeList: AppRouteRecordRaw[] = []; 226 let routeList: AppRouteRecordRaw[] = [];
227 - const userInfo: any = getAuthCache(USER_INFO_KEY); 227 + const userInfo: any = getAuthCache(USER_INFO_KEY) || { roles: [] };
228 const filterMenu = (allMenuList, menuIdsList) => { 228 const filterMenu = (allMenuList, menuIdsList) => {
229 return allMenuList 229 return allMenuList
230 .filter((item) => { 230 .filter((item) => {
@@ -4,7 +4,14 @@ import { defineStore } from 'pinia'; @@ -4,7 +4,14 @@ import { defineStore } from 'pinia';
4 import { store } from '/@/store'; 4 import { store } from '/@/store';
5 import { RoleEnum } from '/@/enums/roleEnum'; 5 import { RoleEnum } from '/@/enums/roleEnum';
6 import { PageEnum } from '/@/enums/pageEnum'; 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 import { getAuthCache, setAuthCache } from '/@/utils/auth'; 15 import { getAuthCache, setAuthCache } from '/@/utils/auth';
9 import { 16 import {
10 LoginParams, 17 LoginParams,
@@ -33,6 +40,8 @@ interface UserState { @@ -33,6 +40,8 @@ interface UserState {
33 lastUpdateTime: number; 40 lastUpdateTime: number;
34 jwtToken?: string; 41 jwtToken?: string;
35 refreshToken?: string; 42 refreshToken?: string;
  43 + shareJwtToken?: string;
  44 + shareRefreshToken?: string;
36 outTarget?: string; 45 outTarget?: string;
37 } 46 }
38 47
@@ -100,6 +109,13 @@ export const useUserStore = defineStore({ @@ -100,6 +109,13 @@ export const useUserStore = defineStore({
100 setAuthCache(JWT_TOKEN_KEY, jwtToken); 109 setAuthCache(JWT_TOKEN_KEY, jwtToken);
101 setAuthCache(REFRESH_TOKEN_KEY, refreshToken); 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 setToken(info: string | undefined) { 119 setToken(info: string | undefined) {
104 this.jwtToken = info; 120 this.jwtToken = info;
105 setAuthCache(JWT_TOKEN_KEY, info); 121 setAuthCache(JWT_TOKEN_KEY, info);
@@ -188,7 +204,7 @@ export const useUserStore = defineStore({ @@ -188,7 +204,7 @@ export const useUserStore = defineStore({
188 const roleList = roles.map((item) => item) as RoleEnum[]; 204 const roleList = roles.map((item) => item) as RoleEnum[];
189 this.setRoleList(roleList); 205 this.setRoleList(roleList);
190 try { 206 try {
191 - if (roleList[0] !== 'SYS_ADMIN') { 207 + if (roleList[0] !== RoleEnum.SYS_ADMIN && roleList[0] !== RoleEnum.PLATFORM_ADMIN) {
192 const res = await getEntitiesId(); 208 const res = await getEntitiesId();
193 const entityId = res.data[0]?.entityId; 209 const entityId = res.data[0]?.entityId;
194 window.localStorage.setItem('entityId', JSON.stringify(entityId)); 210 window.localStorage.setItem('entityId', JSON.stringify(entityId));
@@ -232,6 +248,18 @@ export const useUserStore = defineStore({ @@ -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 * @description: Confirm before logging out 264 * @description: Confirm before logging out
237 */ 265 */
1 import { Persistent, BasicKeys } from '/@/utils/cache/persistent'; 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 import projectSetting from '/@/settings/projectSetting'; 3 import projectSetting from '/@/settings/projectSetting';
4 import { JWT_TOKEN_KEY, REFRESH_TOKEN_KEY } from '/@/enums/cacheEnum'; 4 import { JWT_TOKEN_KEY, REFRESH_TOKEN_KEY } from '/@/enums/cacheEnum';
5 5
@@ -26,3 +26,11 @@ export function getJwtToken() { @@ -26,3 +26,11 @@ export function getJwtToken() {
26 export function getRefreshToken() { 26 export function getRefreshToken() {
27 return getAuthCache(REFRESH_TOKEN_KEY); 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,6 +17,8 @@ import {
17 APP_SESSION_CACHE_KEY, 17 APP_SESSION_CACHE_KEY,
18 MULTIPLE_TABS_KEY, 18 MULTIPLE_TABS_KEY,
19 MENU_LIST, 19 MENU_LIST,
  20 + SHARE_JWT_TOKEN_KEY,
  21 + SHARE_REFRESH_TOKEN_KEY,
20 } from '/@/enums/cacheEnum'; 22 } from '/@/enums/cacheEnum';
21 import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting'; 23 import { DEFAULT_CACHE_TIME } from '/@/settings/encryptionSetting';
22 import { toRaw } from 'vue'; 24 import { toRaw } from 'vue';
@@ -33,6 +35,8 @@ interface BasicStore { @@ -33,6 +35,8 @@ interface BasicStore {
33 [PROJ_CFG_KEY]: ProjectConfig; 35 [PROJ_CFG_KEY]: ProjectConfig;
34 [MULTIPLE_TABS_KEY]: RouteLocationNormalized[]; 36 [MULTIPLE_TABS_KEY]: RouteLocationNormalized[];
35 [MENU_LIST]: any[]; 37 [MENU_LIST]: any[];
  38 + [SHARE_JWT_TOKEN_KEY]: string;
  39 + [SHARE_REFRESH_TOKEN_KEY]: string;
36 } 40 }
37 41
38 type LocalStore = BasicStore; 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,7 +80,6 @@ export class VAxios {
80 private refreshTokenBeforeReq(doRefreshTokenApi: () => Promise<unknown>): Promise<unknown> { 80 private refreshTokenBeforeReq(doRefreshTokenApi: () => Promise<unknown>): Promise<unknown> {
81 // 创建一个未完成的promise,把改变状态的resolve方法交给请求token结束后执行 81 // 创建一个未完成的promise,把改变状态的resolve方法交给请求token结束后执行
82 const promise = new Promise((resolve) => { 82 const promise = new Promise((resolve) => {
83 - console.log('等待新token');  
84 // 等待队列放的是一个回调函数,来延迟resolve的执行,以此控制promise状态的改变 83 // 等待队列放的是一个回调函数,来延迟resolve的执行,以此控制promise状态的改变
85 this.waitingQueue.push(() => resolve(null)); 84 this.waitingQueue.push(() => resolve(null));
86 }); 85 });
@@ -88,7 +87,6 @@ export class VAxios { @@ -88,7 +87,6 @@ export class VAxios {
88 this.refreshing = true; 87 this.refreshing = true;
89 // 模拟请求刷新Token接口,当接口返回数据时执行then方法 TODO 添加catch捕获异常 88 // 模拟请求刷新Token接口,当接口返回数据时执行then方法 TODO 添加catch捕获异常
90 doRefreshTokenApi().then(() => { 89 doRefreshTokenApi().then(() => {
91 - console.log('刷新token成功,放行队列中的请求', this.waitingQueue.length);  
92 this.refreshing = false; 90 this.refreshing = false;
93 this.waitingQueue.forEach((cb) => cb()); 91 this.waitingQueue.forEach((cb) => cb());
94 this.waitingQueue.length = 0; 92 this.waitingQueue.length = 0;
@@ -117,12 +115,19 @@ export class VAxios { @@ -117,12 +115,19 @@ export class VAxios {
117 this.axiosInstance.interceptors.request.use(async (config: AxiosRequestConfig) => { 115 this.axiosInstance.interceptors.request.use(async (config: AxiosRequestConfig) => {
118 // If cancel repeat request is turned on, then cancel repeat request is prohibited 116 // If cancel repeat request is turned on, then cancel repeat request is prohibited
119 const userStore = useUserStore(); 117 const userStore = useUserStore();
120 - if (userStore && userStore.jwtToken) { 118 + if (userStore) {
121 try { 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 } catch (error) { 132 } catch (error) {
128 userStore.logout(); 133 userStore.logout();
@@ -244,7 +249,6 @@ export class VAxios { @@ -244,7 +249,6 @@ export class VAxios {
244 const { requestOptions } = this.options; 249 const { requestOptions } = this.options;
245 250
246 const opt: RequestOptions = Object.assign({}, requestOptions, options); 251 const opt: RequestOptions = Object.assign({}, requestOptions, options);
247 -  
248 const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {}; 252 const { beforeRequestHook, requestCatchHook, transformRequestHook } = transform || {};
249 if (beforeRequestHook && isFunction(beforeRequestHook)) { 253 if (beforeRequestHook && isFunction(beforeRequestHook)) {
250 conf = beforeRequestHook(conf, opt); 254 conf = beforeRequestHook(conf, opt);
1 /** 1 /**
2 * Data processing class, can be configured according to the project 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 import type { RequestOptions, Result } from '/#/axios'; 5 import type { RequestOptions, Result } from '/#/axios';
6 6
7 export interface CreateAxiosOptions extends AxiosRequestConfig { 7 export interface CreateAxiosOptions extends AxiosRequestConfig {
@@ -10,6 +10,7 @@ export interface CreateAxiosOptions extends AxiosRequestConfig { @@ -10,6 +10,7 @@ export interface CreateAxiosOptions extends AxiosRequestConfig {
10 transform?: AxiosTransform; 10 transform?: AxiosTransform;
11 requestOptions?: RequestOptions; 11 requestOptions?: RequestOptions;
12 } 12 }
  13 +export type AxiosRequestConfig = OriginalAxiosRequestConfig & { requestOptions?: RequestOptions };
13 14
14 export abstract class AxiosTransform { 15 export abstract class AxiosTransform {
15 /** 16 /**
@@ -10,7 +10,7 @@ import { useGlobSetting } from '/@/hooks/setting'; @@ -10,7 +10,7 @@ import { useGlobSetting } from '/@/hooks/setting';
10 import { useMessage } from '/@/hooks/web/useMessage'; 10 import { useMessage } from '/@/hooks/web/useMessage';
11 import { RequestEnum, ContentTypeEnum } from '/@/enums/httpEnum'; 11 import { RequestEnum, ContentTypeEnum } from '/@/enums/httpEnum';
12 import { isString } from '/@/utils/is'; 12 import { isString } from '/@/utils/is';
13 -import { getJwtToken } from '/@/utils/auth'; 13 +import { getJwtToken, getShareJwtToken } from '/@/utils/auth';
14 import { setObjToUrlParams, deepMerge } from '/@/utils'; 14 import { setObjToUrlParams, deepMerge } from '/@/utils';
15 import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog'; 15 import { useErrorLogStoreWithOut } from '/@/store/modules/errorLog';
16 import { useI18n } from '/@/hooks/web/useI18n'; 16 import { useI18n } from '/@/hooks/web/useI18n';
@@ -92,12 +92,23 @@ const transform: AxiosTransform = { @@ -92,12 +92,23 @@ const transform: AxiosTransform = {
92 */ 92 */
93 requestInterceptors: (config, options) => { 93 requestInterceptors: (config, options) => {
94 // 请求之前处理config 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 return config; 113 return config;
103 }, 114 },
@@ -194,7 +205,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) { @@ -194,7 +205,7 @@ function createAxios(opt?: Partial<CreateAxiosOptions>) {
194 ignoreCancelToken: true, 205 ignoreCancelToken: true,
195 // 是否携带token 206 // 是否携带token
196 withToken: true, 207 withToken: true,
197 - }, 208 + } as RequestOptions,
198 }, 209 },
199 opt || {} 210 opt || {}
200 ) 211 )
1 import { BasicColumn, FormSchema } from '/@/components/Table'; 1 import { BasicColumn, FormSchema } from '/@/components/Table';
2 -import { getOrganizationList } from '/@/api/system/system';  
3 -import { copyTransFun } from '/@/utils/fnUtils';  
4 import { findDictItemByCode } from '/@/api/system/dict'; 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 export const columns: BasicColumn[] = [ 7 export const columns: BasicColumn[] = [
8 { 8 {
@@ -98,19 +98,8 @@ export const formSchema: FormSchema[] = [ @@ -98,19 +98,8 @@ export const formSchema: FormSchema[] = [
98 { 98 {
99 field: 'organizationId', 99 field: 'organizationId',
100 label: '所属组织', 100 label: '所属组织',
101 - component: 'ApiTreeSelect', 101 + component: 'OrgTreeSelect',
102 required: true, 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 field: 'alarmContactId', 105 field: 'alarmContactId',
1 import { BasicColumn, FormSchema } from '/@/components/Table'; 1 import { BasicColumn, FormSchema } from '/@/components/Table';
2 -import { getOrganizationList } from '/@/api/system/system';  
3 -import { copyTransFun } from '/@/utils/fnUtils';  
4 import { emailRule, phoneRule } from '/@/utils/rules'; 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 export const columns: BasicColumn[] = [ 7 export const columns: BasicColumn[] = [
8 { 8 {
@@ -78,14 +78,7 @@ export const formSchema: FormSchema[] = [ @@ -78,14 +78,7 @@ export const formSchema: FormSchema[] = [
78 field: 'organizationId', 78 field: 'organizationId',
79 label: '所属组织', 79 label: '所属组织',
80 required: true, 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 field: 'phone', 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,7 +25,7 @@ export const schemas: FormSchema[] = [
25 { 25 {
26 field: FieldsEnum.ACCESS_CREDENTIALS, 26 field: FieldsEnum.ACCESS_CREDENTIALS,
27 label: '访问凭证', 27 label: '访问凭证',
28 - component: 'Input', 28 + component: 'InputPassword',
29 ifShow: ({ model }) => model[FieldsEnum.IS_SHARE], 29 ifShow: ({ model }) => model[FieldsEnum.IS_SHARE],
30 componentProps: { 30 componentProps: {
31 maxLength: 64, 31 maxLength: 64,
@@ -3,9 +3,13 @@ @@ -3,9 +3,13 @@
3 import { BasicModal, useModalInner } from '/@/components/Modal'; 3 import { BasicModal, useModalInner } from '/@/components/Modal';
4 import { FieldsEnum, schemas, ViewTypeEnum } from './config'; 4 import { FieldsEnum, schemas, ViewTypeEnum } from './config';
5 import { DataBoardRecord } from '/@/api/dataBoard/model'; 5 import { DataBoardRecord } from '/@/api/dataBoard/model';
6 - import { Alert } from 'ant-design-vue'; 6 + import { Alert, Button, Tooltip } from 'ant-design-vue';
7 import { ref, unref } from 'vue'; 7 import { ref, unref } from 'vue';
8 import { nextTick } from 'vue'; 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 const props = defineProps<{ 14 const props = defineProps<{
11 shareApi?: (...args: any[]) => Promise<any>; 15 shareApi?: (...args: any[]) => Promise<any>;
@@ -13,15 +17,20 @@ @@ -13,15 +17,20 @@
13 17
14 const loading = ref(false); 18 const loading = ref(false);
15 const record = ref<DataBoardRecord>({} as unknown as DataBoardRecord); 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 const [registerForm, { getFieldsValue, setFieldsValue }] = useForm({ 35 const [registerForm, { getFieldsValue, setFieldsValue }] = useForm({
27 schemas, 36 schemas,
@@ -32,6 +41,11 @@ @@ -32,6 +41,11 @@
32 41
33 const emit = defineEmits(['register', 'success']); 42 const emit = defineEmits(['register', 'success']);
34 43
  44 + const handleOpenLink = () => {
  45 + window.open(unref(link));
  46 + };
  47 + const { createMessage } = useMessage();
  48 +
35 const handleSubmit = async () => { 49 const handleSubmit = async () => {
36 try { 50 try {
37 loading.value = true; 51 loading.value = true;
@@ -40,10 +54,23 @@ @@ -40,10 +54,23 @@
40 await props?.shareApi?.({ id, ...value }); 54 await props?.shareApi?.({ id, ...value });
41 closeModal(); 55 closeModal();
42 emit('success'); 56 emit('success');
  57 + createMessage.success('操作成功');
43 } finally { 58 } finally {
44 loading.value = false; 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 </script> 74 </script>
48 75
49 <template> 76 <template>
@@ -54,5 +81,27 @@ @@ -54,5 +81,27 @@
54 message="私有视图只有项目成员可以浏览,公开视图拥有一个公开的 URL,任何人无需登录即可浏览." 81 message="私有视图只有项目成员可以浏览,公开视图拥有一个公开的 URL,任何人无需登录即可浏览."
55 /> 82 />
56 <BasicForm @register="registerForm" /> 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 </BasicModal> 106 </BasicModal>
58 </template> 107 </template>
1 import { BasicColumn, FormSchema } from '/@/components/Table'; 1 import { BasicColumn, FormSchema } from '/@/components/Table';
2 -import { getOrganizationList } from '/@/api/system/system';  
3 -import { copyTransFun } from '/@/utils/fnUtils';  
4 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; 2 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
5 import { createImgPreview } from '/@/components/Preview'; 3 import { createImgPreview } from '/@/components/Preview';
6 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter'; 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 export enum Platform { 9 export enum Platform {
8 PHONE = 'phone', 10 PHONE = 'phone',
9 PC = 'pc', 11 PC = 'pc',
@@ -126,14 +128,7 @@ export const formSchema: FormSchema[] = [ @@ -126,14 +128,7 @@ export const formSchema: FormSchema[] = [
126 field: 'organizationId', 128 field: 'organizationId',
127 label: '所属组织', 129 label: '所属组织',
128 required: true, 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 field: 'platform', 134 field: 'platform',
@@ -28,11 +28,15 @@ @@ -28,11 +28,15 @@
28 import { ViewTypeNameEnum } from '../../common/ShareModal/config'; 28 import { ViewTypeNameEnum } from '../../common/ShareModal/config';
29 import { useModal } from '/@/components/Modal'; 29 import { useModal } from '/@/components/Modal';
30 import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'; 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 const listColumn = ref(5); 34 const listColumn = ref(5);
33 35
34 const { createMessage } = useMessage(); 36 const { createMessage } = useMessage();
35 37
  38 + const { isCustomerUser } = useRole();
  39 +
36 const organizationId = ref<Nullable<number>>(null); 40 const organizationId = ref<Nullable<number>>(null);
37 41
38 const pagination = reactive<PaginationProps>({ 42 const pagination = reactive<PaginationProps>({
@@ -153,17 +157,20 @@ @@ -153,17 +157,20 @@
153 getListData(); 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 const searchParams = new URLSearchParams(); 161 const searchParams = new URLSearchParams();
161 isDev && searchParams.set('dev', '1'); 162 isDev && searchParams.set('dev', '1');
162 searchParams.set('share', 'SCADA'); 163 searchParams.set('share', 'SCADA');
163 searchParams.set('configurationId', record.id); 164 searchParams.set('configurationId', record.id);
164 searchParams.set('publicId', record.publicId || ''); 165 searchParams.set('publicId', record.publicId || '');
165 searchParams.set('lightbox', '1'); 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 clipboardRef.value = url; 174 clipboardRef.value = url;
168 if (unref(isSuccessRef)) { 175 if (unref(isSuccessRef)) {
169 createMessage.success('复制成功~'); 176 createMessage.success('复制成功~');
@@ -173,7 +180,7 @@ @@ -173,7 +180,7 @@
173 const [registerShareModal, { openModal }] = useModal(); 180 const [registerShareModal, { openModal }] = useModal();
174 181
175 const handleOpenShareModal = (record: ConfigurationCenterItemsModal) => { 182 const handleOpenShareModal = (record: ConfigurationCenterItemsModal) => {
176 - openModal(true, record); 183 + openModal(true, { record, href: createShareUrl(record) });
177 }; 184 };
178 185
179 const listEl = ref<Nullable<ComponentElRef>>(null); 186 const listEl = ref<Nullable<ComponentElRef>>(null);
@@ -215,7 +222,7 @@ @@ -215,7 +222,7 @@
215 > 222 >
216 <template #header> 223 <template #header>
217 <div class="flex gap-3 justify-end"> 224 <div class="flex gap-3 justify-end">
218 - <Authority :value="ConfigurationPermission.CREATE"> 225 + <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE">
219 <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button> 226 <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button>
220 </Authority> 227 </Authority>
221 <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" /> 228 <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />
@@ -228,7 +235,13 @@ @@ -228,7 +235,13 @@
228 </template> 235 </template>
229 <template #renderItem="{ item }"> 236 <template #renderItem="{ item }">
230 <List.Item> 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 <template #cover> 245 <template #cover>
233 <div 246 <div
234 class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative" 247 class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative"
@@ -255,7 +268,7 @@ @@ -255,7 +268,7 @@
255 @click="handlePreview(item)" 268 @click="handlePreview(item)"
256 /> 269 />
257 </Tooltip> 270 </Tooltip>
258 - <Tooltip title="设计"> 271 + <Tooltip v-if="!isCustomerUser" title="设计">
259 <AuthIcon 272 <AuthIcon
260 :auth="ConfigurationPermission.DESIGN" 273 :auth="ConfigurationPermission.DESIGN"
261 class="!text-lg" 274 class="!text-lg"
@@ -273,6 +286,7 @@ @@ -273,6 +286,7 @@
273 /> 286 />
274 </Tooltip> 287 </Tooltip>
275 <AuthDropDown 288 <AuthDropDown
  289 + v-if="!isCustomerUser"
276 :dropMenuList="[ 290 :dropMenuList="[
277 { 291 {
278 text: '分享', 292 text: '分享',
@@ -357,6 +371,6 @@ @@ -357,6 +371,6 @@
357 } 371 }
358 372
359 .card-container:deep(.ant-card-cover) { 373 .card-container:deep(.ant-card-cover) {
360 - background-color: #2db7f5; 374 + background-color: var(--viewType);
361 } 375 }
362 </style> 376 </style>
@@ -2,7 +2,70 @@ @@ -2,7 +2,70 @@
2 <div> 2 <div>
3 <!-- 首页基础信息 --> 3 <!-- 首页基础信息 -->
4 <div class="md:flex"> 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 <div class="flex" style="height: 100px"> 69 <div class="flex" style="height: 100px">
7 <div class="mr-4" 70 <div class="mr-4"
8 ><img 71 ><img
@@ -33,7 +96,7 @@ @@ -33,7 +96,7 @@
33 今日新增 {{ toThousands(growCardList?.deviceInfo?.todayAdd) }} 96 今日新增 {{ toThousands(growCardList?.deviceInfo?.todayAdd) }}
34 </div> 97 </div>
35 </Card> 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 <div class="flex" style="height: 100px"> 100 <div class="flex" style="height: 100px">
38 <div class="mr-4"> 101 <div class="mr-4">
39 <img 102 <img
@@ -70,7 +133,7 @@ @@ -70,7 +133,7 @@
70 growCardList?.alarmInfo?.todayAdd 133 growCardList?.alarmInfo?.todayAdd
71 )}` 134 )}`
72 : `租户总量:${growCardList?.tenantInfo?.sumCount} 今日新增 ${toThousands( 135 : `租户总量:${growCardList?.tenantInfo?.sumCount} 今日新增 ${toThousands(
73 - growCardList?.alarmInfo?.todayAdd 136 + growCardList?.tenantInfo?.todayAdd
74 )}` 137 )}`
75 }} 138 }}
76 </template> 139 </template>
@@ -87,7 +150,7 @@ @@ -87,7 +150,7 @@
87 今日新增 {{ toThousands(growCardList?.tenantInfo?.todayAdd) }}</div 150 今日新增 {{ toThousands(growCardList?.tenantInfo?.todayAdd) }}</div
88 > 151 >
89 </Card> 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 <div class="flex" style="height: 100px"> 154 <div class="flex" style="height: 100px">
92 <div class="mr-4"> 155 <div class="mr-4">
93 <img 156 <img
@@ -104,8 +167,8 @@ @@ -104,8 +167,8 @@
104 style="font-size: 1.625rem; color: #333; font-weight: bold" 167 style="font-size: 1.625rem; color: #333; font-weight: bold"
105 > 168 >
106 <CountTo 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 <CountTo v-else :end-val="0" /> 173 <CountTo v-else :end-val="0" />
111 </div> 174 </div>
@@ -120,88 +183,25 @@ @@ -120,88 +183,25 @@
120 <template #title> 183 <template #title>
121 {{ 184 {{
122 !isAdmin(role) 185 !isAdmin(role)
123 - ? `消息数:${growCardList?.messageInfo?.messageCount} 今日新增 ${toThousands( 186 + ? `今日消息数:${
124 growCardList?.messageInfo?.todayMessageAdd 187 growCardList?.messageInfo?.todayMessageAdd
125 - )}` 188 + } 近30日新增 ${toThousands(growCardList?.messageInfo?.messageCount)}`
126 : `客户总量:${growCardList?.customerInfo?.sumCount} 今日新增 ${toThousands( 189 : `客户总量:${growCardList?.customerInfo?.sumCount} 今日新增 ${toThousands(
127 - growCardList?.messageInfo?.todayMessageAdd 190 + growCardList?.customerInfo?.todayAdd
128 )}` 191 )}`
129 }} 192 }}
130 </template> 193 </template>
131 <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" /> 194 <img src="/src/assets/images/tip.png" style="width: 1.125rem; height: 1.125rem" />
132 </Tooltip> 195 </Tooltip>
133 </div> 196 </div>
134 - <div> {{ !isAdmin(role) ? `消息数` : '客户总量' }}</div> 197 + <div> {{ !isAdmin(role) ? `今日消息数` : '客户总量' }}</div>
135 </div> 198 </div>
136 </div> 199 </div>
137 <div v-if="!isAdmin(role)" class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5"> 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 <div v-else class="ml-2 pt-4" style="border-top: 2px solid #f0f2f5"> 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 </Card> 206 </Card>
207 </div> 207 </div>
@@ -22,9 +22,12 @@ @@ -22,9 +22,12 @@
22 shallowReactive, 22 shallowReactive,
23 onUnmounted, 23 onUnmounted,
24 nextTick, 24 nextTick,
  25 + computed,
  26 + watch,
25 } from 'vue'; 27 } from 'vue';
26 import * as echarts from 'echarts'; 28 import * as echarts from 'echarts';
27 import { seriesDataT } from './props'; 29 import { seriesDataT } from './props';
  30 + import { useAppStore } from '/@/store/modules/app';
28 31
29 export default defineComponent({ 32 export default defineComponent({
30 props: { 33 props: {
@@ -35,10 +38,29 @@ @@ -35,10 +38,29 @@
35 }, 38 },
36 39
37 setup(props) { 40 setup(props) {
  41 + const appStore = useAppStore();
  42 + const skinName = computed(() => {
  43 + return appStore.getDarkMode === 'light' ? '#ffffff' : '#151515';
  44 + });
  45 +
38 const chartsInstance = shallowReactive<{ [key: string]: echarts.ECharts }>({}); 46 const chartsInstance = shallowReactive<{ [key: string]: echarts.ECharts }>({});
39 47
40 const { seriesStatusData } = toRefs(props); 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 const total = seriesStatusData.value 64 const total = seriesStatusData.value
43 .map((m) => m.value) 65 .map((m) => m.value)
44 .reduce((prev, cur) => prev! + cur!, 0); 66 .reduce((prev, cur) => prev! + cur!, 0);
@@ -61,8 +83,9 @@ @@ -61,8 +83,9 @@
61 chartsInstance[key!] = echarts.init( 83 chartsInstance[key!] = echarts.init(
62 document.getElementById(`chartPie${key}`) as HTMLElement 84 document.getElementById(`chartPie${key}`) as HTMLElement
63 ); 85 );
  86 + console.log('11', skinName.value);
64 const option = { 87 const option = {
65 - backgroundColor: '#ffffff', 88 + backgroundColor: skinName.value,
66 tooltip: { 89 tooltip: {
67 trigger: 'item', 90 trigger: 'item',
68 formatter: `${legendKey}设备${((value! / total!) * 100).toFixed(2)}%`, 91 formatter: `${legendKey}设备${((value! / total!) * 100).toFixed(2)}%`,
@@ -101,6 +124,9 @@ @@ -101,6 +124,9 @@
101 ], 124 ],
102 }; 125 };
103 chartsInstance[key!].setOption(option); 126 chartsInstance[key!].setOption(option);
  127 + // chartsInstance[key!].setOption({
  128 + // backgroundColor: skinName,
  129 + // });
104 } 130 }
105 }); 131 });
106 132
@@ -5,10 +5,11 @@ @@ -5,10 +5,11 @@
5 /></div> 5 /></div>
6 </template> 6 </template>
7 <script lang="ts"> 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 import { useECharts } from '/@/hooks/web/useECharts'; 9 import { useECharts } from '/@/hooks/web/useECharts';
10 import { Empty } from 'ant-design-vue'; 10 import { Empty } from 'ant-design-vue';
11 import { seriesDataT } from './props'; 11 import { seriesDataT } from './props';
  12 + import { useAppStore } from '/@/store/modules/app';
12 13
13 export default defineComponent({ 14 export default defineComponent({
14 components: { Empty }, 15 components: { Empty },
@@ -35,23 +36,22 @@ @@ -35,23 +36,22 @@
35 }, 36 },
36 }, 37 },
37 setup(props) { 38 setup(props) {
  39 + const appStore = useAppStore();
38 const { legendData, seriesData } = toRefs(props); 40 const { legendData, seriesData } = toRefs(props);
39 const dataSeries: Ref<seriesDataT[]> = ref([]); 41 const dataSeries: Ref<seriesDataT[]> = ref([]);
40 const legendDatas: Ref<seriesDataT[]> = ref([]); 42 const legendDatas: Ref<seriesDataT[]> = ref([]);
41 dataSeries.value = seriesData.value as unknown as seriesDataT[]; 43 dataSeries.value = seriesData.value as unknown as seriesDataT[];
42 legendDatas.value = legendData.value as unknown as seriesDataT[]; 44 legendDatas.value = legendData.value as unknown as seriesDataT[];
43 - 45 + const skinName = computed(() => {
  46 + return appStore.getDarkMode === 'light' ? '#ffffff' : '#151515';
  47 + });
44 const chartRef = ref<HTMLDivElement | null>(null); 48 const chartRef = ref<HTMLDivElement | null>(null);
  49 +
45 const { setOptions, resize } = useECharts(chartRef as Ref<HTMLDivElement>); 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 tooltip: { 55 tooltip: {
56 trigger: 'item', 56 trigger: 'item',
57 formatter: '{b} {d}%', 57 formatter: '{b} {d}%',
@@ -72,7 +72,29 @@ @@ -72,7 +72,29 @@
72 labelLine, 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 window.addEventListener('resize', () => resize()); 99 window.addEventListener('resize', () => resize());
78 }); 100 });
@@ -42,8 +42,6 @@ @@ -42,8 +42,6 @@
42 42
43 const role: string = userInfo?.roles[0]; 43 const role: string = userInfo?.roles[0];
44 44
45 - console.log({ role });  
46 -  
47 const loading = ref(true); 45 const loading = ref(true);
48 46
49 setTimeout(() => { 47 setTimeout(() => {
1 import { FormSchema } from '/@/components/Table'; 1 import { FormSchema } from '/@/components/Table';
2 -import { getOrganizationList } from '/@/api/system/system';  
3 -import { copyTransFun } from '/@/utils/fnUtils';  
4 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue'; 2 import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
5 import { createImgPreview } from '/@/components/Preview'; 3 import { createImgPreview } from '/@/components/Preview';
6 import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter'; 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 export enum Platform { 10 export enum Platform {
8 PHONE = 0, 11 PHONE = 0,
9 PC = 1, 12 PC = 1,
@@ -16,6 +19,8 @@ export enum ConfigurationPermission { @@ -16,6 +19,8 @@ export enum ConfigurationPermission {
16 SHARE = 'api:yt:dataview:center:share', 19 SHARE = 'api:yt:dataview:center:share',
17 DESIGN = 'api:yt:dataview:center:get_configuration_info:design', 20 DESIGN = 'api:yt:dataview:center:get_configuration_info:design',
18 PREVIEW = 'api:yt:dataview:center:get_configuration_info:preview', 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,29 +83,8 @@ export const formSchema: FormSchema[] = [
78 field: 'organizationId', 83 field: 'organizationId',
79 label: '所属组织', 84 label: '所属组织',
80 required: true, 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 field: 'remark', 89 field: 'remark',
106 label: '备注', 90 label: '备注',
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue'; 2 import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue';
3 import { ReloadOutlined } from '@ant-design/icons-vue'; 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 import { OrganizationIdTree, useResetOrganizationTree } from '../common/organizationIdTree'; 5 import { OrganizationIdTree, useResetOrganizationTree } from '../common/organizationIdTree';
6 import { 6 import {
7 bigScreenCancelPublish, 7 bigScreenCancelPublish,
@@ -29,6 +29,10 @@ @@ -29,6 +29,10 @@
29 import { ShareModal } from '/@/views/common/ShareModal'; 29 import { ShareModal } from '/@/views/common/ShareModal';
30 import { ViewTypeNameEnum } from '../common/ShareModal/config'; 30 import { ViewTypeNameEnum } from '../common/ShareModal/config';
31 import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard'; 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 const listColumn = ref(5); 37 const listColumn = ref(5);
34 38
@@ -48,6 +52,7 @@ @@ -48,6 +52,7 @@
48 }); 52 });
49 53
50 const loading = ref(false); 54 const loading = ref(false);
  55 + const { isCustomerUser } = useRole();
51 56
52 const dataSource = ref<BigScreenCenterItemsModel[]>([]); 57 const dataSource = ref<BigScreenCenterItemsModel[]>([]);
53 58
@@ -163,28 +168,31 @@ @@ -163,28 +168,31 @@
163 const getPublicApiListData = () => {}; 168 const getPublicApiListData = () => {};
164 169
165 const [registerShareModal, { openModal }] = useModal(); 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 const { clipboardRef, isSuccessRef } = useCopyToClipboard(); 185 const { clipboardRef, isSuccessRef } = useCopyToClipboard();
171 const handleCreateShareUrl = (record: BigScreenCenterItemsModel) => { 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 if (unref(isSuccessRef)) { 188 if (unref(isSuccessRef)) {
176 createMessage.success('复制成功~'); 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 getListData(); 196 getListData();
189 }; 197 };
190 </script> 198 </script>
@@ -207,10 +215,10 @@ @@ -207,10 +215,10 @@
207 > 215 >
208 <template #header> 216 <template #header>
209 <div class="flex gap-3 justify-end"> 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 </Authority> 220 </Authority>
213 - <Authority :value="ConfigurationPermission.CREATE"> 221 + <Authority v-if="hasPublicInterfacePermission" :value="ConfigurationPermission.CREATE">
214 <Button type="primary" @click="handleCreateOrUpdatePublicApi()">公共接口管理</Button> 222 <Button type="primary" @click="handleCreateOrUpdatePublicApi()">公共接口管理</Button>
215 </Authority> 223 </Authority>
216 <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" /> 224 <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />
@@ -223,7 +231,12 @@ @@ -223,7 +231,12 @@
223 </template> 231 </template>
224 <template #renderItem="{ item }"> 232 <template #renderItem="{ item }">
225 <List.Item> 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 <template #cover> 240 <template #cover>
228 <div class="h-full w-full relative hover-show-modal-content img-container"> 241 <div class="h-full w-full relative hover-show-modal-content img-container">
229 <img 242 <img
@@ -265,7 +278,7 @@ @@ -265,7 +278,7 @@
265 @click="handlePreview(item)" 278 @click="handlePreview(item)"
266 /> 279 />
267 </Tooltip> 280 </Tooltip>
268 - <Tooltip title="设计"> 281 + <Tooltip v-if="!isCustomerUser" title="设计">
269 <AuthIcon 282 <AuthIcon
270 :disabled="item.state === 1" 283 :disabled="item.state === 1"
271 icon="ant-design:edit-outlined" 284 icon="ant-design:edit-outlined"
@@ -282,6 +295,7 @@ @@ -282,6 +295,7 @@
282 /> 295 />
283 </Tooltip> 296 </Tooltip>
284 <AuthDropDown 297 <AuthDropDown
  298 + v-if="!isCustomerUser"
285 :dropMenuList="[ 299 :dropMenuList="[
286 { 300 {
287 text: '分享', 301 text: '分享',
@@ -291,19 +305,14 @@ @@ -291,19 +305,14 @@
291 onClick: handleOpenShareModal.bind(null, item), 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 event: '', 314 event: '',
298 onClick: handlePublish.bind(null, item), 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 text: '编辑', 318 text: '编辑',
@@ -421,6 +430,6 @@ @@ -421,6 +430,6 @@
421 } 430 }
422 431
423 .card-container:deep(.ant-card-cover) { 432 .card-container:deep(.ant-card-cover) {
424 - background-color: #2db7f5; 433 + background-color: var(--viewType);
425 } 434 }
426 </style> 435 </style>