gemercheung преди 2 години
родител
ревизия
39e25c982a

+ 3 - 0
package.json

@@ -25,10 +25,13 @@
     "axios": "^0.27.2",
     "dayjs": "^1.11.6",
     "less": "^4.1.3",
+    "lodash-es": "^4.17.21",
     "normalize.css": "^8.0.1",
     "pinia": "^2.0.22",
+    "pinia-plugin-persistedstate": "^2.3.0",
     "sass": "^1.54.9",
     "vue": "^3.2.37",
+    "vue-i18n": "^9.2.2",
     "vue-router": "4"
   },
   "devDependencies": {

+ 68 - 0
pnpm-lock.yaml

@@ -17,9 +17,11 @@ specifiers:
   husky: ^8.0.1
   less: ^4.1.3
   lint-staged: ^13.0.3
+  lodash-es: ^4.17.21
   minimist: ^1.2.6
   normalize.css: ^8.0.1
   pinia: ^2.0.22
+  pinia-plugin-persistedstate: ^2.3.0
   prettier: ^2.7.1
   sass: ^1.54.9
   typescript: ^4.6.4
@@ -29,6 +31,7 @@ specifiers:
   vite: ^3.1.0
   vue: ^3.2.37
   vue-eslint-parser: ^9.1.0
+  vue-i18n: ^9.2.2
   vue-router: '4'
   vue-tsc: ^0.40.4
 
@@ -39,10 +42,13 @@ dependencies:
   axios: 0.27.2
   dayjs: 1.11.6
   less: 4.1.3
+  lodash-es: 4.17.21
   normalize.css: 8.0.1
   pinia: 2.0.23_l7r24p6nevbtlimqmqcwa3ouhu
+  pinia-plugin-persistedstate: 2.4.0_pinia@2.0.23
   sass: 1.55.0
   vue: 3.2.41
+  vue-i18n: 9.2.2_vue@3.2.41
   vue-router: 4.1.6_vue@3.2.41
 
 devDependencies:
@@ -182,6 +188,44 @@ packages:
     resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==}
     dev: true
 
+  /@intlify/core-base/9.2.2:
+    resolution: {integrity: sha512-JjUpQtNfn+joMbrXvpR4hTF8iJQ2sEFzzK3KIESOx+f+uwIjgw20igOyaIdhfsVVBCds8ZM64MoeNSx+PHQMkA==}
+    engines: {node: '>= 14'}
+    dependencies:
+      '@intlify/devtools-if': 9.2.2
+      '@intlify/message-compiler': 9.2.2
+      '@intlify/shared': 9.2.2
+      '@intlify/vue-devtools': 9.2.2
+    dev: false
+
+  /@intlify/devtools-if/9.2.2:
+    resolution: {integrity: sha512-4ttr/FNO29w+kBbU7HZ/U0Lzuh2cRDhP8UlWOtV9ERcjHzuyXVZmjyleESK6eVP60tGC9QtQW9yZE+JeRhDHkg==}
+    engines: {node: '>= 14'}
+    dependencies:
+      '@intlify/shared': 9.2.2
+    dev: false
+
+  /@intlify/message-compiler/9.2.2:
+    resolution: {integrity: sha512-IUrQW7byAKN2fMBe8z6sK6riG1pue95e5jfokn8hA5Q3Bqy4MBJ5lJAofUsawQJYHeoPJ7svMDyBaVJ4d0GTtA==}
+    engines: {node: '>= 14'}
+    dependencies:
+      '@intlify/shared': 9.2.2
+      source-map: 0.6.1
+    dev: false
+
+  /@intlify/shared/9.2.2:
+    resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==}
+    engines: {node: '>= 14'}
+    dev: false
+
+  /@intlify/vue-devtools/9.2.2:
+    resolution: {integrity: sha512-+dUyqyCHWHb/UcvY1MlIpO87munedm3Gn6E9WWYdWrMuYLcoIoOEVDWSS8xSwtlPU+kA+MEQTP6Q1iI/ocusJg==}
+    engines: {node: '>= 14'}
+    dependencies:
+      '@intlify/core-base': 9.2.2
+      '@intlify/shared': 9.2.2
+    dev: false
+
   /@nodelib/fs.scandir/2.1.5:
     resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
     engines: {node: '>= 8'}
@@ -1997,6 +2041,17 @@ packages:
     engines: {node: '>=6'}
     optional: true
 
+  /pinia-plugin-persistedstate/2.4.0_pinia@2.0.23:
+    resolution: {integrity: sha512-bQcpv47jk3ISl+InuJWsFaS/K7pRZ97kfoD2WCf/suhnlLy48k3BnFM2tI6YZ1xMsDaPv4yOsaPuPAUuSmEO2Q==}
+    peerDependencies:
+      pinia: ^2.0.0
+    peerDependenciesMeta:
+      pinia:
+        optional: true
+    dependencies:
+      pinia: 2.0.23_l7r24p6nevbtlimqmqcwa3ouhu
+    dev: false
+
   /pinia/2.0.23_l7r24p6nevbtlimqmqcwa3ouhu:
     resolution: {integrity: sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==}
     peerDependencies:
@@ -2535,6 +2590,19 @@ packages:
       - supports-color
     dev: true
 
+  /vue-i18n/9.2.2_vue@3.2.41:
+    resolution: {integrity: sha512-yswpwtj89rTBhegUAv9Mu37LNznyu3NpyLQmozF3i1hYOhwpG8RjcjIFIIfnu+2MDZJGSZPXaKWvnQA71Yv9TQ==}
+    engines: {node: '>= 14'}
+    peerDependencies:
+      vue: ^3.0.0
+    dependencies:
+      '@intlify/core-base': 9.2.2
+      '@intlify/shared': 9.2.2
+      '@intlify/vue-devtools': 9.2.2
+      '@vue/devtools-api': 6.4.5
+      vue: 3.2.41
+    dev: false
+
   /vue-router/4.1.6_vue@3.2.41:
     resolution: {integrity: sha512-DYWYwsG6xNPmLq/FmZn8Ip+qrhFEzA14EI12MsMgVxvHFDYvlr4NXpVF5hrRH1wVcDP8fGi5F4rxuJSl8/r+EQ==}
     peerDependencies:

+ 24 - 5
src/api/instance.ts

@@ -1,5 +1,6 @@
 import { axiosFactory } from './setup'
-import { message } from 'ant-design-vue'
+import { h } from 'vue'
+import { message, Modal } from 'ant-design-vue'
 import { showLoading, hideLoading } from '@/components/loading'
 import { ResCode, ResCodeDesc } from './constant'
 import { baseURL, token, mainURL } from '@/env'
@@ -28,10 +29,28 @@ export const gotoLogin = () => {
     mainURL + '/?redirect=' + escape(location.href) + '#/login/login'
   )
 }
-
+let isOnWarning = false
 const tokenInvalid = () => {
-  message.error(ResCodeDesc[ResCode.TOKEN_INVALID])
-  setTimeout(gotoLogin, 1000)
+  if (!isOnWarning) {
+    // debugger
+    setTimeout(() => {
+      Modal.info({
+        title: () => '提示',
+        zIndex: 10000,
+        centered: true,
+        content: () => h('div', {}, [h('p', '登录状态失效,请重新登录')]),
+        okText: '确定',
+        onOk() {
+          console.log('ok')
+          gotoLogin()
+        }
+      })
+    }, 100)
+    isOnWarning = true
+  }
+
+  // message.error(ResCodeDesc[ResCode.TOKEN_INVALID])
+  // setTimeout(gotoLogin, 1000)
 }
 
 addReqErrorHandler(err => {
@@ -63,7 +82,7 @@ console.log(baseURL)
 setDefaultURI(baseURL)
 
 if (!token) {
-  tokenInvalid()
+  // tokenInvalid()
 } else {
   setToken(token)
 }

+ 1 - 0
src/components/loading/index.ts

@@ -41,6 +41,7 @@ export const hideLoading = function (hkey?: LoginMark) {
 
 export const hideLoadingAll = function () {
   for (const { close } of closeStack) {
+    console.log('closeStack', closeStack)
     close && close()
   }
   closeStack.length = 0

+ 54 - 0
src/hook/useI18n.ts

@@ -0,0 +1,54 @@
+import { i18n } from '@/locales/setupI18n'
+
+type I18nGlobalTranslation = {
+  (key: string): string
+  (key: string, locale: string): string
+  (key: string, locale: string, list: unknown[]): string
+  (key: string, locale: string, named: Record<string, unknown>): string
+  (key: string, list: unknown[]): string
+  (key: string, named: Record<string, unknown>): string
+}
+
+type I18nTranslationRestParameters = [string, any]
+
+function getKey(namespace: string | undefined, key: string) {
+  if (!namespace) {
+    return key
+  }
+  if (key.startsWith(namespace)) {
+    return key
+  }
+  return `${namespace}.${key}`
+}
+
+export function useI18n(namespace?: string): {
+  t: I18nGlobalTranslation
+} {
+  const normalFn = {
+    t: (key: string) => {
+      return getKey(namespace, key)
+    }
+  }
+
+  if (!i18n) {
+    return normalFn
+  }
+
+  const { t, ...methods } = i18n.global
+
+  const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => {
+    if (!key) return ''
+    if (!key.includes('.') && !namespace) return key
+    const res = t(
+      getKey(namespace, key),
+      ...(arg as I18nTranslationRestParameters)
+    )
+    return res
+  }
+  return {
+    ...methods,
+    t: tFn
+  }
+}
+
+export const t = (key: string) => key

+ 1 - 1
src/layout/header.vue

@@ -36,7 +36,7 @@
 
 <script lang="ts" setup>
 import { MenuProps } from 'ant-design-vue'
-import { useUserStore } from '@/store'
+import { useUserStore } from '@/store/modules/user'
 import { postLogout } from '@/api'
 import { mainURL } from '@/env'
 import logoPng from '@/assets/images/logo.png'

+ 40 - 0
src/locales/helper.ts

@@ -0,0 +1,40 @@
+import type { LocaleType } from '/#/config'
+
+import { set } from 'lodash-es'
+
+export const loadLocalePool: LocaleType[] = []
+
+export function setHtmlPageLang(locale: LocaleType) {
+  document.querySelector('html')?.setAttribute('lang', locale)
+}
+
+export function setLoadLocalePool(cb: (loadLocalePool: LocaleType[]) => void) {
+  cb(loadLocalePool)
+}
+
+export function genMessage(
+  langs: Record<string, Record<string, any>>,
+  prefix = 'lang'
+) {
+  const obj: Recordable = {}
+
+  Object.keys(langs).forEach(key => {
+    const langFileModule = langs[key].default
+    let fileName = key.replace(`./${prefix}/`, '').replace(/^\.\//, '')
+    const lastIndex = fileName.lastIndexOf('.')
+    fileName = fileName.substring(0, lastIndex)
+    const keyList = fileName.split('/')
+    const moduleName = keyList.shift()
+    const objKey = keyList.join('.')
+
+    if (moduleName) {
+      if (objKey) {
+        set(obj, moduleName, obj[moduleName] || {})
+        set(obj[moduleName], objKey, langFileModule)
+      } else {
+        set(obj, moduleName, langFileModule || {})
+      }
+    }
+  })
+  return obj
+}

+ 3 - 0
src/locales/lang/en/index.ts

@@ -0,0 +1,3 @@
+export default {
+  name: 'test'
+}

+ 3 - 0
src/locales/lang/zh-CN/index.ts

@@ -0,0 +1,3 @@
+export default {
+  name: '测试'
+}

+ 44 - 0
src/locales/setupI18n.ts

@@ -0,0 +1,44 @@
+import type { App } from 'vue'
+import type { I18n, I18nOptions } from 'vue-i18n'
+
+import { createI18n } from 'vue-i18n'
+import { setHtmlPageLang, setLoadLocalePool } from './helper'
+import { localeSetting } from '@/settings/localeSetting'
+import { useLocaleStoreWithOut } from '@/store/modules/locale'
+
+const { fallback, availableLocales } = localeSetting
+
+export let i18n: ReturnType<typeof createI18n>
+
+async function createI18nOptions(): Promise<I18nOptions> {
+  const localeStore = useLocaleStoreWithOut()
+  const locale = localeStore.getLocale
+  const defaultLocal = await import(`./lang/${locale}.ts`)
+  const message = defaultLocal.default?.message ?? {}
+
+  setHtmlPageLang(locale)
+  setLoadLocalePool(loadLocalePool => {
+    loadLocalePool.push(locale)
+  })
+
+  return {
+    legacy: false,
+    locale,
+    fallbackLocale: fallback,
+    messages: {
+      [locale]: message
+    },
+    availableLocales: availableLocales,
+    sync: true, //If you don’t want to inherit locale from global scope, you need to set sync of i18n component option to false.
+    silentTranslationWarn: true, // true - warning off
+    missingWarn: false,
+    silentFallbackWarn: true
+  }
+}
+
+// setup i18n instance with glob
+export async function setupI18n(app: App) {
+  const options = await createI18nOptions()
+  i18n = createI18n(options) as I18n
+  app.use(i18n)
+}

+ 70 - 0
src/locales/useLocale.ts

@@ -0,0 +1,70 @@
+/**
+ * Multi-language related operations
+ */
+import type { LocaleType } from '#/config'
+
+import { i18n } from './setupI18n'
+import { useLocaleStoreWithOut } from '@/store/modules/locale'
+import { unref, computed } from 'vue'
+import { loadLocalePool, setHtmlPageLang } from './helper'
+
+interface LangModule {
+  message: Recordable
+  dateLocale: Recordable
+  dateLocaleName: string
+}
+
+function setI18nLanguage(locale: LocaleType) {
+  const localeStore = useLocaleStoreWithOut()
+
+  if (i18n.mode === 'legacy') {
+    i18n.global.locale = locale
+  } else {
+    ; (i18n.global.locale as any).value = locale
+  }
+  localeStore.setLocaleInfo({ locale })
+  setHtmlPageLang(locale)
+}
+
+export function useLocale() {
+  const localeStore = useLocaleStoreWithOut()
+  const getLocale = computed(() => localeStore.getLocale)
+  // const getShowLocalePicker = computed(() => localeStore.getShowPicker);
+
+  // const getAntdLocale = computed((): any => {
+  //   return i18n.global.getLocaleMessage(unref(getLocale))?.antdLocale ?? {};
+  // });
+
+  // Switching the language will change the locale of useI18n
+  // And submit to configuration modification
+  async function changeLocale(locale: LocaleType) {
+    const globalI18n = i18n.global
+    const currentLocale = unref(globalI18n.locale)
+    if (currentLocale === locale) {
+      return locale
+    }
+
+    if (loadLocalePool.includes(locale)) {
+      setI18nLanguage(locale)
+      return locale
+    }
+    const langModule = ((await import(`./lang/${locale}.ts`)) as any)
+      .default as LangModule
+    if (!langModule) return
+
+    const { message } = langModule
+
+    globalI18n.setLocaleMessage(locale, message)
+    loadLocalePool.push(locale)
+
+    setI18nLanguage(locale)
+    return locale
+  }
+
+  return {
+    getLocale,
+    // getShowLocalePicker,
+    changeLocale
+    // getAntdLocale,
+  }
+}

+ 2 - 2
src/main.ts

@@ -5,9 +5,9 @@ import 'ant-design-vue/lib/message/style/index.less'
 import './style.css'
 import App from './App.vue'
 import router from './router'
-import store from './store'
+import { setupStore } from '@/store'
 
 export const app = createApp(App)
 app.use(router)
-app.use(store)
+setupStore(app)
 app.mount('#app')

+ 27 - 0
src/settings/localeSetting.ts

@@ -0,0 +1,27 @@
+import type { LocaleSetting, LocaleType } from '/#/config'
+
+export const LOCALE: { [key: string]: LocaleType } = {
+  ZH_CN: 'zh_CN',
+  EN_US: 'en'
+}
+
+export const localeSetting: LocaleSetting = {
+  // Locale
+  locale: LOCALE.ZH_CN,
+  // Default locale
+  fallback: LOCALE.ZH_CN,
+  // available Locales
+  availableLocales: [LOCALE.ZH_CN, LOCALE.EN_US]
+}
+
+// locale list
+// export const localeList: DropMenu[] = [
+//   {
+//     text: '简体中文',
+//     event: LOCALE.ZH_CN,
+//   },
+//   {
+//     text: 'English',
+//     event: LOCALE.EN_US,
+//   },
+// ];

+ 8 - 6
src/store/index.ts

@@ -1,9 +1,11 @@
+import type { App } from 'vue'
 import { createPinia } from 'pinia'
+import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
+const store = createPinia()
+store.use(piniaPluginPersistedstate)
 
-export * from './room'
-export * from './user'
-export * from './scene'
-export * from './constant'
+export function setupStore(app: App<Element>) {
+  app.use(store)
+}
 
-export const pinia = createPinia()
-export default pinia
+export { store }

src/store/constant.ts → src/store/modules/constant.ts


+ 52 - 0
src/store/modules/locale.ts

@@ -0,0 +1,52 @@
+import type { LocaleSetting, LocaleType } from '/#/config'
+
+import { defineStore } from 'pinia'
+import { store } from '/@/store'
+
+interface LocaleState {
+  localInfo: LocaleSetting
+}
+
+export const useLocaleStore = defineStore({
+  id: 'app-locale',
+  state: (): LocaleState => ({
+    localInfo: {
+      locale: 'zh_CN',
+      // default language
+      fallback: 'zh_CN',
+      // available Locales
+      availableLocales: ['zh_CN', 'en']
+    }
+  }),
+  persist: {
+    storage: localStorage,
+    paths: ['localInfo']
+  },
+  getters: {
+    getLocale(): LocaleType {
+      return this.localInfo?.locale ?? 'zh_CN'
+    }
+  },
+  actions: {
+    /**
+     * Set up multilingual information and cache
+     * @param info multilingual info
+     */
+    setLocaleInfo(info: Partial<LocaleSetting>) {
+      this.localInfo = { ...this.localInfo, ...info }
+    },
+    /**
+     * Initialize multilingual information and load the existing configuration from the local cache
+     */
+    initLocale() {
+      this.setLocaleInfo({
+        ...this.localInfo
+      })
+    }
+  }
+})
+
+// Need to be used outside the setup
+export function useLocaleStoreWithOut() {
+  return useLocaleStore(store)
+}

+ 2 - 4
src/store/room.ts

@@ -20,8 +20,6 @@ export type Room = SRoom & {
   miniCode?: string
   leaderMiniCode?: string
   scenes: Scenes
-
-
 }
 
 export interface ShareLinkType {
@@ -58,8 +56,8 @@ export const useRoomStore = defineStore('room', {
   getters: {
     getNums:
       () =>
-        <T extends Pick<Room, 'scenes'>>(room: T) =>
-          room.scenes.map(scene => scene.num),
+      <T extends Pick<Room, 'scenes'>>(room: T) =>
+        room.scenes.map(scene => scene.num),
     getShareUrl: () => (param: ShareLinkType) => {
       const search = new URLSearchParams()
       search.set('m', `${param.num}`)

src/store/scene.ts → src/store/modules/scene.ts


src/store/user.ts → src/store/modules/user.ts


+ 3 - 3
src/views/room/edit-room/index.vue

@@ -76,7 +76,7 @@
           :rules="[{ required: true, message: '请输入昵称' }]"
         >
           <a-input
-            v-model:value="current.leaderName"
+            v-model:value.trim="current.leaderName"
             placeholder="请输入主持人昵称,限15字"
             :maxlength="15"
             show-count
@@ -102,14 +102,14 @@
 
 <script lang="ts">
 import { ref, defineComponent, reactive } from 'vue'
-import { createRoom, useRoomStore } from '@/store'
+import { createRoom, useRoomStore } from '@/store/modules/room'
 import { props } from './props'
 import { message } from 'ant-design-vue'
 import { mainURL } from '@/env'
 import EditScenes from './scene-list.vue'
 import unScenePng from '@/assets/images/un-scene.png'
 
-import type { Scene } from '@/store'
+import type { Scene } from '@/store/modules/scene'
 import type { FormInstance } from 'ant-design-vue'
 
 const roomStore = useRoomStore()

+ 6 - 2
src/views/room/edit-room/scene-list.vue

@@ -27,11 +27,15 @@
 <script lang="ts" setup>
 import { computed } from 'vue'
 import { Modal } from 'ant-design-vue'
-import { useSceneStore, sceneStatusDesc, SceneStatus } from '@/store'
+import {
+  useSceneStore,
+  sceneStatusDesc,
+  SceneStatus
+} from '@/store/modules/scene'
 import { diffArrayChange } from '@/shared'
 import SceneList from '@/views/scene/list.vue'
 
-import type { Scene } from '@/store'
+import type { Scene } from '@/store/modules/scene'
 import { renderModal } from '@/helper'
 
 defineOptions<{ name: 'RoomSceneList' }>()

+ 3 - 2
src/views/room/list.vue

@@ -61,7 +61,8 @@
 </template>
 
 <script setup lang="ts">
-import { useRoomStore, useUserStore } from '@/store'
+import { useRoomStore } from '@/store/modules/room'
+import { useUserStore } from '@/store/modules/user'
 import { ref, computed, createVNode } from 'vue'
 import { message, Modal } from 'ant-design-vue'
 import { copyText } from '@/shared'
@@ -75,7 +76,7 @@ import Share from './modal/share.vue'
 import MiniSync from './modal/mini-sync.vue'
 import DataList from '@/components/data-list/index.vue'
 
-import type { Room } from '@/store'
+import type { Room } from '@/store/modules/room'
 import { fetchRoomDetail } from '@/api'
 
 defineOptions({ name: 'RoomList' })

+ 2 - 2
src/views/room/modal/share.vue

@@ -14,8 +14,8 @@
 </template>
 
 <script lang="ts" setup>
-import { useRoomStore } from '@/store'
-import type { Room } from '@/store'
+import { useRoomStore } from '@/store/modules/room'
+import type { Room } from '@/store/modules/room'
 
 defineOptions({ name: 'RoomShare' })
 const props = defineProps<{ room: Room; num: string }>()

+ 1 - 1
src/views/room/sign.vue

@@ -57,7 +57,7 @@
 </template>
 
 <script lang="ts" setup>
-import type { Room } from '@/store'
+import type { Room } from '@/store/modules/room'
 import Dayjs from 'dayjs'
 type RoomSignProps = { room: Room }
 type RoomSignEmit = {

+ 2 - 2
src/views/scene/list.vue

@@ -43,9 +43,9 @@
 
 <script lang="ts" setup>
 import { ref, computed } from 'vue'
-import { useSceneStore } from '@/store'
+import { useSceneStore } from '@/store/modules/scene'
 import DataList from '@/components/data-list/index.vue'
-import type { Scene } from '@/store'
+import type { Scene } from '@/store/modules/scene'
 
 defineOptions<{ name: 'SceneList' }>()
 

+ 3 - 1
tsconfig.json

@@ -13,9 +13,11 @@
     "lib": ["ESNext", "DOM"],
     "skipLibCheck": true,
     "types": ["unplugin-vue-define-options/macros-global"],
+    "typeRoots": ["./node_modules/@types/", "./types"],
     "baseUrl": "./",
     "paths": {
-      "@/*": ["src/*"]
+      "@/*": ["src/*"],
+      "#/*": ["types/*"]
     }
   },
   "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],

+ 11 - 0
types/config.d.ts

@@ -0,0 +1,11 @@
+export type LocaleType = 'zh_CN' | 'en' | 'ru' | 'ja' | 'ko'
+
+export interface LocaleSetting {
+  // showPicker: boolean;
+  // Current language
+  locale: LocaleType
+  // default language
+  fallback: LocaleType
+  // available Locales
+  availableLocales: LocaleType[]
+}

+ 101 - 0
types/global.d.ts

@@ -0,0 +1,101 @@
+import type {
+  ComponentRenderProxy,
+  VNode,
+  VNodeChild,
+  ComponentPublicInstance,
+  FunctionalComponent,
+  PropType as VuePropType
+} from 'vue'
+
+declare global {
+  const __APP_INFO__: {
+    pkg: {
+      name: string
+      version: string
+      dependencies: Recordable<string>
+      devDependencies: Recordable<string>
+    }
+    lastBuildTime: string
+  }
+  // declare interface Window {
+  //   // Global vue app instance
+  //   __APP__: App<Element>;
+  // }
+
+  // vue
+  declare type PropType<T> = VuePropType<T>
+  declare type VueNode = VNodeChild | JSX.Element
+
+  export type Writable<T> = {
+    -readonly [P in keyof T]: T[P]
+  }
+
+  declare type Nullable<T> = T | null
+  declare type NonNullable<T> = T extends null | undefined ? never : T
+  declare type Recordable<T = any> = Record<string, T>
+  declare type ReadonlyRecordable<T = any> = {
+    readonly [key: string]: T
+  }
+  declare type Indexable<T = any> = {
+    [key: string]: T
+  }
+  declare type DeepPartial<T> = {
+    [P in keyof T]?: DeepPartial<T[P]>
+  }
+  declare type TimeoutHandle = ReturnType<typeof setTimeout>
+  declare type IntervalHandle = ReturnType<typeof setInterval>
+
+  declare interface ChangeEvent extends Event {
+    target: HTMLInputElement
+  }
+
+  declare interface WheelEvent {
+    path?: EventTarget[]
+  }
+  interface ImportMetaEnv extends ViteEnv {
+    __: unknown
+  }
+
+  declare interface ViteEnv {
+    VITE_PORT: number
+    VITE_USE_MOCK: boolean
+    VITE_USE_PWA: boolean
+    VITE_PUBLIC_PATH: string
+    VITE_PROXY: [string, string][]
+    VITE_GLOB_APP_TITLE: string
+    VITE_GLOB_APP_SHORT_NAME: string
+    VITE_USE_CDN: boolean
+    VITE_DROP_CONSOLE: boolean
+    VITE_BUILD_COMPRESS: 'gzip' | 'brotli' | 'none'
+    VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE: boolean
+    VITE_LEGACY: boolean
+    VITE_USE_IMAGEMIN: boolean
+    VITE_GENERATE_UI: string
+  }
+
+  declare function parseInt(s: string | number, radix?: number): number
+
+  declare function parseFloat(string: string | number): number
+
+  namespace JSX {
+    // tslint:disable no-empty-interface
+    type Element = VNode
+    // tslint:disable no-empty-interface
+    type ElementClass = ComponentRenderProxy
+    interface ElementAttributesProperty {
+      $props: any
+    }
+    interface IntrinsicElements {
+      [elem: string]: any
+    }
+    interface IntrinsicAttributes {
+      [elem: string]: any
+    }
+  }
+}
+
+declare module 'vue' {
+  export type JSXComponent<Props = any> =
+    | { new (): ComponentPublicInstance<Props> }
+    | FunctionalComponent<Props>
+}

+ 28 - 0
types/index.d.ts

@@ -0,0 +1,28 @@
+declare interface Fn<T = any, R = T> {
+  (...arg: T[]): R
+}
+
+declare interface PromiseFn<T = any, R = T> {
+  (...arg: T[]): Promise<R>
+}
+
+declare type RefType<T> = T | null
+
+declare type LabelValueOptions = {
+  label: string
+  value: any
+  [key: string]: string | number | boolean
+}[]
+
+declare type EmitType = (event: string, ...args: any[]) => void
+
+declare type TargetContext = '_self' | '_blank'
+
+declare interface ComponentElRef<T extends HTMLElement = HTMLDivElement> {
+  $el: T
+}
+
+declare type ComponentRef<T extends HTMLElement = HTMLDivElement> =
+  ComponentElRef<T> | null
+
+declare type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>

+ 75 - 0
types/sdk.d.ts

@@ -0,0 +1,75 @@
+import EventEmitter from 'events'
+
+declare interface CadCadManagerType extends EventEmitter {
+  edit: {
+    enter: Fn
+    exit: Fn
+  }
+}
+declare interface TourManagerType extends EventEmitter {
+  load: Fn<any[]>
+}
+declare interface RemoteEditorType extends EventEmitter {
+  tour_delete: ({ num: string }) => Promise<void>
+}
+
+declare interface KanKanInstance extends EventEmitter {
+  [x: string]: any
+  CadCadManager: CadCadManagerType
+  TourManager: TourManagerType
+  remote_editor: RemoteEditorType
+}
+
+declare interface KankanMetaDataType {
+  num: string
+  floorLogo: string
+  floorLogoSize: number
+  floorLogoFile: string
+  music: string
+  musicFile: string
+  scenePassword: string
+  title: string
+  description: string
+  controls: {
+    showMap: number
+    showLock: number
+    showTitle: number
+    showPanorama: number
+    showDollhouse: number
+    showFloorplan: number
+    showVR: number
+    showTour: number
+    showRule: number
+  }
+  createTime: string
+  version: number
+  imgVersion: number
+  linkVersion: number
+  floorPlanUser: number
+  entry: any
+  sceneResolution: string
+  sceneFrom: string
+  sceneKind: string
+  boxPhotos: string
+  boxModels: string
+  videos: {
+    data: [{ blend_fov: string; id: string; value: string }]
+    upPath: string
+    version: number
+  }
+  tags: number
+  loadingLogo: string
+  loadingLogoFile: string
+  dataSync: any
+  floorPlanAngle: number
+  floorPlanCompass: number
+  floorPlanUpload: any
+  tours: number
+  mosaic: number
+  mosaicList: []
+  waterMark: any
+  links: number
+  filters: number
+  roiFilter: any
+  surveillances: number
+}

Файловите разлики са ограничени, защото са твърде много
+ 0 - 58
vite.config.ts.timestamp-1668659340338.mjs