diff --git a/package-lock.json b/package-lock.json index 4ed28de..d155106 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@better-scroll/scroll-bar": "^2.5.1", "axios": "^1.6.7", "lodash-es": "^4.17.21", + "pinia": "^2.2.0", "swiper": "^11.1.1", "vue": "^3.4.15" }, @@ -1931,6 +1932,11 @@ "@vue/shared": "3.4.19" } }, + "node_modules/@vue/devtools-api": { + "version": "6.6.3", + "resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.6.3.tgz", + "integrity": "sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==" + }, "node_modules/@vue/eslint-config-prettier": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/@vue/eslint-config-prettier/-/eslint-config-prettier-8.0.0.tgz", @@ -6579,12 +6585,62 @@ "node": ">=0.10.0" } }, + "node_modules/pinia": { + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/pinia/-/pinia-2.2.0.tgz", + "integrity": "sha512-iPrIh26GMqfpUlMOGyxuDowGmYousTecbTHFwT0xZ1zJvh23oQ+Cj99ZoPQA1TnUPhU6AuRPv6/drkTCJ0VHQA==", + "dependencies": { + "@vue/devtools-api": "^6.6.3", + "vue-demi": "^0.14.8" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.3.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/pinia-logger": { "version": "1.3.12", "resolved": "https://registry.npmjs.org/pinia-logger/-/pinia-logger-1.3.12.tgz", "integrity": "sha512-0qY41Bh6iYN7mncwOGCaiS02OGujf9NK1kLVzNTRLw/L88xbBOCA6r4bO1PD3KRTiPtXc5u3nnnqKR+kTIIdsA==", "dev": true }, + "node_modules/pinia/node_modules/vue-demi": { + "version": "0.14.10", + "resolved": "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz", + "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", diff --git a/package.json b/package.json index 0baab33..ea414eb 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@better-scroll/scroll-bar": "^2.5.1", "axios": "^1.6.7", "lodash-es": "^4.17.21", + "pinia": "^2.2.0", "swiper": "^11.1.1", "vue": "^3.4.15" }, diff --git a/public/static/offline/JSON/getBackTime.json b/public/static/offline/JSON/getBackTime.json new file mode 100644 index 0000000..5a77f33 --- /dev/null +++ b/public/static/offline/JSON/getBackTime.json @@ -0,0 +1 @@ +{"code":200,"msg":"操作成功","data":[60,0]} diff --git a/public/static/offline/JSON/getCustomerQr.json b/public/static/offline/JSON/getCustomerQr.json new file mode 100644 index 0000000..ae3caeb --- /dev/null +++ b/public/static/offline/JSON/getCustomerQr.json @@ -0,0 +1,16 @@ +{ + "code": 200, + "msg": "操作成功", + "data": [ + { + "id": 1476, + "entryCode": "ogMDNkoxrQrFIXWP06T6y", + "title": "顾客心声二维码", + "content": { + "qrUrl": [ + "/iotFile/project-bg9aktmmvxxfvi6vya0dua/20240715/ZR8QBUPdCyn2qZIszWaT2.jpg" + ] + } + } + ] +} diff --git a/public/static/offline/JSON/getDevCoordinateByIP.json b/public/static/offline/JSON/getDevCoordinateByIP.json index 2ae12ac..ca4a715 100644 --- a/public/static/offline/JSON/getDevCoordinateByIP.json +++ b/public/static/offline/JSON/getDevCoordinateByIP.json @@ -17,7 +17,7 @@ "mac": "30B49EC47D5E", "location": "51", "angle": "0", - "projectCode": "project-o99mwit8jby-qb_xrffk2a", + "projectCode": "project-200", "regionCode": "", "lensCoordinate": "", "orientationCoordinate": "", diff --git a/src/App.vue b/src/App.vue index c652628..a984dc9 100644 --- a/src/App.vue +++ b/src/App.vue @@ -3,12 +3,51 @@ + + + diff --git a/src/assets/images/a.svg b/src/assets/images/a.svg new file mode 100644 index 0000000..86a89d3 --- /dev/null +++ b/src/assets/images/a.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/q.svg b/src/assets/images/q.svg new file mode 100644 index 0000000..bd236fd --- /dev/null +++ b/src/assets/images/q.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/base/AutoBackNotification/AutoBackNotification.vue b/src/base/AutoBackNotification/AutoBackNotification.vue new file mode 100644 index 0000000..35bb398 --- /dev/null +++ b/src/base/AutoBackNotification/AutoBackNotification.vue @@ -0,0 +1,53 @@ + + + + {{ title }} + + + {{ delay }}s + + + + + + + + + + diff --git a/src/components/List/List.vue b/src/components/List/List.vue index 708e43e..f769091 100644 --- a/src/components/List/List.vue +++ b/src/components/List/List.vue @@ -1,18 +1,18 @@ - + - + @@ -38,7 +38,7 @@ import 'swiper/css' const modules = [Autoplay] type Props = { - customerList: Customer[] + customerList: Customer['proposalList'] } const props = defineProps() diff --git a/src/components/ListItem/ListItem.vue b/src/components/ListItem/ListItem.vue index 50c41ee..3e0207a 100644 --- a/src/components/ListItem/ListItem.vue +++ b/src/components/ListItem/ListItem.vue @@ -1,19 +1,26 @@ - + - {{ customer.addTime }} - - - Q:{{ customer.suggestionContent }} - + + + {{ formatTime(customer.createTime) }} + + + {{ customer.content }} - {{ customer.updateTime }} - - - A: - + + + {{ formatTime(customer.replyTime) }} + + + + + + 总经理:{{ customer.managerSignature }} + + @@ -24,25 +31,20 @@ import ScrollView from '@/base/ScrollView/ScrollView.vue' type Props = { - customer: Customer + customer: ProposalList } withDefaults(defineProps(), { - customer: () => ({}) as Customer + customer: () => ({}) as ProposalList }) - - + diff --git a/src/composables/useHandleScreen.ts b/src/composables/useHandleScreen.ts index 47c000f..40c65f5 100644 --- a/src/composables/useHandleScreen.ts +++ b/src/composables/useHandleScreen.ts @@ -1,44 +1,23 @@ -import { ref, computed, watch, onUnmounted } from 'vue' +import { ref, watch, onUnmounted } from 'vue' import { storeToRefs } from 'pinia' -import * as Sentry from '@sentry/vue' import { useRootStore } from '@/stores/root' -import { useRouter } from 'vue-router' -import { getStatistics } from '@/http/api/statistics' export const useHandleScreen = (callback: () => void) => { const MIN_TIME = 0 const MAX_TIME = 5 const CHECK_TIME = 1000 const DELAY_CHECK_TIME = 400 + const title = '即将进入屏幕保护' - const router = useRouter() const store = useRootStore() - const { nativeMethods, mapStatus, backTime, device } = storeToRefs(store) + const { nativeMethods, backTime } = storeToRefs(store) - const toIndexTime = ref(backTime.value[0]) //回首页的时间 const toWallpaperTime = ref(backTime.value[1]) //回屏保的时间 const isWallpaper = ref(false) //回首页是否已经跑完 const showCountDownDialog = ref(false) - const title = computed(() => (isWallpaper.value ? '即将进入屏幕保护' : '即将进入首页')) - const delayTime = computed(() => (isWallpaper.value ? toWallpaperTime.value : toIndexTime.value)) - - let toIndexInterval: any let toWallpaperInterval: any let delayCheckRoutePathTimer: any - function sleepToIndex() { - isWallpaper.value = false - return new Promise(resolve => { - toIndexInterval = setInterval(() => { - toIndexTime.value-- - if (toIndexTime.value <= MIN_TIME) { - clearInterval(toIndexInterval) - toIndexTime.value = backTime.value[0] - resolve() - } - }, CHECK_TIME) - }) - } function sleepToWallpaper() { isWallpaper.value = true return new Promise(resolve => { @@ -55,7 +34,6 @@ export const useHandleScreen = (callback: () => void) => { } function clearTimers() { - clearInterval(toIndexInterval) clearInterval(toWallpaperInterval) clearTimeout(delayCheckRoutePathTimer) } @@ -66,30 +44,14 @@ export const useHandleScreen = (callback: () => void) => { } function checkHandleScreen() { - getStatistics({ - deviceCode: device.value.machineCode, - projectCode: device.value.projectCode, - tag: 'device' - }) - toIndexTime.value = backTime.value[0] toWallpaperTime.value = backTime.value[1] clearTimers() delayCheckRoutePathTimer = setTimeout(async () => { try { - if (router.currentRoute.value.fullPath !== '/') { - await sleepToIndex() - callback() - } - //没有屏保 if (backTime.value[1] < 0) { - //在根路由页面上执行全局弹框操作(比如全局搜索, 全局详情等)重新触发返回首页弹框 - if (router.currentRoute.value.fullPath === '/') { - await sleepToIndex() - callback() - } return } await sleepToWallpaper() @@ -97,36 +59,18 @@ export const useHandleScreen = (callback: () => void) => { nativeMethods.value?.goScreenSave() } catch (error) { clearTimers() - Sentry.captureException('checkHandleScreen:' + error) } }, DELAY_CHECK_TIME) } //监听时间 大于等于0且小于等于5时显示弹框 - const stopHandler = watch([toIndexTime, toWallpaperTime], ([indexTime, wallpaperTime]) => { - showCountDownDialog.value = (indexTime >= MIN_TIME && indexTime <= MAX_TIME) || (wallpaperTime >= MIN_TIME && wallpaperTime <= MAX_TIME) + const stopHandler = watch(toWallpaperTime, wallpaperTime => { + showCountDownDialog.value = wallpaperTime >= MIN_TIME && wallpaperTime <= MAX_TIME }) - const stopMapStatusHandler = watch( - mapStatus, - newVal => { - if (newVal) { - if (backTime.value[1] < 0) { - return - } - checkHandleScreen() - } - }, - { - once: true - } - ) - onUnmounted(() => { clearTimers() stopHandler() - stopMapStatusHandler() - toIndexInterval = null toWallpaperInterval = null delayCheckRoutePathTimer = null }) @@ -135,6 +79,6 @@ export const useHandleScreen = (callback: () => void) => { checkHandleScreen, showCountDownDialog, title, - delayTime + toWallpaperTime } } diff --git a/src/composables/useInitBaseData.ts b/src/composables/useInitBaseData.ts new file mode 100644 index 0000000..c2f140c --- /dev/null +++ b/src/composables/useInitBaseData.ts @@ -0,0 +1,19 @@ +import { useRootStore } from '@/stores/root' +import { getConfig, getDevice, getWeather, getBackTime } from '@/http/api' + +export const useInitBaseData = async () => { + const store = useRootStore() + try { + const _config = await getConfig() + store.SET_CONFIG(_config.data.map(item => item.content)[0]) + + const [_deviceInfo, _weather, _backTime] = await Promise.all([getDevice(), getWeather(), getBackTime()]) + + store.SET_DEVICE(_deviceInfo.data) + store.SET_WEATHER(_weather.data) + store.SET_BACK_TIME([_backTime.data[0], _backTime.data[1] ? _backTime.data[1] : -1]) + } catch (error) { + console.log('🚀 ~ useInitBaseData ~ error:', error) + alert('数据异常,软件启动失败') + } +} diff --git a/src/http/api.ts b/src/http/api.ts index 47c60b6..eccd900 100644 --- a/src/http/api.ts +++ b/src/http/api.ts @@ -1,16 +1,22 @@ -import { request } from '@/http/http' +import { getPrefixUrl, request } from '@/http/http' import { PREFIX } from '@/enums' //获取配置项 -export const getConfig = () => request({ url: `${PREFIX.STATIC_URL}/JSON/getConfig.json` }) +export const getConfig = () => request[]>({ url: `${PREFIX.STATIC_URL}/JSON/getConfig.json` }) -//获取设备 +//获取当前所处楼层 export const getDevice = () => request({ url: `${PREFIX.STATIC_URL}/JSON/getDevCoordinateByIP.json` }) //获取天气 export const getWeather = () => request({ url: `${PREFIX.STATIC_URL}/JSON/getWeather.json` }) +// 指定时间返回 +export const getBackTime = () => request<[number, number]>({ url: `${PREFIX.STATIC_URL}/JSON/getBackTime.json` }) + +//获取二维码 +export const getCustomerQr = () => request[]>({ url: `${PREFIX.STATIC_URL}/JSON/getCustomerQr.json` }) + //获取心声列表 -export const getCustomerList = (url: string, params: { pageIndex: number; pageSize: number; mallCode: string }) => { - return request<{ allPage: number; allCount: number; list: Customer[] }>({ url: `${url}/Api/Suggestion/Page`, params }) +export const getCustomerList = (projectCode: string) => { + return request({ url: `${getPrefixUrl().interfaceUrl}/data/v1/web/webProposalList/${projectCode}` }) } diff --git a/src/http/http.ts b/src/http/http.ts index d8279d5..4b0e026 100644 --- a/src/http/http.ts +++ b/src/http/http.ts @@ -2,6 +2,7 @@ import axios from 'axios' import type { AxiosResponse, AxiosInstance, InternalAxiosRequestConfig } from 'axios' import { addPrefixByRecursive } from '@/utils/utils' import type { RequestConfig, RequestInterceptors, CreateRequestConfig } from './types' +import { useRootStore } from '@/stores/root' export default class Request { // axios 实例 @@ -106,6 +107,11 @@ export type Response = { code: number } +export function getPrefixUrl() { + const store = useRootStore() + return store.config +} + export const request = (config: RequestConfig>) => _request.request(config) export const cancelRequest = (url: string | string[]) => _request.cancelRequest(url) export const cancelAllRequest = () => _request.cancelAllRequest() diff --git a/src/main.ts b/src/main.ts index eae564f..042508d 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,9 +1,14 @@ import { createApp } from 'vue' import App from './App.vue' +import { setupPinia } from './stores' +import { useInitBaseData } from './composables/useInitBaseData' + import '@/assets/scss/index.scss' async function bootstrap() { const app = createApp(App) + setupPinia(app) + await useInitBaseData() app.mount('#app') } diff --git a/src/stores/index.ts b/src/stores/index.ts new file mode 100644 index 0000000..d84ac24 --- /dev/null +++ b/src/stores/index.ts @@ -0,0 +1,7 @@ +import { createPinia } from 'pinia' +import type { App } from 'vue' + +export function setupPinia(app: App) { + const pinia = createPinia() + app.use(pinia) +} diff --git a/src/stores/root/actions.ts b/src/stores/root/actions.ts new file mode 100644 index 0000000..586675e --- /dev/null +++ b/src/stores/root/actions.ts @@ -0,0 +1,26 @@ +import type { State } from './state' +import type { CreateActions, Root } from '../types' + +export interface Actions { + SET_BACK_TIME(times: [number, number]): void + SET_WEATHER(weather: Weather): void + SET_DEVICE(device: Device): void + SET_CONFIG(config: Config): void +} + +export type GenActions = CreateActions + +export const actions: GenActions = { + SET_BACK_TIME(times) { + this.backTime = times + }, + SET_WEATHER(weather) { + this.weather = weather + }, + SET_CONFIG(config) { + this.config = config + }, + SET_DEVICE(device) { + this.device = device + } +} diff --git a/src/stores/root/getters.ts b/src/stores/root/getters.ts new file mode 100644 index 0000000..cf9d024 --- /dev/null +++ b/src/stores/root/getters.ts @@ -0,0 +1,18 @@ +import { DEVICE } from '@/enums' +import type { State } from './state' +import type { CreateGetters } from '../types' + +export type Getters = { + nativeMethods(): NativeMethods //容器端暴露的方法 +} + +export type GenGetters = CreateGetters + +export const getters: GenGetters = { + nativeMethods() { + if (this.device.label === DEVICE.ANDROID) { + return window.android + } + return window?.chrome?.webview?.hostObjects?.csobj + } +} diff --git a/src/stores/root/index.ts b/src/stores/root/index.ts new file mode 100644 index 0000000..461c228 --- /dev/null +++ b/src/stores/root/index.ts @@ -0,0 +1,14 @@ +import { defineStore } from 'pinia' +import { state } from './state' +import { getters } from './getters' +import { actions } from './actions' +import type { Actions } from './actions' +import type { Root } from '../types' +import type { State } from './state' +import type { Getters } from './getters' + +export const useRootStore = defineStore('root', { + state, + getters, + actions +}) diff --git a/src/stores/root/state.ts b/src/stores/root/state.ts new file mode 100644 index 0000000..f096ad2 --- /dev/null +++ b/src/stores/root/state.ts @@ -0,0 +1,13 @@ +export interface State { + backTime: [number, number] // 返回弹框的出现时间 第一位为返回首页 第二位返回屏保 + config: Config //配置文件 + device: Device //当前设备信息 + weather: Weather +} + +export const state = (): State => ({ + weather: {} as Weather, + backTime: [60, -1], + config: {} as Config, + device: {} as Device +}) diff --git a/src/stores/types.ts b/src/stores/types.ts new file mode 100644 index 0000000..bb2b00d --- /dev/null +++ b/src/stores/types.ts @@ -0,0 +1,11 @@ +import type { UnwrapRef } from 'vue' +import type { PiniaCustomProperties, StateTree, _GettersTree, _StoreWithGetters, _StoreWithState } from 'pinia' + +export type Root = 'root' + +export type CreateActions = A & + ThisType & _StoreWithState, A> & _StoreWithGetters<_GettersTree> & PiniaCustomProperties> + +export type CreateGetters> = G & + ThisType & _StoreWithGetters & PiniaCustomProperties> & + _GettersTree diff --git a/src/types/base.d.ts b/src/types/base.d.ts new file mode 100644 index 0000000..ca227a8 --- /dev/null +++ b/src/types/base.d.ts @@ -0,0 +1,6 @@ +declare interface Base { + id: number + title: string + entryCode: string + content: T +} diff --git a/src/types/config.d.ts b/src/types/config.d.ts index c558039..cb07377 100644 --- a/src/types/config.d.ts +++ b/src/types/config.d.ts @@ -1,5 +1,5 @@ declare interface Config { - smallUrl: string - bigUrl: string - baseUrl: string + interfaceUrl: string + mobileNav: string + handWriteUrl: string } diff --git a/src/types/customer.d.ts b/src/types/customer.d.ts index 418f9a1..0ea2f36 100644 --- a/src/types/customer.d.ts +++ b/src/types/customer.d.ts @@ -1,6 +1,40 @@ -interface Customer { - suggestionContent: string - replyContent: string - addTime: string - updateTime: string +declare interface Customer { + /** + * 经理签名 + */ + managerSignature: string + proposalList: ProposalList[] + /** + * 盖章图片 + */ + sealUrl: string +} +declare interface ProposalList { + /** + * 心声内容 + */ + content: string + createTime: string + /** + * 顾客称呼 + */ + customerName: string + /** + * 顾客电话 + */ + customerPhone: string + disposeDes: string + pictureListAfter: PictureListAfter[] + pictureListBefore: PictureListBefore[] + proposalCode: string + replyTime: string + /** + * 店长签名 + */ + signature: string + /** + * 分类名称 + */ + sortName: string + [x: string]: any } diff --git a/src/types/device.d.ts b/src/types/device.d.ts index 13ba0fd..7d2f5f9 100644 --- a/src/types/device.d.ts +++ b/src/types/device.d.ts @@ -1,12 +1,20 @@ -interface Device { - id: number - ip: string - devNum: string - xaxis: string - yaxis: string - angle: string - floorCode: string - floorName: string - order: number - mallCode: string +declare interface Device { + angle: string //角度 + building: string //楼栋名称 + buildingOrder: number //楼栋order + buildingCode: number //楼栋code + floor: string //楼层名称 + floorCode: number //楼层code + floorOrder: number //楼层order + ip: string //设备ip + label: 'windows' | 'android' //设备类型 + location: string //设备点位 + mac: string //mac 地址 + machineCode: string //设备code + machineName: string //设备名称 + machineTypeName: string //设备类型 + deviceCode: string + projectCode: string //项目code + screenAttribute: string //屏幕属性 + deployType: string //部署方式 } diff --git a/src/types/native.d.ts b/src/types/native.d.ts new file mode 100644 index 0000000..0d8ad7f --- /dev/null +++ b/src/types/native.d.ts @@ -0,0 +1,55 @@ +export declare global { + type VideoStream = { + age: 23 + genderMale: '女' | '男' + faceID: string + faceImage: string //人脸图片base64格式 需自行拼接前缀 data:image/jpg;base64, + } + + type HardwareInfo = { + width: string + height: string + ip: string + code: string + mac: string + serverIP: string + } + + type VoiceContent = { + code: number | string + msg: string | null + data: { + modelType: string + actionType: string + query: string + ttsMsg?: string + word?: string + reply?: string[] + } + } + + interface NativeMethods { + startFace(): boolean //通知APP开启摄像头,开始采集 + stopFace(): boolean //通知APP结束识别 + pushFaceBase(): VideoStream //APP推送视频流给应用 + pushFaceAttribute(): VideoStream //APP获取人脸属性推送给应用 + takePhoto(): string //应用通知APP拍照 返回Base64 + startVoice(): boolean //应用通知APP开始语音识别 + stopVoice(): boolean //应用通知APP停止语音识别 + voiceContent(): VoiceContent //语音返回数据 + deviceInfo(): HardwareInfo //设备信息 + goScreenSave(): void // 针对导视应用与app之间屏保跳转进行通讯 + hasProgram(): boolean //是否有节目列表 可结合后台管理系统屏保倒计时时间确认前端导视是否弹起屏保弹框 + } + interface Window { + leaveScreenSave(): void + android: NativeMethods + chrome: { + webview: { + hostObjects: { + csobj: NativeMethods + } + } + } + } +}
+ {{ title }} +
+ {{ delay }}s +