Commit d89a9f019ec82f8edcef07193ab8d7c3303f6f5d
Merge branch 'perf/visual-app' into 'main_dev'
feat:看板添加手机端逻辑 See merge request yunteng/thingskit-front!1122
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> | ... | ... |