Commit b1fea99bc5155d6014b6d3550f9f4b42a45f51ba
Committed by
xp.Huang
1 parent
b65d9399
feat:看板添加手机端逻辑
Showing
27 changed files
with
462 additions
and
66 deletions
... | ... | @@ -53,6 +53,7 @@ export interface DataBoardRecord { |
53 | 53 | publicId: string; |
54 | 54 | organizationId?: string; |
55 | 55 | accessCredentials?: string; |
56 | + platform?: string; | |
56 | 57 | } |
57 | 58 | |
58 | 59 | export interface DataBoardList { |
... | ... | @@ -137,7 +138,12 @@ export interface MasterDeviceList { |
137 | 138 | } |
138 | 139 | |
139 | 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 | 149 | export interface DeviceAttributeParams { | ... | ... |
... | ... | @@ -21,6 +21,7 @@ enum Api { |
21 | 21 | SendLoginSmsCode = '/noauth/send_login_code/', |
22 | 22 | ResetCode = '/noauth/reset_code/', |
23 | 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 | 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 | +}; | ... | ... |
src/assets/svg/battery.svg
0 → 100644
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> | |
\ No newline at end of file | ... | ... |
src/assets/svg/signal.svg
0 → 100644
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> | |
\ No newline at end of file | ... | ... |
src/assets/svg/wifi.svg
0 → 100644
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> | |
\ No newline at end of file | ... | ... |
... | ... | @@ -2,7 +2,7 @@ import { RouteLocationNormalizedLoaded } from 'vue-router'; |
2 | 2 | |
3 | 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 | 6 | menuMap.set('/rule/chain/:id', '/rule/chain'); |
7 | 7 | |
8 | 8 | export const useMenuActiveFix = (route: RouteLocationNormalizedLoaded) => { | ... | ... |
... | ... | @@ -11,7 +11,8 @@ import { getAuthCache } from '/@/utils/auth'; |
11 | 11 | const LOGIN_PATH = PageEnum.BASE_LOGIN; |
12 | 12 | const ROOT_PATH = RootRoute.path; |
13 | 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 | 16 | // const userInfo1 = getAuthCache(USER_INFO_KEY); |
16 | 17 | // const userInfo = ref(userInfo1); |
17 | 18 | ... | ... |
src/router/routes/appPage.ts
0 → 100644
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 | 5 | import { t } from '/@/hooks/web/useI18n'; |
6 | 6 | import { LAYOUT } from '../constant'; |
7 | 7 | import { PUBLIC_PAGE_ROUTER } from './public'; |
8 | +import { APP_PAGE_ROUTER } from './appPage'; | |
8 | 9 | |
9 | 10 | const modules = import.meta.globEager('./modules/**/*.ts'); |
10 | 11 | const routeModuleList: AppRouteModule[] = []; |
... | ... | @@ -87,4 +88,5 @@ export const basicRoutes = [ |
87 | 88 | REDIRECT_ROUTE, |
88 | 89 | PAGE_NOT_FOUND_ROUTE, |
89 | 90 | PUBLIC_PAGE_ROUTER, |
91 | + APP_PAGE_ROUTER, | |
90 | 92 | ]; | ... | ... |
... | ... | @@ -105,7 +105,7 @@ export const getFormSchemas = (hasAlarmNotify: Ref<boolean>): FormSchema[] => { |
105 | 105 | field: FormFieldsEnum.ALARM_PROFILED, |
106 | 106 | label: '', |
107 | 107 | component: 'AlarmProfileSelect', |
108 | - rules: [{ required: true, message: `请选择${FormFieldsEnum.ALARM_PROFILED}` }], | |
108 | + // rules: [{ required: true, message: `请选择${FormFieldsEnum.ALARM_PROFILED}` }], | |
109 | 109 | ifShow: ({ model }) => model[FormFieldsEnum.OUT_TARGET] === ExecutionActionEnum.MSG_NOTIFY, |
110 | 110 | componentProps: () => { |
111 | 111 | return { | ... | ... |
src/views/sys/appPage/config/config.ts
0 → 100644
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 | +}; | ... | ... |
src/views/sys/appPage/hook/index.ts
0 → 100644
src/views/sys/appPage/index.vue
0 → 100644
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 | 1 | <script lang="ts" setup> |
2 | 2 | import { BasicModal, useModalInner } from '/@/components/Modal'; |
3 | 3 | import { BasicForm, useForm } from '/@/components/Form'; |
4 | - import { formSchema } from '../config/panelDetail'; | |
4 | + import { formSchema, PlatformType } from '../config/panelDetail'; | |
5 | 5 | import { addDataBoard, updateDataBoard } from '/@/api/dataBoard'; |
6 | 6 | import { AddDataBoardParams } from '/@/api/dataBoard/model'; |
7 | 7 | import { useMessage } from '/@/hooks/web/useMessage'; |
... | ... | @@ -34,6 +34,9 @@ |
34 | 34 | if (!Reflect.get(value, 'accessCredentials')) { |
35 | 35 | Reflect.deleteProperty(value, 'accessCredentials'); |
36 | 36 | } |
37 | + if (value.platform === PlatformType.PC) { | |
38 | + Reflect.deleteProperty(value, 'phoneModel'); | |
39 | + } | |
37 | 40 | return value as any; |
38 | 41 | }; |
39 | 42 | ... | ... |
... | ... | @@ -4,7 +4,13 @@ export enum ViewType { |
4 | 4 | PRIVATE_VIEW = 'PRIVATE_VIEW', |
5 | 5 | PUBLIC_VIEW = 'PUBLIC_VIEW', |
6 | 6 | } |
7 | +import { Platform } from '../../palette/components/PagerHeader/config'; | |
8 | + | |
7 | 9 | useComponentRegister('OrgTreeSelect', OrgTreeSelect); |
10 | +export enum PlatformType { | |
11 | + PHONE = 'phone', | |
12 | + PC = 'pc', | |
13 | +} | |
8 | 14 | |
9 | 15 | export const formSchema: FormSchema[] = [ |
10 | 16 | { |
... | ... | @@ -24,6 +30,32 @@ export const formSchema: FormSchema[] = [ |
24 | 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 | 59 | field: 'remark', |
28 | 60 | label: '备注', |
29 | 61 | component: 'InputTextArea', | ... | ... |
... | ... | @@ -135,12 +135,13 @@ |
135 | 135 | |
136 | 136 | const handleViewBoard = (record: DataBoardRecord) => { |
137 | 137 | const hasDetailPermission = hasPermission(VisualBoardPermission.DETAIL); |
138 | + const { platform = 'pc' } = record || {}; | |
138 | 139 | if (hasDetailPermission) { |
139 | 140 | const boardId = encode(record.id); |
140 | 141 | const boardName = encode(record.name); |
141 | 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 | 145 | } else createMessage.warning('没有权限'); |
145 | 146 | }; |
146 | 147 | </script> |
... | ... | @@ -170,7 +171,7 @@ |
170 | 171 | </Dropdown> |
171 | 172 | </template> |
172 | 173 | <section @click="handleViewBoard(item)"> |
173 | - <div class="flex data-card__info"> | |
174 | + <div class="flex data-card__info relative"> | |
174 | 175 | <div> |
175 | 176 | <div>组件数量</div> |
176 | 177 | <Statistic class="text-2xl" :value="item.componentNum"> |
... | ... | @@ -179,6 +180,12 @@ |
179 | 180 | </template> |
180 | 181 | </Statistic> |
181 | 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 | 189 | </div> |
183 | 190 | <div class="flex justify-between mt-4 text-sm" style="color: #999"> |
184 | 191 | <div class="flex min-w-20 mr-3"> | ... | ... |
... | ... | @@ -23,6 +23,7 @@ |
23 | 23 | import { WidgetDataType } from '../../hooks/useDataSource'; |
24 | 24 | import { ExtraDataSource } from '../../types'; |
25 | 25 | import { OrderByEnum } from '/@/views/device/localtion/cpns/TimePeriodForm/config'; |
26 | + import { useApp } from '../../hooks/useApp'; | |
26 | 27 | |
27 | 28 | type DeviceOption = Record<'label' | 'value' | 'organizationId', string>; |
28 | 29 | |
... | ... | @@ -38,6 +39,8 @@ |
38 | 39 | |
39 | 40 | const historyData = ref<{ ts: number; value: string; name: string }[]>([]); |
40 | 41 | |
42 | + const { getIsAppPage } = useApp(); | |
43 | + | |
41 | 44 | const { deviceAttrs, getDeviceKeys, getSearchParams, setChartOptions, getDeviceAttribute } = |
42 | 45 | useHistoryData(); |
43 | 46 | |
... | ... | @@ -294,7 +297,7 @@ |
294 | 297 | :destroy-on-close="true" |
295 | 298 | :show-ok-btn="false" |
296 | 299 | cancel-text="关闭" |
297 | - width="70%" | |
300 | + :width="getIsAppPage ? '100%' : '75%'" | |
298 | 301 | title="历史趋势" |
299 | 302 | > |
300 | 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 | 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 | 4 | import { useRoute, useRouter } from 'vue-router'; |
5 | 5 | import { decode } from '../..'; |
6 | 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 | 13 | defineProps<{ widgetNumber: number }>(); |
14 | + const emits = defineEmits(['getPhoneSize']); | |
9 | 15 | |
10 | 16 | const ROUTE = useRoute(); |
11 | 17 | const ROUTER = useRouter(); |
18 | + | |
19 | + const { isPhone } = useApp(); | |
12 | 20 | const getIsSharePage = computed(() => { |
13 | 21 | return ROUTE.matched.find((item) => item.path === '/share/:viewType/:id/:publicId'); |
14 | 22 | }); |
... | ... | @@ -22,7 +30,85 @@ |
22 | 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 | 112 | </script> |
27 | 113 | |
28 | 114 | <template> |
... | ... | @@ -46,6 +132,18 @@ |
46 | 132 | <span class="mr-3 text-sm text-gray-500">已创建组件:</span> |
47 | 133 | <span class="text-blue-500"> {{ widgetNumber }}个</span> |
48 | 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 | 147 | </PageHeader> |
50 | 148 | </template> |
51 | 149 | ... | ... |
... | ... | @@ -7,6 +7,7 @@ |
7 | 7 | import { BasicForm } from '/@/components/Form'; |
8 | 8 | import { BasicModal } from '/@/components/Modal'; |
9 | 9 | import { nextTick } from 'vue'; |
10 | + import { useApp } from '../../hooks/useApp'; | |
10 | 11 | const emit = defineEmits(['register', 'getAlarmForm', 'getHistoryForm']); |
11 | 12 | // const emit = defineEmits<{ |
12 | 13 | // (event: 'getAlarmForm', data: WidgetDataType): void; |
... | ... | @@ -15,6 +16,8 @@ |
15 | 16 | // const fontId = ref(''); |
16 | 17 | const [registerModal, { closeModal }] = useModalInner(async () => {}); |
17 | 18 | |
19 | + const { getIsAppPage } = useApp(); | |
20 | + | |
18 | 21 | const [register, method] = useForm({ |
19 | 22 | schemas: formSchema(), |
20 | 23 | baseColProps: useGridLayout(1) as unknown as ColEx, |
... | ... | @@ -53,7 +56,7 @@ |
53 | 56 | :destroy-on-close="true" |
54 | 57 | :show-ok-btn="true" |
55 | 58 | cancel-text="关闭" |
56 | - width="40%" | |
59 | + :width="getIsAppPage ? '80%' : '40%'" | |
57 | 60 | title="历史趋势" |
58 | 61 | > |
59 | 62 | <section | ... | ... |
src/views/visual/palette/hooks/useApp.ts
0 → 100644
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 | 1 | import { unref } from 'vue'; |
2 | 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 | 6 | interface GapRecord { |
6 | 7 | maxGap: number; |
... | ... | @@ -8,9 +9,14 @@ interface GapRecord { |
8 | 9 | endIndex: Nullable<number>; |
9 | 10 | } |
10 | 11 | |
12 | +const { isPhone } = useApp(); | |
13 | + | |
11 | 14 | export const useCalcNewWidgetPosition = ( |
12 | 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 | 21 | let maxWidth = 0; |
16 | 22 | let maxHeight = 0; | ... | ... |
... | ... | @@ -30,6 +30,10 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => { |
30 | 30 | const getIsSharePage = computed(() => { |
31 | 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 | 38 | const getBoardId = computed(() => { |
35 | 39 | return decode((ROUTE.params as { boardId: string }).boardId); |
... | ... | @@ -131,8 +135,8 @@ export const useDataSource = (propsRef: ComputedRef<Recordable>) => { |
131 | 135 | |
132 | 136 | return { |
133 | 137 | loading, |
134 | - draggable: !unref(getIsSharePage), | |
135 | - resizable: !unref(getIsSharePage), | |
138 | + draggable: !unref(getIsSharePage) && !unref(getIsAppPage), | |
139 | + resizable: !unref(getIsSharePage) && !unref(getIsAppPage), | |
136 | 140 | dataSource, |
137 | 141 | rawDataSource, |
138 | 142 | getDataSource, | ... | ... |
... | ... | @@ -11,6 +11,12 @@ export const DEFAULT_WIDGET_HEIGHT = 6; |
11 | 11 | export const DEFAULT_MIN_HEIGHT = 5; |
12 | 12 | export const DEFAULT_MIN_WIDTH = 3; |
13 | 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 | 21 | import { ViewTypeEnum } from '/@/views/sys/share/config/config'; |
16 | 22 | ... | ... |
... | ... | @@ -10,6 +10,7 @@ |
10 | 10 | DEFAULT_MIN_WIDTH, |
11 | 11 | DEFAULT_ITEM_MARIGN, |
12 | 12 | VisualComponentPermission, |
13 | + PHONE_SIZE, | |
13 | 14 | } from './index'; |
14 | 15 | import { useDragGridLayout } from './hooks/useDragGridLayout'; |
15 | 16 | import { WidgetHeader, WidgetWrapper } from './components/WidgetWrapper'; |
... | ... | @@ -19,6 +20,7 @@ |
19 | 20 | import { DataSourceBindPanel } from '../dataSourceBindPanel'; |
20 | 21 | import { PageHeader } from './components/PagerHeader'; |
21 | 22 | import { useShare } from './hooks/useShare'; |
23 | + import { useApp } from './hooks/useApp'; | |
22 | 24 | import { useRole } from '/@/hooks/business/useRole'; |
23 | 25 | import { Authority } from '/@/components/Authority'; |
24 | 26 | import { useModal } from '/@/components/Modal'; |
... | ... | @@ -33,6 +35,11 @@ |
33 | 35 | import { useSocket } from '/@/views/visual/packages/hook/socket/useSocket'; |
34 | 36 | import { createAlarmContext } from './hooks/useAlarmTime'; |
35 | 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 | 44 | const props = defineProps<{ |
38 | 45 | value?: Recordable; |
... | ... | @@ -44,6 +51,8 @@ |
44 | 51 | |
45 | 52 | const containerRectRef = ref<DOMRect>({} as unknown as DOMRect); |
46 | 53 | |
54 | + const ROUTE = useRoute(); | |
55 | + | |
47 | 56 | const { loading, draggable, resizable, dataSource, rawDataSource, setLayoutInfo, getDataSource } = |
48 | 57 | useDataSource(getProps); |
49 | 58 | |
... | ... | @@ -63,6 +72,9 @@ |
63 | 72 | } |
64 | 73 | }); |
65 | 74 | const { getIsSharePage } = useShare(); |
75 | + | |
76 | + // getIsAppPage 是否是小程序进入的页面 isPhone 是否是创建的手机端 | |
77 | + const { getIsAppPage, isPhone } = useApp(); | |
66 | 78 | const { isCustomerUser } = useRole(); |
67 | 79 | const handleOpenCreatePanel = () => { |
68 | 80 | openModal(true, { mode: DataActionModeEnum.CREATE } as ModalParamsType); |
... | ... | @@ -157,6 +169,28 @@ |
157 | 169 | }, |
158 | 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 | 194 | </script> |
161 | 195 | |
162 | 196 | <template> |
... | ... | @@ -164,7 +198,11 @@ |
164 | 198 | ref="containerRefEl" |
165 | 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 | 206 | <Authority :value="VisualComponentPermission.CREATE"> |
169 | 207 | <Button |
170 | 208 | v-if="!getIsSharePage && !isCustomerUser" |
... | ... | @@ -177,55 +215,104 @@ |
177 | 215 | </PageHeader> |
178 | 216 | |
179 | 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 | 316 | </Spin> |
230 | 317 | |
231 | 318 | <DataSourceBindPanel @register="register" :layout="dataSource" @ok="getDataSource" /> |
... | ... | @@ -238,4 +325,9 @@ |
238 | 325 | </section> |
239 | 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> | ... | ... |