Commit b1fea99bc5155d6014b6d3550f9f4b42a45f51ba

Authored by 张 峰林
Committed by xp.Huang
1 parent b65d9399

feat:看板添加手机端逻辑

@@ -53,6 +53,7 @@ export interface DataBoardRecord { @@ -53,6 +53,7 @@ export interface DataBoardRecord {
53 publicId: string; 53 publicId: string;
54 organizationId?: string; 54 organizationId?: string;
55 accessCredentials?: string; 55 accessCredentials?: string;
  56 + platform?: string;
56 } 57 }
57 58
58 export interface DataBoardList { 59 export interface DataBoardList {
@@ -137,7 +138,12 @@ export interface MasterDeviceList { @@ -137,7 +138,12 @@ export interface MasterDeviceList {
137 } 138 }
138 139
139 export interface ComponentInfoDetail { 140 export interface ComponentInfoDetail {
140 - data: { componentData: DataComponentRecord[]; componentLayout: Layout[] }; 141 + data: {
  142 + componentData: DataComponentRecord[];
  143 + componentLayout: Layout[];
  144 + phoneModel?: string | object;
  145 + platform: string;
  146 + };
141 } 147 }
142 148
143 export interface DeviceAttributeParams { 149 export interface DeviceAttributeParams {
@@ -21,6 +21,7 @@ enum Api { @@ -21,6 +21,7 @@ enum Api {
21 SendLoginSmsCode = '/noauth/send_login_code/', 21 SendLoginSmsCode = '/noauth/send_login_code/',
22 ResetCode = '/noauth/reset_code/', 22 ResetCode = '/noauth/reset_code/',
23 ResetPassword = '/noauth/reset/', 23 ResetPassword = '/noauth/reset/',
  24 + APP_GET_TOKEN = '/third/login/id/',
24 } 25 }
25 26
26 /** 27 /**
@@ -100,3 +101,9 @@ export const getUserToken = (id: string) => { @@ -100,3 +101,9 @@ export const getUserToken = (id: string) => {
100 url: `/third/login/id/${id}`, 101 url: `/third/login/id/${id}`,
101 }); 102 });
102 }; 103 };
  104 +
  105 +export const doAppLogin = (userId: string) => {
  106 + return defHttp.get<Record<'refreshToken' | 'token', string>>({
  107 + url: `${Api.APP_GET_TOKEN}${userId}`,
  108 + });
  109 +};
  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="1702374950759" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1997" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M144.700101 684.994006l535.580045 0L680.280146 359.237781 144.700101 359.237781 144.700101 684.994006 144.700101 684.994006zM918.373823 440.680675l0-81.442894c0-44.791136-36.649711-81.437777-81.437777-81.437777l-692.235944 0c-44.791136 0-81.437777 36.646642-81.437777 81.437777L63.262324 684.994006c0 44.791136 36.646642 81.442894 81.437777 81.442894l692.235944 0c44.788066 0 81.437777-36.650735 81.437777-81.442894l0-81.437777c22.396079 0 40.7194-18.322297 40.7194-40.7194l0-81.436754C959.093223 459.003995 940.769902 440.680675 918.373823 440.680675L918.373823 440.680675zM877.655446 481.400075l0 81.436754L877.655446 684.994006c0 22.395056-18.323321 40.718377-40.7194 40.718377l-692.235944 0c-22.396079 0-40.7194-18.323321-40.7194-40.718377L103.980701 359.237781c0-22.396079 18.323321-40.7194 40.7194-40.7194l692.235944 0c22.396079 0 40.7194 18.323321 40.7194 40.7194L877.655446 481.400075 877.655446 481.400075zM877.655446 481.400075" fill="#272636" p-id="1998"></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="1702374946029" class="icon" viewBox="0 0 1294 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1850" xmlns:xlink="http://www.w3.org/1999/xlink" width="252.734375" height="200"><path d="M0 727.578947l188.631579 0 0 296.421053-188.631579 0 0-296.421053Z" p-id="1851"></path><path d="M269.473684 565.894737l188.631579 0 0 458.105263-188.631579 0 0-458.105263Z" p-id="1852"></path><path d="M565.894737 377.263158l161.684211 0 0 646.736842-161.684211 0 0-646.736842Z" p-id="1853"></path><path d="M835.368421 188.631579l188.631579 0 0 835.368421-188.631579 0 0-835.368421Z" p-id="1854"></path><path d="M1104.842105 0l188.631579 0 0 1024-188.631579 0 0-1024Z" p-id="1855"></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="1702374954963" class="icon" viewBox="0 0 1280 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2277" xmlns:xlink="http://www.w3.org/1999/xlink" width="250" height="200"><path d="M1269.82 309.76C915.48-17.98 364.38-17.86 10.18 309.76c-13.32 12.32-13.58 33.18-0.7 45.96l68.48 67.94c12.28 12.2 32.04 12.46 44.8 0.76 291.84-267.36 742.6-267.42 1034.5 0 12.76 11.7 32.52 11.42 44.8-0.76l68.48-67.94c12.86-12.78 12.6-33.64-0.72-45.96zM640 704c-70.7 0-128 57.3-128 128s57.3 128 128 128 128-57.3 128-128-57.3-128-128-128z m405.34-167.18c-230.52-203.86-580.42-203.64-810.68 0-13.8 12.2-14.24 33.38-1.14 46.3l68.88 67.98c12 11.84 31.32 12.64 44.1 1.6 167.9-145.14 419.48-144.82 586.98 0 12.78 11.04 32.1 10.26 44.1-1.6l68.88-67.98c13.12-12.92 12.66-34.12-1.12-46.3z" p-id="2278"></path></svg>
@@ -16,6 +16,7 @@ export const PageEnum = { @@ -16,6 +16,7 @@ export const PageEnum = {
16 DEVICE_LIST: '/device/list', 16 DEVICE_LIST: '/device/list',
17 17
18 SHARE_PAGE: '/share/:viewType/:id/:publicId', 18 SHARE_PAGE: '/share/:viewType/:id/:publicId',
  19 + APP_PAGE: '/appPage/:boardId/:userId',
19 20
20 RULE_CHAIN_DETAIL: '/rule/chain/:id', 21 RULE_CHAIN_DETAIL: '/rule/chain/:id',
21 22
@@ -2,7 +2,7 @@ import { RouteLocationNormalizedLoaded } from 'vue-router'; @@ -2,7 +2,7 @@ import { RouteLocationNormalizedLoaded } from 'vue-router';
2 2
3 const menuMap = new Map(); 3 const menuMap = new Map();
4 4
5 -menuMap.set('/visual/board/detail/:boardId/:boardName/:organizationId?', '/visual/board'); 5 +menuMap.set('/visual/board/detail/:boardId/:boardName/:platform/:organizationId?', '/visual/board');
6 menuMap.set('/rule/chain/:id', '/rule/chain'); 6 menuMap.set('/rule/chain/:id', '/rule/chain');
7 7
8 export const useMenuActiveFix = (route: RouteLocationNormalizedLoaded) => { 8 export const useMenuActiveFix = (route: RouteLocationNormalizedLoaded) => {
@@ -11,7 +11,8 @@ import { getAuthCache } from '/@/utils/auth'; @@ -11,7 +11,8 @@ import { getAuthCache } from '/@/utils/auth';
11 const LOGIN_PATH = PageEnum.BASE_LOGIN; 11 const LOGIN_PATH = PageEnum.BASE_LOGIN;
12 const ROOT_PATH = RootRoute.path; 12 const ROOT_PATH = RootRoute.path;
13 const SHARE_PATH = PageEnum.SHARE_PAGE; 13 const SHARE_PATH = PageEnum.SHARE_PAGE;
14 -const whitePathList: string[] = [LOGIN_PATH, SHARE_PATH]; 14 +const APP_PATH = PageEnum.APP_PAGE;
  15 +const whitePathList: string[] = [LOGIN_PATH, SHARE_PATH, APP_PATH];
15 // const userInfo1 = getAuthCache(USER_INFO_KEY); 16 // const userInfo1 = getAuthCache(USER_INFO_KEY);
16 // const userInfo = ref(userInfo1); 17 // const userInfo = ref(userInfo1);
17 18
  1 +import { AppRouteRecordRaw } from '../types';
  2 +import { PageEnum } from '/@/enums/pageEnum';
  3 +
  4 +export const APP_PAGE_ROUTER: AppRouteRecordRaw = {
  5 + path: PageEnum.APP_PAGE,
  6 + name: 'appPage',
  7 + component: () => import('/@/views/sys/appPage/index.vue'),
  8 + meta: {
  9 + title: '公开',
  10 + hideBreadcrumb: true,
  11 + hideChildrenInMenu: true,
  12 + },
  13 +};
@@ -5,6 +5,7 @@ import { PageEnum } from '/@/enums/pageEnum'; @@ -5,6 +5,7 @@ import { PageEnum } from '/@/enums/pageEnum';
5 import { t } from '/@/hooks/web/useI18n'; 5 import { t } from '/@/hooks/web/useI18n';
6 import { LAYOUT } from '../constant'; 6 import { LAYOUT } from '../constant';
7 import { PUBLIC_PAGE_ROUTER } from './public'; 7 import { PUBLIC_PAGE_ROUTER } from './public';
  8 +import { APP_PAGE_ROUTER } from './appPage';
8 9
9 const modules = import.meta.globEager('./modules/**/*.ts'); 10 const modules = import.meta.globEager('./modules/**/*.ts');
10 const routeModuleList: AppRouteModule[] = []; 11 const routeModuleList: AppRouteModule[] = [];
@@ -87,4 +88,5 @@ export const basicRoutes = [ @@ -87,4 +88,5 @@ export const basicRoutes = [
87 REDIRECT_ROUTE, 88 REDIRECT_ROUTE,
88 PAGE_NOT_FOUND_ROUTE, 89 PAGE_NOT_FOUND_ROUTE,
89 PUBLIC_PAGE_ROUTER, 90 PUBLIC_PAGE_ROUTER,
  91 + APP_PAGE_ROUTER,
90 ]; 92 ];
@@ -105,7 +105,7 @@ export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => { @@ -105,7 +105,7 @@ export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => {
105 field: FormFieldsEnum.ALARM_PROFILED, 105 field: FormFieldsEnum.ALARM_PROFILED,
106 label: '', 106 label: '',
107 component: 'AlarmProfileSelect', 107 component: 'AlarmProfileSelect',
108 - rules: [{ required: true, message: `请选择${FormFieldsEnum.ALARM_PROFILED}` }], 108 + // rules: [{ required: true, message: `请选择${FormFieldsEnum.ALARM_PROFILED}` }],
109 ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.MSG_NOTIFY, 109 ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.MSG_NOTIFY,
110 componentProps: () => { 110 componentProps: () => {
111 return { 111 return {
  1 +import { useRouter } from 'vue-router';
  2 +
  3 +export enum ViewTypeEnum {
  4 + DATA_BOARD = 'DATA_BOARD',
  5 + LARGE_SCREEN = 'LARGE_SCREEN',
  6 + SCADA = 'SCADA',
  7 +}
  8 +
  9 +export const goShareUrl = (options: { type: ViewTypeEnum; id: string }, openNew?: false) => {
  10 + const { type, id } = options;
  11 + const ROUTER = useRouter();
  12 + const { origin } = location;
  13 + const path = `/share/${type}/${id}`;
  14 + openNew ? ROUTER.push(path) : open(`${origin}${path}`);
  15 +};
  1 +export const isShareMode = () => {
  2 + const sharePageReg = /^\/share\/[^/]+\/[^/]+\/[^/]+$/;
  3 + const { pathname } = location;
  4 + return sharePageReg.test(pathname);
  5 +};
  1 +<script lang="ts" setup>
  2 + import { onMounted } from 'vue';
  3 + import { Spin } from 'ant-design-vue';
  4 + import { ref } from 'vue';
  5 + import { useRoute } from 'vue-router';
  6 + import { useUserStore } from '/@/store/modules/user';
  7 + import BoardDetail from '/@/views/visual/board/detail/index.vue';
  8 + import { doAppLogin } from '/@/api/sys/user';
  9 +
  10 + const loading = ref(true);
  11 + const ROUTE = useRoute();
  12 + const contentData = ref<any>();
  13 + const canLoadComponent = ref(false);
  14 +
  15 + const modelVisable = ref(false);
  16 +
  17 + const userStore = useUserStore();
  18 +
  19 + const getShareToken = async () => {
  20 + const { params } = ROUTE;
  21 + const { userId }: any = params;
  22 + const { token, refreshToken } = await doAppLogin(userId);
  23 + userStore.storeToken(token, refreshToken);
  24 + };
  25 +
  26 + const getContentLoading = ref(false);
  27 + const getContentData = async () => {
  28 + try {
  29 + getContentLoading.value = true;
  30 + loading.value = false;
  31 + canLoadComponent.value = true;
  32 + modelVisable.value = false;
  33 + } catch (error) {
  34 + } finally {
  35 + getContentLoading.value = false;
  36 + }
  37 + };
  38 +
  39 + onMounted(async () => {
  40 + await getShareToken();
  41 + await getContentData();
  42 + });
  43 +</script>
  44 +
  45 +<template>
  46 + <Spin
  47 + :spinning="loading"
  48 + tip="正在加载中..."
  49 + size="large"
  50 + class="!flex justify-center items-center w-full h-full share-full-loading"
  51 + >
  52 + <BoardDetail v-if="canLoadComponent" :value="contentData" />
  53 + </Spin>
  54 +</template>
  55 +
  56 +<style lang="less">
  57 + .share-page-token-modal {
  58 + .ant-modal-close {
  59 + display: none;
  60 + }
  61 + }
  62 +</style>
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 import { BasicModal, useModalInner } from '/@/components/Modal'; 2 import { BasicModal, useModalInner } from '/@/components/Modal';
3 import { BasicForm, useForm } from '/@/components/Form'; 3 import { BasicForm, useForm } from '/@/components/Form';
4 - import { formSchema } from '../config/panelDetail'; 4 + import { formSchema, PlatformType } from '../config/panelDetail';
5 import { addDataBoard, updateDataBoard } from '/@/api/dataBoard'; 5 import { addDataBoard, updateDataBoard } from '/@/api/dataBoard';
6 import { AddDataBoardParams } from '/@/api/dataBoard/model'; 6 import { AddDataBoardParams } from '/@/api/dataBoard/model';
7 import { useMessage } from '/@/hooks/web/useMessage'; 7 import { useMessage } from '/@/hooks/web/useMessage';
@@ -34,6 +34,9 @@ @@ -34,6 +34,9 @@
34 if (!Reflect.get(value, 'accessCredentials')) { 34 if (!Reflect.get(value, 'accessCredentials')) {
35 Reflect.deleteProperty(value, 'accessCredentials'); 35 Reflect.deleteProperty(value, 'accessCredentials');
36 } 36 }
  37 + if (value.platform === PlatformType.PC) {
  38 + Reflect.deleteProperty(value, 'phoneModel');
  39 + }
37 return value as any; 40 return value as any;
38 }; 41 };
39 42
@@ -4,7 +4,13 @@ export enum ViewType { @@ -4,7 +4,13 @@ export enum ViewType {
4 PRIVATE_VIEW = 'PRIVATE_VIEW', 4 PRIVATE_VIEW = 'PRIVATE_VIEW',
5 PUBLIC_VIEW = 'PUBLIC_VIEW', 5 PUBLIC_VIEW = 'PUBLIC_VIEW',
6 } 6 }
  7 +import { Platform } from '../../palette/components/PagerHeader/config';
  8 +
7 useComponentRegister('OrgTreeSelect', OrgTreeSelect); 9 useComponentRegister('OrgTreeSelect', OrgTreeSelect);
  10 +export enum PlatformType {
  11 + PHONE = 'phone',
  12 + PC = 'pc',
  13 +}
8 14
9 export const formSchema: FormSchema[] = [ 15 export const formSchema: FormSchema[] = [
10 { 16 {
@@ -24,6 +30,32 @@ export const formSchema: FormSchema[] = [ @@ -24,6 +30,32 @@ export const formSchema: FormSchema[] = [
24 rules: [{ required: true, message: '组织为必填项' }], 30 rules: [{ required: true, message: '组织为必填项' }],
25 }, 31 },
26 { 32 {
  33 + field: 'platform',
  34 + label: '平台',
  35 + required: true,
  36 + component: 'RadioGroup',
  37 + defaultValue: PlatformType.PC,
  38 + componentProps({ formModel }) {
  39 + return {
  40 + defaultValue: PlatformType.PC,
  41 + options: [
  42 + { label: 'PC端', value: PlatformType.PC },
  43 + { label: '移动端', value: PlatformType.PHONE },
  44 + ],
  45 + onChange(e) {
  46 + formModel.phoneModel =
  47 + e?.target?.value === PlatformType.PHONE ? JSON.stringify(Platform[1]) : [];
  48 + },
  49 + };
  50 + },
  51 + },
  52 + {
  53 + field: 'phoneModel',
  54 + label: '手机端尺寸',
  55 + component: 'Input',
  56 + ifShow: false,
  57 + },
  58 + {
27 field: 'remark', 59 field: 'remark',
28 label: '备注', 60 label: '备注',
29 component: 'InputTextArea', 61 component: 'InputTextArea',
@@ -135,12 +135,13 @@ @@ -135,12 +135,13 @@
135 135
136 const handleViewBoard = (record: DataBoardRecord) => { 136 const handleViewBoard = (record: DataBoardRecord) => {
137 const hasDetailPermission = hasPermission(VisualBoardPermission.DETAIL); 137 const hasDetailPermission = hasPermission(VisualBoardPermission.DETAIL);
  138 + const { platform = 'pc' } = record || {};
138 if (hasDetailPermission) { 139 if (hasDetailPermission) {
139 const boardId = encode(record.id); 140 const boardId = encode(record.id);
140 const boardName = encode(record.name); 141 const boardName = encode(record.name);
141 const organizationId = encode(record!.organizationId!); 142 const organizationId = encode(record!.organizationId!);
142 143
143 - router.push(`/visual/board/detail/${boardId}/${boardName}/${organizationId}`); 144 + router.push(`/visual/board/detail/${boardId}/${boardName}/${platform}/${organizationId}`);
144 } else createMessage.warning('没有权限'); 145 } else createMessage.warning('没有权限');
145 }; 146 };
146 </script> 147 </script>
@@ -170,7 +171,7 @@ @@ -170,7 +171,7 @@
170 </Dropdown> 171 </Dropdown>
171 </template> 172 </template>
172 <section @click="handleViewBoard(item)"> 173 <section @click="handleViewBoard(item)">
173 - <div class="flex data-card__info"> 174 + <div class="flex data-card__info relative">
174 <div> 175 <div>
175 <div>组件数量</div> 176 <div>组件数量</div>
176 <Statistic class="text-2xl" :value="item.componentNum"> 177 <Statistic class="text-2xl" :value="item.componentNum">
@@ -179,6 +180,12 @@ @@ -179,6 +180,12 @@
179 </template> 180 </template>
180 </Statistic> 181 </Statistic>
181 </div> 182 </div>
  183 + <div class="absolute" style="right: 1%">
  184 + <Icon
  185 + :icon="item.platform === 'pc' ? 'ri:computer-line' : 'clarity:mobile-phone-solid'"
  186 + style="font-size: 32px"
  187 + />
  188 + </div>
182 </div> 189 </div>
183 <div class="flex justify-between mt-4 text-sm" style="color: #999"> 190 <div class="flex justify-between mt-4 text-sm" style="color: #999">
184 <div class="flex min-w-20 mr-3"> 191 <div class="flex min-w-20 mr-3">
@@ -23,6 +23,7 @@ @@ -23,6 +23,7 @@
23 import { WidgetDataType } from '../../hooks/useDataSource'; 23 import { WidgetDataType } from '../../hooks/useDataSource';
24 import { ExtraDataSource } from '../../types'; 24 import { ExtraDataSource } from '../../types';
25 import { OrderByEnum } from '/@/views/device/localtion/cpns/TimePeriodForm/config'; 25 import { OrderByEnum } from '/@/views/device/localtion/cpns/TimePeriodForm/config';
  26 + import { useApp } from '../../hooks/useApp';
26 27
27 type DeviceOption = Record<'label' | 'value' | 'organizationId', string>; 28 type DeviceOption = Record<'label' | 'value' | 'organizationId', string>;
28 29
@@ -38,6 +39,8 @@ @@ -38,6 +39,8 @@
38 39
39 const historyData = ref<{ ts: number; value: string; name: string }[]>([]); 40 const historyData = ref<{ ts: number; value: string; name: string }[]>([]);
40 41
  42 + const { getIsAppPage } = useApp();
  43 +
41 const { deviceAttrs, getDeviceKeys, getSearchParams, setChartOptions, getDeviceAttribute } = 44 const { deviceAttrs, getDeviceKeys, getSearchParams, setChartOptions, getDeviceAttribute } =
42 useHistoryData(); 45 useHistoryData();
43 46
@@ -294,7 +297,7 @@ @@ -294,7 +297,7 @@
294 :destroy-on-close="true" 297 :destroy-on-close="true"
295 :show-ok-btn="false" 298 :show-ok-btn="false"
296 cancel-text="关闭" 299 cancel-text="关闭"
297 - width="70%" 300 + :width="getIsAppPage ? '100%' : '75%'"
298 title="历史趋势" 301 title="历史趋势"
299 > 302 >
300 <section 303 <section
  1 +export const Platform = [
  2 + { key: 'iPhone 8', title: 'iPhone 8', width: 375, height: 667 },
  3 + { key: 'iPhone 8 Plus', title: 'iPhone 8 Plus', width: 415, height: 737 },
  4 + { key: 'iPhone X/XS', title: 'iPhone X/XS', width: 376, height: 813 },
  5 + { key: 'iPad 4', title: 'iPad 4', width: 709, height: 1025 },
  6 + { key: 'Galaxy S9', title: 'Galaxy S9', width: 361, height: 741 },
  7 + { key: 'Galaxy S10/S10+', title: 'Galaxy S10/S10+', width: 413, height: 870 },
  8 + { key: 'Pixel 2', title: 'Pixel 2', width: 413, height: 732 },
  9 + { key: 'custom', title: '自定义', width: '', height: '' },
  10 +];
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 - import { PageHeader } from 'ant-design-vue';  
3 - import { computed, unref } from 'vue'; 2 + import { PageHeader, RadioButton, RadioGroup, InputNumber, Button } from 'ant-design-vue';
  3 + import { computed, ref, unref, onMounted } from 'vue';
4 import { useRoute, useRouter } from 'vue-router'; 4 import { useRoute, useRouter } from 'vue-router';
5 import { decode } from '../..'; 5 import { decode } from '../..';
6 import { RollbackOutlined } from '@ant-design/icons-vue'; 6 import { RollbackOutlined } from '@ant-design/icons-vue';
  7 + import { Platform } from './config';
  8 + import { useApp } from '../../hooks/useApp';
  9 + import { getDataComponent } from '/@/api/dataBoard';
  10 + import { updateDataBoard } from '/@/api/dataBoard';
  11 + import { useMessage } from '/@/hooks/web/useMessage';
7 12
8 defineProps<{ widgetNumber: number }>(); 13 defineProps<{ widgetNumber: number }>();
  14 + const emits = defineEmits(['getPhoneSize']);
9 15
10 const ROUTE = useRoute(); 16 const ROUTE = useRoute();
11 const ROUTER = useRouter(); 17 const ROUTER = useRouter();
  18 +
  19 + const { isPhone } = useApp();
12 const getIsSharePage = computed(() => { 20 const getIsSharePage = computed(() => {
13 return ROUTE.matched.find((item) => item.path === '/share/:viewType/:id/:publicId'); 21 return ROUTE.matched.find((item) => item.path === '/share/:viewType/:id/:publicId');
14 }); 22 });
@@ -22,7 +30,85 @@ @@ -22,7 +30,85 @@
22 ROUTER.go(-1); 30 ROUTER.go(-1);
23 }; 31 };
24 32
25 - // const handleOpenCreatePanel = () => {}; 33 + const getSize = () => {
  34 + return {
  35 + width: '',
  36 + height: '',
  37 + };
  38 + };
  39 +
  40 + const phoneRadio = ref<string>('iPhone 8');
  41 + const phoneSize = ref<{ width?: number | string; height?: number | string }>(getSize());
  42 + const ifShowWidth = ref<boolean>(false);
  43 +
  44 + const phoneModel = ref<{
  45 + key: string;
  46 + width: string | number;
  47 + height: number | string;
  48 + title: string;
  49 + }>();
  50 + const getDataList = async () => {
  51 + const values = await getDataComponent(decode((ROUTE.params as { boardId: string }).boardId));
  52 + phoneModel.value = values?.data.phoneModel && JSON.parse(values?.data.phoneModel as string);
  53 + phoneRadio.value = unref(phoneModel)?.key as any;
  54 + ifShowWidth.value = unref(phoneRadio) === 'custom' ? true : false;
  55 + phoneSize.value = {
  56 + width: unref(phoneModel)?.width,
  57 + height: unref(phoneModel)?.height,
  58 + };
  59 +
  60 + emits('getPhoneSize', unref(phoneSize));
  61 + };
  62 +
  63 + const updateComponent = () => {
  64 + const { boardId, boardName, organizationId, platform } = ROUTE.params;
  65 +
  66 + const values = {
  67 + id: boardId,
  68 + boardName,
  69 + organizationId,
  70 + platform,
  71 + phoneModel: JSON.stringify({
  72 + key: unref(phoneRadio),
  73 + ...phoneSize.value,
  74 + }),
  75 + };
  76 + updateDataBoard(values as any);
  77 + };
  78 +
  79 + const handleChange = (e) => {
  80 + const { target } = e || {};
  81 + phoneSize.value = getSize();
  82 + if (target.value === 'custom') {
  83 + ifShowWidth.value = true;
  84 + return;
  85 + } else {
  86 + ifShowWidth.value = false;
  87 + const values = Platform.find((item) => item.key == target.value);
  88 + phoneSize.value = {
  89 + width: values?.width || '',
  90 + height: values?.height || '',
  91 + };
  92 + }
  93 + updateComponent();
  94 + emits('getPhoneSize', unref(phoneSize));
  95 + };
  96 +
  97 + const { createMessage } = useMessage();
  98 +
  99 + const handleCustom = () => {
  100 + const { width, height } = unref(phoneSize);
  101 + if (!width || !height) {
  102 + createMessage.warning('请填写尺寸大小');
  103 + return;
  104 + }
  105 + emits('getPhoneSize', unref(phoneSize));
  106 + updateComponent();
  107 + };
  108 +
  109 + onMounted(() => {
  110 + getDataList();
  111 + });
26 </script> 112 </script>
27 113
28 <template> 114 <template>
@@ -46,6 +132,18 @@ @@ -46,6 +132,18 @@
46 <span class="mr-3 text-sm text-gray-500">已创建组件:</span> 132 <span class="mr-3 text-sm text-gray-500">已创建组件:</span>
47 <span class="text-blue-500"> {{ widgetNumber }}个</span> 133 <span class="text-blue-500"> {{ widgetNumber }}个</span>
48 </div> 134 </div>
  135 + <div v-if="isPhone()" class="flex items-center my-1">
  136 + <RadioGroup v-model:value="phoneRadio" button-style="solid" @change="handleChange">
  137 + <RadioButton v-for="item in Platform" :value="item.key" :key="item.key">{{
  138 + item.title
  139 + }}</RadioButton>
  140 + </RadioGroup>
  141 + <div class="ml-1" v-if="ifShowWidth">
  142 + <InputNumber v-model:value="phoneSize.width" :min="300" placeholder="请输入宽度" />
  143 + <InputNumber v-model:value="phoneSize.height" :min="300" placeholder="请输入高度" />
  144 + <Button type="primary" @click="handleCustom" class="ml-1">确定</Button>
  145 + </div>
  146 + </div>
49 </PageHeader> 147 </PageHeader>
50 </template> 148 </template>
51 149
@@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
7 import { BasicForm } from '/@/components/Form'; 7 import { BasicForm } from '/@/components/Form';
8 import { BasicModal } from '/@/components/Modal'; 8 import { BasicModal } from '/@/components/Modal';
9 import { nextTick } from 'vue'; 9 import { nextTick } from 'vue';
  10 + import { useApp } from '../../hooks/useApp';
10 const emit = defineEmits(['register', 'getAlarmForm', 'getHistoryForm']); 11 const emit = defineEmits(['register', 'getAlarmForm', 'getHistoryForm']);
11 // const emit = defineEmits<{ 12 // const emit = defineEmits<{
12 // (event: 'getAlarmForm', data: WidgetDataType): void; 13 // (event: 'getAlarmForm', data: WidgetDataType): void;
@@ -15,6 +16,8 @@ @@ -15,6 +16,8 @@
15 // const fontId = ref(''); 16 // const fontId = ref('');
16 const [registerModal, { closeModal }] = useModalInner(async () => {}); 17 const [registerModal, { closeModal }] = useModalInner(async () => {});
17 18
  19 + const { getIsAppPage } = useApp();
  20 +
18 const [register, method] = useForm({ 21 const [register, method] = useForm({
19 schemas: formSchema(), 22 schemas: formSchema(),
20 baseColProps: useGridLayout(1) as unknown as ColEx, 23 baseColProps: useGridLayout(1) as unknown as ColEx,
@@ -53,7 +56,7 @@ @@ -53,7 +56,7 @@
53 :destroy-on-close="true" 56 :destroy-on-close="true"
54 :show-ok-btn="true" 57 :show-ok-btn="true"
55 cancel-text="关闭" 58 cancel-text="关闭"
56 - width="40%" 59 + :width="getIsAppPage ? '80%' : '40%'"
57 title="历史趋势" 60 title="历史趋势"
58 > 61 >
59 <section 62 <section
  1 +import { computed } from 'vue';
  2 +import { useRoute } from 'vue-router';
  3 +
  4 +export const useApp = () => {
  5 + const ROUTE = useRoute();
  6 + const getIsAppPage = computed(() => {
  7 + return ROUTE.matched.find((item) => item.path === '/appPage/:boardId/:userId');
  8 + });
  9 +
  10 + const isPhone = () => {
  11 + const values = location?.pathname.split('/') || [];
  12 + return values[values?.length - 2] === 'phone' ? true : false;
  13 + };
  14 +
  15 + return { getIsAppPage, isPhone };
  16 +};
1 import { unref } from 'vue'; 1 import { unref } from 'vue';
2 import { Layout } from 'vue3-grid-layout'; 2 import { Layout } from 'vue3-grid-layout';
3 -import { DEFAULT_MAX_COL, DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH } from '..'; 3 +import { DEFAULT_MAX_COL, DEFAULT_WIDGET_HEIGHT, DEFAULT_WIDGET_WIDTH, PHONE_SIZE } from '..';
  4 +import { useApp } from './useApp';
4 5
5 interface GapRecord { 6 interface GapRecord {
6 maxGap: number; 7 maxGap: number;
@@ -8,9 +9,14 @@ interface GapRecord { @@ -8,9 +9,14 @@ interface GapRecord {
8 endIndex: Nullable<number>; 9 endIndex: Nullable<number>;
9 } 10 }
10 11
  12 +const { isPhone } = useApp();
  13 +
11 export const useCalcNewWidgetPosition = ( 14 export const useCalcNewWidgetPosition = (
12 layoutInfo: Layout[], 15 layoutInfo: Layout[],
13 - randomLayout = { width: DEFAULT_WIDGET_WIDTH, height: DEFAULT_WIDGET_HEIGHT } 16 + randomLayout = {
  17 + width: isPhone() ? PHONE_SIZE.DEFAULT_WIDGET_WIDTH : DEFAULT_WIDGET_WIDTH,
  18 + height: isPhone() ? PHONE_SIZE.DEFAULT_WIDGET_HEIGHT : DEFAULT_WIDGET_HEIGHT,
  19 + }
14 ) => { 20 ) => {
15 let maxWidth = 0; 21 let maxWidth = 0;
16 let maxHeight = 0; 22 let maxHeight = 0;
@@ -30,6 +30,10 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => { @@ -30,6 +30,10 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => {
30 const getIsSharePage = computed(() => { 30 const getIsSharePage = computed(() => {
31 return ROUTE.matched.find((item) => item.path === '/share/:viewType/:id/:publicId'); 31 return ROUTE.matched.find((item) => item.path === '/share/:viewType/:id/:publicId');
32 }); 32 });
  33 + //小程序打开的页面
  34 + const getIsAppPage = computed(() => {
  35 + return ROUTE.matched.find((item) => item.path === '/appPage/:boardId/:userId');
  36 + });
33 37
34 const getBoardId = computed(() => { 38 const getBoardId = computed(() => {
35 return decode((ROUTE.params as { boardId: string }).boardId); 39 return decode((ROUTE.params as { boardId: string }).boardId);
@@ -131,8 +135,8 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => { @@ -131,8 +135,8 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => {
131 135
132 return { 136 return {
133 loading, 137 loading,
134 - draggable: !unref(getIsSharePage),  
135 - resizable: !unref(getIsSharePage), 138 + draggable: !unref(getIsSharePage) && !unref(getIsAppPage),
  139 + resizable: !unref(getIsSharePage) && !unref(getIsAppPage),
136 dataSource, 140 dataSource,
137 rawDataSource, 141 rawDataSource,
138 getDataSource, 142 getDataSource,
@@ -11,6 +11,12 @@ export const DEFAULT_WIDGET_HEIGHT = 6; @@ -11,6 +11,12 @@ export const DEFAULT_WIDGET_HEIGHT = 6;
11 export const DEFAULT_MIN_HEIGHT = 5; 11 export const DEFAULT_MIN_HEIGHT = 5;
12 export const DEFAULT_MIN_WIDTH = 3; 12 export const DEFAULT_MIN_WIDTH = 3;
13 export const DEFAULT_ITEM_MARIGN = 20; 13 export const DEFAULT_ITEM_MARIGN = 20;
  14 +export const PHONE_SIZE = {
  15 + DEFAULT_WIDGET_WIDTH: 12,
  16 + DEFAULT_WIDGET_HEIGHT: 4,
  17 + DEFAULT_MIN_HEIGHT: 4,
  18 + DEFAULT_MIN_WIDTH: 11,
  19 +};
14 20
15 import { ViewTypeEnum } from '/@/views/sys/share/config/config'; 21 import { ViewTypeEnum } from '/@/views/sys/share/config/config';
16 22
@@ -10,6 +10,7 @@ @@ -10,6 +10,7 @@
10 DEFAULT_MIN_WIDTH, 10 DEFAULT_MIN_WIDTH,
11 DEFAULT_ITEM_MARIGN, 11 DEFAULT_ITEM_MARIGN,
12 VisualComponentPermission, 12 VisualComponentPermission,
  13 + PHONE_SIZE,
13 } from './index'; 14 } from './index';
14 import { useDragGridLayout } from './hooks/useDragGridLayout'; 15 import { useDragGridLayout } from './hooks/useDragGridLayout';
15 import { WidgetHeader, WidgetWrapper } from './components/WidgetWrapper'; 16 import { WidgetHeader, WidgetWrapper } from './components/WidgetWrapper';
@@ -19,6 +20,7 @@ @@ -19,6 +20,7 @@
19 import { DataSourceBindPanel } from '../dataSourceBindPanel'; 20 import { DataSourceBindPanel } from '../dataSourceBindPanel';
20 import { PageHeader } from './components/PagerHeader'; 21 import { PageHeader } from './components/PagerHeader';
21 import { useShare } from './hooks/useShare'; 22 import { useShare } from './hooks/useShare';
  23 + import { useApp } from './hooks/useApp';
22 import { useRole } from '/@/hooks/business/useRole'; 24 import { useRole } from '/@/hooks/business/useRole';
23 import { Authority } from '/@/components/Authority'; 25 import { Authority } from '/@/components/Authority';
24 import { useModal } from '/@/components/Modal'; 26 import { useModal } from '/@/components/Modal';
@@ -33,6 +35,11 @@ @@ -33,6 +35,11 @@
33 import { useSocket } from '/@/views/visual/packages/hook/socket/useSocket'; 35 import { useSocket } from '/@/views/visual/packages/hook/socket/useSocket';
34 import { createAlarmContext } from './hooks/useAlarmTime'; 36 import { createAlarmContext } from './hooks/useAlarmTime';
35 import { createHistoryContext } from './hooks/useHistoryForm'; 37 import { createHistoryContext } from './hooks/useHistoryForm';
  38 + import { MoreOutlined, LeftOutlined } from '@ant-design/icons-vue';
  39 + import WIFISVG from '/@/assets/svg/wifi.svg';
  40 + import SIGNALSVG from '/@/assets/svg/signal.svg';
  41 + import BATTERYSVG from '/@/assets/svg/battery.svg';
  42 + import { useRoute } from 'vue-router';
36 43
37 const props = defineProps<{ 44 const props = defineProps<{
38 value?: Recordable; 45 value?: Recordable;
@@ -44,6 +51,8 @@ @@ -44,6 +51,8 @@
44 51
45 const containerRectRef = ref<DOMRect>({} as unknown as DOMRect); 52 const containerRectRef = ref<DOMRect>({} as unknown as DOMRect);
46 53
  54 + const ROUTE = useRoute();
  55 +
47 const { loading, draggable, resizable, dataSource, rawDataSource, setLayoutInfo, getDataSource } = 56 const { loading, draggable, resizable, dataSource, rawDataSource, setLayoutInfo, getDataSource } =
48 useDataSource(getProps); 57 useDataSource(getProps);
49 58
@@ -63,6 +72,9 @@ @@ -63,6 +72,9 @@
63 } 72 }
64 }); 73 });
65 const { getIsSharePage } = useShare(); 74 const { getIsSharePage } = useShare();
  75 +
  76 + // getIsAppPage 是否是小程序进入的页面 isPhone 是否是创建的手机端
  77 + const { getIsAppPage, isPhone } = useApp();
66 const { isCustomerUser } = useRole(); 78 const { isCustomerUser } = useRole();
67 const handleOpenCreatePanel = () => { 79 const handleOpenCreatePanel = () => {
68 openModal(true, { mode: DataActionModeEnum.CREATE } as ModalParamsType); 80 openModal(true, { mode: DataActionModeEnum.CREATE } as ModalParamsType);
@@ -157,6 +169,28 @@ @@ -157,6 +169,28 @@
157 }, 169 },
158 { immediate: true } 170 { immediate: true }
159 ); 171 );
  172 + watch(
  173 + getIsAppPage,
  174 + (value) => {
  175 + if (value) {
  176 + const root = document.querySelector('#app');
  177 + (root as HTMLDivElement).style.backgroundColor =
  178 + unref(getDarkMode) === ThemeEnum.LIGHT ? '#F5F5F5' : '#1b1b1b';
  179 + }
  180 + },
  181 + { immediate: true }
  182 + );
  183 + const getDataBoardName = computed(() => {
  184 + return decodeURIComponent((ROUTE.params as { boardName: string }).boardName || '');
  185 + });
  186 +
  187 + const phoneSize = ref({
  188 + width: '' || 375,
  189 + height: '' || 667,
  190 + });
  191 + const getPhoneSize = (values) => {
  192 + phoneSize.value = { ...values };
  193 + };
160 </script> 194 </script>
161 195
162 <template> 196 <template>
@@ -164,7 +198,11 @@ @@ -164,7 +198,11 @@
164 ref="containerRefEl" 198 ref="containerRefEl"
165 class="palette w-full h-full flex-col bg-neutral-100 flex dark:bg-dark-700 dark:text-light-50" 199 class="palette w-full h-full flex-col bg-neutral-100 flex dark:bg-dark-700 dark:text-light-50"
166 > 200 >
167 - <PageHeader :widget-number="dataSource.length"> 201 + <PageHeader
  202 + v-if="!getIsAppPage"
  203 + :widget-number="dataSource.length"
  204 + @getPhoneSize="getPhoneSize"
  205 + >
168 <Authority :value="VisualComponentPermission.CREATE"> 206 <Authority :value="VisualComponentPermission.CREATE">
169 <Button 207 <Button
170 v-if="!getIsSharePage && !isCustomerUser" 208 v-if="!getIsSharePage && !isCustomerUser"
@@ -177,55 +215,104 @@ @@ -177,55 +215,104 @@
177 </PageHeader> 215 </PageHeader>
178 216
179 <Spin :spinning="loading"> 217 <Spin :spinning="loading">
180 - <GridLayout  
181 - v-model:layout="dataSource"  
182 - :col-num="DEFAULT_MAX_COL"  
183 - :row-height="30"  
184 - :margin="[DEFAULT_ITEM_MARIGN, DEFAULT_ITEM_MARIGN]"  
185 - :is-draggable="draggable"  
186 - :is-resizable="resizable"  
187 - :vertical-compact="true"  
188 - :use-css-transforms="true"  
189 - style="width: 100%"  
190 - >  
191 - <GridItem  
192 - v-for="item in dataSource"  
193 - :key="item.i"  
194 - :static="item.static"  
195 - :x="item.x"  
196 - :y="item.y"  
197 - :w="item.w"  
198 - :h="item.h"  
199 - :i="item.i"  
200 - :min-h="DEFAULT_MIN_HEIGHT"  
201 - :min-w="DEFAULT_MIN_WIDTH"  
202 - :style="{ display: 'flex', flexWrap: 'wrap' }"  
203 - class="grid-item-layout"  
204 - @resized="resized"  
205 - @resize="resize"  
206 - @moved="moved"  
207 - @container-resized="containerResized"  
208 - drag-ignore-from=".no-drag" 218 + <div class="w-full h-full flex justify-center items-center mb-3">
  219 + <div
  220 + :style="
  221 + !getIsAppPage && isPhone()
  222 + ? {
  223 + width: phoneSize.width + 'px',
  224 + height: phoneSize.height + 'px',
  225 + border: '2px solid #e5e7eb',
  226 + }
  227 + : { width: '100%', height: '100%' }
  228 + "
  229 + style="border-radius: 1%"
209 > 230 >
210 - <WidgetWrapper>  
211 - <template #header>  
212 - <WidgetHeader  
213 - :raw-data-source="rawDataSource"  
214 - :source-info="item"  
215 - @update="handleUpdateWidget"  
216 - @open-trend="handleOpenTrend"  
217 - @open-alarm="handleOpenAlarm"  
218 - @ok="getDataSource"  
219 - />  
220 - </template>  
221 - <WidgetDistribute :source-info="item" />  
222 - </WidgetWrapper>  
223 - </GridItem>  
224 - </GridLayout>  
225 - <Empty  
226 - v-if="!dataSource.length"  
227 - class="fixed top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"  
228 - /> 231 + <!-- 手机端写的模拟样式 -->
  232 + <div
  233 + v-if="!getIsAppPage && isPhone()"
  234 + style="height: 60px; background: white"
  235 + class="px-1 py-1 relative"
  236 + >
  237 + <div class="flex justify-between">
  238 + <div>thingskit</div>
  239 + <div class="flex items-center">
  240 + <img :src="WIFISVG" alt="" class="w-3 h-3" />
  241 + <img :src="SIGNALSVG" alt="" class="w-3 h-3 mx-1" />
  242 + <img :src="BATTERYSVG" alt="" class="w-4 h-4 rotate-45" />
  243 + <h1 class="mb-0">18:13</h1>
  244 + </div>
  245 + </div>
  246 + <div class="flex items-center justify-between">
  247 + <LeftOutlined class="transform cursor-pointer text-lg" />
  248 + <h1 class="font-bold">{{ getDataBoardName }}</h1>
  249 + <MoreOutlined class="transform rotate-90 cursor-pointer text-lg" />
  250 + </div>
  251 + </div>
  252 +
  253 + <div
  254 + id="appLayoutId"
  255 + :style="
  256 + !getIsAppPage && isPhone() ? { height: `calc(${phoneSize.height}px - 67px)` } : {}
  257 + "
  258 + class="overflow-y-scroll"
  259 + >
  260 + <GridLayout
  261 + v-model:layout="dataSource"
  262 + :col-num="DEFAULT_MAX_COL"
  263 + :row-height="30"
  264 + :margin="[DEFAULT_ITEM_MARIGN, DEFAULT_ITEM_MARIGN]"
  265 + :is-draggable="draggable"
  266 + :is-resizable="resizable"
  267 + :vertical-compact="true"
  268 + :use-css-transforms="true"
  269 + style="width: 100%"
  270 + :style="{
  271 + '--is-app': isPhone() ? 'auto' : 'none',
  272 + }"
  273 + >
  274 + <GridItem
  275 + v-for="item in dataSource"
  276 + :key="item.i"
  277 + :static="item.static"
  278 + :x="item.x"
  279 + :y="item.y"
  280 + :w="item.w"
  281 + :h="item.h"
  282 + :i="item.i"
  283 + :min-h="isPhone() ? PHONE_SIZE.DEFAULT_MIN_HEIGHT : DEFAULT_MIN_HEIGHT"
  284 + :min-w="isPhone() ? PHONE_SIZE.DEFAULT_MIN_WIDTH : DEFAULT_MIN_WIDTH"
  285 + :style="{ display: 'flex', flexWrap: 'wrap' }"
  286 + class="grid-item-layout"
  287 + @resized="resized"
  288 + @resize="resize"
  289 + @moved="moved"
  290 + @container-resized="containerResized"
  291 + drag-ignore-from=".no-drag"
  292 + >
  293 + <WidgetWrapper>
  294 + <template #header>
  295 + <WidgetHeader
  296 + :raw-data-source="rawDataSource"
  297 + :source-info="item"
  298 + @update="handleUpdateWidget"
  299 + @open-trend="handleOpenTrend"
  300 + @open-alarm="handleOpenAlarm"
  301 + @ok="getDataSource"
  302 + />
  303 + </template>
  304 + <WidgetDistribute :source-info="item" />
  305 + </WidgetWrapper>
  306 + </GridItem>
  307 + </GridLayout>
  308 + </div>
  309 + <Empty
  310 + v-if="!dataSource.length"
  311 + :class="isPhone() ? 'absolute' : 'fixed'"
  312 + class="top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2"
  313 + />
  314 + </div>
  315 + </div>
229 </Spin> 316 </Spin>
230 317
231 <DataSourceBindPanel @register="register" :layout="dataSource" @ok="getDataSource" /> 318 <DataSourceBindPanel @register="register" :layout="dataSource" @ok="getDataSource" />
@@ -238,4 +325,9 @@ @@ -238,4 +325,9 @@
238 </section> 325 </section>
239 </template> 326 </template>
240 327
241 -<style lang="less" scoped></style> 328 +<style lang="less">
  329 + .vue-grid-item {
  330 + pointer-events: painted;
  331 + touch-action: var(--is-app) !important;
  332 + }
  333 +</style>
@@ -91,6 +91,7 @@ export interface ComponentLayoutType { @@ -91,6 +91,7 @@ export interface ComponentLayoutType {
91 export interface ApiDataBoardDataType { 91 export interface ApiDataBoardDataType {
92 componentData: ComponentDataType[]; 92 componentData: ComponentDataType[];
93 componentLayout: ComponentLayoutType[]; 93 componentLayout: ComponentLayoutType[];
  94 + phoneModel?: string;
94 } 95 }
95 96
96 export interface ApiDataBoardInfoType { 97 export interface ApiDataBoardInfoType {