├── .env.development
├── .env.production
├── .github
└── workflows
│ └── docker-ci.yml
├── .gitignore
├── .prettierignore
├── .prettierrc.js
├── .stylelintrc.js
├── Dockerfile
├── LICENSE
├── README.md
├── auto-imports.d.ts
├── babel.config.js
├── commitlint.config.js
├── components.d.ts
├── config
├── plugin
│ ├── arcoResolver.ts
│ ├── arcoStyleImport.ts
│ ├── compress.ts
│ ├── imagemin.ts
│ └── visualizer.ts
├── utils
│ └── index.ts
├── vite.config.base.ts
├── vite.config.dev.ts
└── vite.config.prod.ts
├── docker-compose.yml
├── forge.config.js
├── icon.ico
├── index.html
├── main.js
├── package-lock.json
├── package.json
├── public
├── 1714006925783.jpg
├── 1722745910257.jpg
├── CNAME
├── K5Channel.xlsx
├── K5Channel_EN.xlsx
├── LOSEHU117P6.bin
├── LOSEHU117P6K.bin
├── LOSEHU126.bin
├── LOSEHU126H.bin
├── LOSEHU126K.bin
├── L_BL001.bin
├── O1CN019Pat6v1ZuNgxL8CRt_!!6000000003254-0-tps-800-450.jpg
├── adimg1c.png
├── adimg2c.png
├── gy.png
├── img1.png
├── img2.png
├── img3.png
├── img4.png
├── img5.png
├── img6.png
├── jjgg.jpg
├── k5web.png
├── mm_facetoface_collect_qrcode_1714392837792.png
├── new_font_h.bin
├── new_font_k.bin
├── new_font_k_f.bin
├── old_font.bin
├── pinyin.bin
├── pinyin_plus.bin
├── qrcode_1714310463601.jpg
├── rhino-design-800x450.png
├── serial.js
├── sms_test.bin
└── ssb.bin
├── src
├── App.vue
├── api
│ ├── interceptor.ts
│ └── user.ts
├── assets
│ ├── images
│ │ └── login-banner.png
│ ├── logo.svg
│ ├── style
│ │ ├── breakpoint.less
│ │ └── global.less
│ └── world.json
├── components
│ ├── breadcrumb
│ │ └── index.vue
│ ├── chart
│ │ └── index.vue
│ ├── footer
│ │ └── index.vue
│ ├── global-setting
│ │ ├── block.vue
│ │ ├── form-wrapper.vue
│ │ └── index.vue
│ ├── index.ts
│ ├── menu
│ │ ├── index.vue
│ │ └── use-menu-tree.ts
│ ├── navbar
│ │ └── index.vue
│ └── tab-bar
│ │ ├── index.vue
│ │ ├── readme.md
│ │ └── tab-item.vue
├── config
│ └── settings.json
├── directive
│ ├── index.ts
│ └── permission
│ │ └── index.ts
├── drivers
│ ├── losehu117.json
│ ├── losehu117k.json
│ ├── losehu118.json
│ ├── losehu118h.json
│ ├── losehu118k.json
│ ├── losehu120k.json
│ ├── losehu124h.json
│ ├── losehubl.json
│ ├── losehud.json
│ ├── lts.json
│ ├── ltsk.json
│ └── todo.json
├── env.d.ts
├── hooks
│ ├── chart-option.ts
│ ├── loading.ts
│ ├── locale.ts
│ ├── permission.ts
│ ├── request.ts
│ ├── responsive.ts
│ ├── themes.ts
│ ├── user.ts
│ └── visible.ts
├── layout
│ ├── default-layout.vue
│ └── page-layout.vue
├── locale
│ ├── en-US.ts
│ ├── en-US
│ │ └── settings.ts
│ ├── index.ts
│ ├── zh-CN.ts
│ └── zh-CN
│ │ └── settings.ts
├── main.ts
├── router
│ ├── app-menus
│ │ └── index.ts
│ ├── constants.ts
│ ├── guard
│ │ ├── index.ts
│ │ ├── permission.ts
│ │ └── userLoginInfo.ts
│ ├── index.ts
│ ├── routes
│ │ ├── base.ts
│ │ ├── externalModules
│ │ │ └── faq.ts
│ │ ├── index.ts
│ │ ├── modules
│ │ │ ├── dashboard.ts
│ │ │ ├── idea.ts
│ │ │ └── list.ts
│ │ └── types.ts
│ └── typings.d.ts
├── store
│ ├── index.ts
│ └── modules
│ │ ├── app
│ │ ├── index.ts
│ │ └── types.ts
│ │ ├── tab-bar
│ │ ├── index.ts
│ │ └── types.ts
│ │ └── user
│ │ ├── index.ts
│ │ └── types.ts
├── types
│ ├── echarts.ts
│ ├── global.ts
│ └── mock.ts
├── utils
│ ├── AutoUpdate.js
│ ├── auth.ts
│ ├── env.ts
│ ├── event.ts
│ ├── index.ts
│ ├── is.ts
│ ├── monitor.ts
│ ├── route-listener.ts
│ ├── serial.js
│ └── setup-mock.ts
└── views
│ ├── dashboard
│ └── workplace
│ │ ├── components
│ │ └── banner.vue
│ │ ├── index.vue
│ │ └── locale
│ │ ├── en-US.ts
│ │ └── zh-CN.ts
│ ├── guide
│ └── f117
│ │ ├── assets
│ │ ├── cj1.png
│ │ ├── cj2.png
│ │ └── cj3.png
│ │ ├── index.vue
│ │ └── locale
│ │ ├── en-US.ts
│ │ └── zh-CN.ts
│ ├── idea
│ ├── channel
│ │ └── index.vue
│ ├── firmware
│ │ └── index.vue
│ ├── image
│ │ └── index.vue
│ └── losehu
│ │ └── index.vue
│ ├── list
│ ├── bl
│ │ └── index.vue
│ ├── card
│ │ ├── index.vue
│ │ └── locale
│ │ │ ├── en-US.ts
│ │ │ └── zh-CN.ts
│ ├── chat
│ │ └── index.vue
│ ├── chi
│ │ ├── index.vue
│ │ └── locale
│ │ │ ├── en-US.ts
│ │ │ └── zh-CN.ts
│ ├── dtmf
│ │ └── index.vue
│ ├── flash
│ │ ├── index.vue
│ │ └── locale
│ │ │ ├── en-US.ts
│ │ │ └── zh-CN.ts
│ ├── image
│ │ ├── index.vue
│ │ └── locale
│ │ │ ├── en-US.ts
│ │ │ └── zh-CN.ts
│ ├── mdc
│ │ └── index.vue
│ ├── radio
│ │ └── index.vue
│ ├── sat
│ │ ├── index.vue
│ │ └── locale
│ │ │ ├── en-US.ts
│ │ │ └── zh-CN.ts
│ ├── sat2
│ │ └── index.vue
│ ├── satloc
│ │ └── index.vue
│ ├── search-table
│ │ ├── index.vue
│ │ └── locale
│ │ │ ├── en-US.ts
│ │ │ └── zh-CN.ts
│ └── settings
│ │ ├── index.vue
│ │ └── locale
│ │ ├── en-US.ts
│ │ └── zh-CN.ts
│ ├── not-found
│ └── index.vue
│ ├── redirect
│ └── index.vue
│ └── thanks
│ └── index.vue
├── tsconfig.json
└── yarn.lock
/.env.development:
--------------------------------------------------------------------------------
1 | VITE_API_BASE_URL= 'http://localhost:8080'
2 | VITE_METER_SITE = ''
3 |
--------------------------------------------------------------------------------
/.env.production:
--------------------------------------------------------------------------------
1 | VITE_METER_SITE = 'k5.vicicode.com,k5.vicicode.cn,k6.vicicode.cn,k5.lhw711.cn,mm.md,k5.mm.md,k5.losehu.com,k6.losehu.com'
2 |
--------------------------------------------------------------------------------
/.github/workflows/docker-ci.yml:
--------------------------------------------------------------------------------
1 | name: k5web-docker-ci
2 |
3 | on: [push, pull_request]
4 |
5 | env:
6 | PLATFORMS: linux/amd64
7 | TAG: latest
8 |
9 | permissions:
10 | packages: write
11 |
12 | jobs:
13 | main:
14 | strategy:
15 | fail-fast: false
16 | matrix:
17 | configuration: [Release]
18 | file: [Dockerfile]
19 |
20 | runs-on: ubuntu-latest
21 |
22 | steps:
23 | - name: Checkout code
24 | uses: actions/checkout@v4.1.7
25 | with:
26 | show-progress: false
27 | submodules: recursive
28 |
29 |
30 | - name: Login to ghcr.io
31 | uses: docker/login-action@v3.2.0
32 | with:
33 | registry: ghcr.io
34 | username: ${{ github.actor }}
35 | password: ${{ secrets.GITHUB_TOKEN }}
36 |
37 | - name: Set up Docker Buildx
38 | uses: docker/setup-buildx-action@v3.3.0
39 |
40 | - name: Prepare environment outputs
41 | shell: sh
42 | run: |
43 | set -eu
44 |
45 | echo "DATE_ISO8601=$(date --iso-8601=seconds --utc)" >> "$GITHUB_ENV"
46 | echo "FIXED_TAG=$(echo ${{ github.ref }} | cut -d '/' -f 3)" >> "$GITHUB_ENV"
47 | echo "GHCR_REPOSITORY=$(echo ${{ github.repository }} | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
48 | echo "SHORT_SHA=$(echo ${{ github.sha }} | cut -c1-8)" >> $GITHUB_ENV
49 |
50 | - name: Build ${{ matrix.configuration }} Docker image from ${{ matrix.file }}
51 | uses: docker/build-push-action@v6.0.0
52 | with:
53 | build-args: CONFIGURATION=${{ matrix.configuration }}
54 | context: .
55 | file: ${{ matrix.file }}
56 | platforms: ${{ env.PLATFORMS }}
57 | labels: |
58 | org.opencontainers.image.created=${{ env.DATE_ISO8601 }}
59 | org.opencontainers.image.version=${{ env.FIXED_TAG }}
60 | org.opencontainers.image.revision=${{ github.sha }}
61 | tags: |
62 | ghcr.io/${{ env.GHCR_REPOSITORY }}:${{ env.SHORT_SHA }}
63 | ghcr.io/${{ env.GHCR_REPOSITORY }}:${{ env.TAG }}
64 | provenance: true
65 | sbom: true
66 | push: true
67 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .DS_Store
3 | dist
4 | dist-ssr
5 | *.local
6 | node_modules
7 | .DS_Store
8 | dist
9 | dist-ssr
10 | *.local
11 | out
12 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | /dist/*
2 | .local
3 | .output.js
4 | /node_modules/**
5 |
6 | **/*.svg
7 | **/*.sh
--------------------------------------------------------------------------------
/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | tabWidth: 2,
3 | semi: true,
4 | printWidth: 80,
5 | singleQuote: true,
6 | quoteProps: 'consistent',
7 | htmlWhitespaceSensitivity: 'strict',
8 | vueIndentScriptAndStyle: true,
9 | };
10 |
--------------------------------------------------------------------------------
/.stylelintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: [
3 | 'stylelint-config-standard',
4 | 'stylelint-config-rational-order',
5 | 'stylelint-config-prettier',
6 | 'stylelint-config-recommended-vue',
7 | ],
8 | defaultSeverity: 'warning',
9 | plugins: ['stylelint-order'],
10 | rules: {
11 | 'at-rule-no-unknown': [
12 | true,
13 | {
14 | ignoreAtRules: ['plugin'],
15 | },
16 | ],
17 | 'rule-empty-line-before': [
18 | 'always',
19 | {
20 | except: ['after-single-line-comment', 'first-nested'],
21 | },
22 | ],
23 | 'selector-pseudo-class-no-unknown': [
24 | true,
25 | {
26 | ignorePseudoClasses: ['deep'],
27 | },
28 | ],
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:22 AS build-yarn
2 |
3 | WORKDIR /app
4 |
5 | COPY yarn.lock package.json ./
6 |
7 | RUN yarn install
8 |
9 | COPY . .
10 |
11 | RUN yarn build
12 |
13 | FROM nginx:latest AS runtime
14 |
15 | COPY --from=build-yarn /app/dist/ /usr/share/nginx/html/
16 |
17 | EXPOSE 80
18 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2024-2025 Silent YANG
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # K5Web
2 |
3 | - 世界业余无线电日 K5Web 正式开源,添加开源许可协议。
4 | - 感谢所有 HAM 。
5 |
6 | ## 简介
7 |
8 | K5Web 用于对兼容业余无线电台 UV-K5 写频、更新固件、写入星历等。
9 |
10 | ## 讨论
11 | - QQ 群:957225277 (K5Web相关)
12 | - QQ 群:201308015 (固件相关)
13 | - Telegram Group: https://t.me/losehu
14 | - Matrix Group: https://matrix.to/#/#losehu:mozilla.org
15 |
16 | ## 功能列表
17 |
18 | - 固件版本检测
19 | - EEPROM 大小检测
20 | - 信道管理
21 | - 启动画面文字管理
22 | - MDC 本地侧音控制(仅支持我的 LTS 固件)
23 | - 备份/还原 EEPROM
24 | - 固件升级
25 | - 开机图片(LOSEHU 固件)
26 | - 字库写入(LOSEHU 固件)
27 | - 星历写入(LOSEHU 固件)
28 | - DTMF ID 设置
29 | - 收音机频道管理
30 | - MDC 联系人管理(LOSEHU 固件)
31 |
32 | ## 开发
33 | ### 安装依赖
34 | ```
35 | yarn
36 | ```
37 | ### 开发
38 | ```
39 | yarn dev
40 | ```
41 | ### 编译
42 | ```
43 | yarn build
44 | ```
45 |
46 | ## 关联项目
47 | ### 星历计算接口:
48 | https://github.com/silenty4ng/k5sat
49 |
50 | ### 我的固件:
51 | https://github.com/silenty4ng/uv-k5-firmware-chinese-lts
52 |
53 | ## 感谢项目
54 | - https://github.com/whosmatt/uvmod
55 | - https://github.com/egzumer/uvtools
56 | - https://github.com/losehu/uv-k5-firmware-custom
57 | - https://github.com/selevo/WebUsbSerialTerminal
58 | - https://github.com/fagci/uvk5-manager
59 | - https://github.com/hank9999/K5_Tools
60 | - https://github.com/kk7ds/chirp
61 |
62 | ## 开源协议
63 |
64 | ```
65 | Copyright (c) 2024 Silent YANG
66 |
67 | Permission is hereby granted, free of charge, to any person obtaining a copy
68 | of this software and associated documentation files (the "Software"), to deal
69 | in the Software without restriction, including without limitation the rights
70 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
71 | copies of the Software, and to permit persons to whom the Software is
72 | furnished to do so, subject to the following conditions:
73 |
74 | The above copyright notice and this permission notice shall be included in all
75 | copies or substantial portions of the Software.
76 |
77 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
78 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
79 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
80 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
81 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
82 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
83 | SOFTWARE.
84 | ```
85 |
86 | ## Star History
87 |
88 | [](https://star-history.com/#silenty4ng/k5web&Date)
89 |
90 | ## 饿饿饭饭
91 |
92 |
93 | TRON / TRX:TPaSnHJ2cRCQjjv7TyAFJDamb3mZSSz1At
94 |
--------------------------------------------------------------------------------
/auto-imports.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // noinspection JSUnusedGlobalSymbols
5 | // Generated by unplugin-auto-import
6 | export {}
7 | declare global {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: ['@vue/babel-plugin-jsx'],
3 | };
4 |
--------------------------------------------------------------------------------
/commitlint.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ['@commitlint/config-conventional'],
3 | };
4 |
--------------------------------------------------------------------------------
/components.d.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable */
2 | /* prettier-ignore */
3 | // @ts-nocheck
4 | // Generated by unplugin-vue-components
5 | // Read more: https://github.com/vuejs/core/pull/3399
6 | export {}
7 |
8 | declare module 'vue' {
9 | export interface GlobalComponents {
10 | Block: typeof import('./src/components/global-setting/block.vue')['default']
11 | Breadcrumb: typeof import('./src/components/breadcrumb/index.vue')['default']
12 | Chart: typeof import('./src/components/chart/index.vue')['default']
13 | Footer: typeof import('./src/components/footer/index.vue')['default']
14 | FormWrapper: typeof import('./src/components/global-setting/form-wrapper.vue')['default']
15 | GlobalSetting: typeof import('./src/components/global-setting/index.vue')['default']
16 | List: typeof import('./src/components/message-box/list.vue')['default']
17 | Menu: typeof import('./src/components/menu/index.vue')['default']
18 | MessageBox: typeof import('./src/components/message-box/index.vue')['default']
19 | Navbar: typeof import('./src/components/navbar/index.vue')['default']
20 | RouterLink: typeof import('vue-router')['RouterLink']
21 | RouterView: typeof import('vue-router')['RouterView']
22 | TabBar: typeof import('./src/components/tab-bar/index.vue')['default']
23 | TabItem: typeof import('./src/components/tab-bar/tab-item.vue')['default']
24 | TButton: typeof import('tdesign-vue-next')['Button']
25 | TCard: typeof import('tdesign-vue-next')['Card']
26 | TCheckbox: typeof import('tdesign-vue-next')['Checkbox']
27 | TCheckboxGroup: typeof import('tdesign-vue-next')['CheckboxGroup']
28 | TCol: typeof import('tdesign-vue-next')['Col']
29 | TConfigProvider: typeof import('tdesign-vue-next')['ConfigProvider']
30 | TDialog: typeof import('tdesign-vue-next')['Dialog']
31 | TDrawer: typeof import('tdesign-vue-next')['Drawer']
32 | TForm: typeof import('tdesign-vue-next')['Form']
33 | TFormItem: typeof import('tdesign-vue-next')['FormItem']
34 | TInput: typeof import('tdesign-vue-next')['Input']
35 | TLink: typeof import('tdesign-vue-next')['Link']
36 | TList: typeof import('tdesign-vue-next')['List']
37 | TListItem: typeof import('tdesign-vue-next')['ListItem']
38 | TPagination: typeof import('tdesign-vue-next')['Pagination']
39 | TRow: typeof import('tdesign-vue-next')['Row']
40 | TSpace: typeof import('tdesign-vue-next')['Space']
41 | TSwitch: typeof import('tdesign-vue-next')['Switch']
42 | TTable: typeof import('tdesign-vue-next')['Table']
43 | TTag: typeof import('tdesign-vue-next')['Tag']
44 | TTextarea: typeof import('tdesign-vue-next')['Textarea']
45 | TUpload: typeof import('tdesign-vue-next')['Upload']
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/config/plugin/arcoResolver.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * If you use the template method for development, you can use the unplugin-vue-components plugin to enable on-demand loading support.
3 | * 按需引入
4 | * https://github.com/antfu/unplugin-vue-components
5 | * https://arco.design/vue/docs/start
6 | * Although the Pro project is full of imported components, this plugin will be used by default.
7 | * 虽然Pro项目中是全量引入组件,但此插件会默认使用。
8 | */
9 | import Components from 'unplugin-vue-components/vite';
10 | import { ArcoResolver } from 'unplugin-vue-components/resolvers';
11 |
12 | export default function configArcoResolverPlugin() {
13 | const arcoResolverPlugin = Components({
14 | dirs: [], // Avoid parsing src/components. 避免解析到src/components
15 | deep: false,
16 | resolvers: [ArcoResolver()],
17 | });
18 | return arcoResolverPlugin;
19 | }
20 |
--------------------------------------------------------------------------------
/config/plugin/arcoStyleImport.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Theme import
3 | * 样式按需引入
4 | * https://github.com/arco-design/arco-plugins/blob/main/packages/plugin-vite-vue/README.md
5 | * https://arco.design/vue/docs/start
6 | */
7 | import { vitePluginForArco } from '@arco-plugins/vite-vue';
8 |
9 | export default function configArcoStyleImportPlugin() {
10 | const arcoResolverPlugin = vitePluginForArco({});
11 | return arcoResolverPlugin;
12 | }
13 |
--------------------------------------------------------------------------------
/config/plugin/compress.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Used to package and output gzip. Note that this does not work properly in Vite, the specific reason is still being investigated
3 | * gzip压缩
4 | * https://github.com/anncwb/vite-plugin-compression
5 | */
6 | import type { Plugin } from 'vite';
7 | import compressPlugin from 'vite-plugin-compression';
8 |
9 | export default function configCompressPlugin(
10 | compress: 'gzip' | 'brotli',
11 | deleteOriginFile = false
12 | ): Plugin | Plugin[] {
13 | const plugins: Plugin[] = [];
14 |
15 | if (compress === 'gzip') {
16 | plugins.push(
17 | compressPlugin({
18 | ext: '.gz',
19 | deleteOriginFile,
20 | })
21 | );
22 | }
23 |
24 | if (compress === 'brotli') {
25 | plugins.push(
26 | compressPlugin({
27 | ext: '.br',
28 | algorithm: 'brotliCompress',
29 | deleteOriginFile,
30 | })
31 | );
32 | }
33 | return plugins;
34 | }
35 |
--------------------------------------------------------------------------------
/config/plugin/imagemin.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Image resource files used to compress the output of the production environment
3 | * 图片压缩
4 | * https://github.com/anncwb/vite-plugin-imagemin
5 | */
6 | import viteImagemin from 'vite-plugin-imagemin';
7 |
8 | export default function configImageminPlugin() {
9 | const imageminPlugin = viteImagemin({
10 | gifsicle: {
11 | optimizationLevel: 7,
12 | interlaced: false,
13 | },
14 | optipng: {
15 | optimizationLevel: 7,
16 | },
17 | mozjpeg: {
18 | quality: 20,
19 | },
20 | pngquant: {
21 | quality: [0.8, 0.9],
22 | speed: 4,
23 | },
24 | svgo: {
25 | plugins: [
26 | {
27 | name: 'removeViewBox',
28 | },
29 | {
30 | name: 'removeEmptyAttrs',
31 | active: false,
32 | },
33 | ],
34 | },
35 | });
36 | return imageminPlugin;
37 | }
38 |
--------------------------------------------------------------------------------
/config/plugin/visualizer.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Generation packaging analysis
3 | * 生成打包分析
4 | */
5 | import visualizer from 'rollup-plugin-visualizer';
6 | import { isReportMode } from '../utils';
7 |
8 | export default function configVisualizerPlugin() {
9 | if (isReportMode()) {
10 | return visualizer({
11 | filename: './node_modules/.cache/visualizer/stats.html',
12 | open: true,
13 | gzipSize: true,
14 | brotliSize: true,
15 | });
16 | }
17 | return [];
18 | }
19 |
--------------------------------------------------------------------------------
/config/utils/index.ts:
--------------------------------------------------------------------------------
1 | /**
2 | * Whether to generate package preview
3 | * 是否生成打包报告
4 | */
5 | export default {};
6 |
7 | export function isReportMode(): boolean {
8 | return process.env.REPORT === 'true';
9 | }
10 |
--------------------------------------------------------------------------------
/config/vite.config.base.ts:
--------------------------------------------------------------------------------
1 | import { resolve } from 'path';
2 | import { defineConfig } from 'vite';
3 | import vue from '@vitejs/plugin-vue';
4 | import vueJsx from '@vitejs/plugin-vue-jsx';
5 | import svgLoader from 'vite-svg-loader';
6 | import configArcoStyleImportPlugin from './plugin/arcoStyleImport';
7 |
8 | import AutoImport from 'unplugin-auto-import/vite';
9 | import Components from 'unplugin-vue-components/vite';
10 | import { TDesignResolver } from 'unplugin-vue-components/resolvers';
11 | import { ArcoResolver } from 'unplugin-vue-components/resolvers';
12 |
13 | import htmlPlugin from "vite-plugin-html-config";
14 |
15 | export default defineConfig({
16 | base: './',
17 | plugins: [
18 | vue(),
19 | vueJsx(),
20 | svgLoader({ svgoConfig: {} }),
21 | configArcoStyleImportPlugin(),
22 | AutoImport({
23 | resolvers: [TDesignResolver({
24 | library: 'vue-next'
25 | })],
26 | }),
27 | Components({
28 | resolvers: [TDesignResolver({
29 | library: 'vue-next'
30 | }), ArcoResolver()],
31 | }),
32 | htmlPlugin({
33 | metas: [
34 | {
35 | name: "builtTime",
36 | content: Math.ceil(parseInt(new Date().toISOString().replace(/[.:TZ-]/g, '')) / 1000).toString()
37 | },
38 | ]
39 | })
40 | ],
41 | resolve: {
42 | alias: [
43 | {
44 | find: '@',
45 | replacement: resolve(__dirname, '../src'),
46 | },
47 | {
48 | find: 'assets',
49 | replacement: resolve(__dirname, '../src/assets'),
50 | },
51 | {
52 | find: 'vue-i18n',
53 | replacement: 'vue-i18n/dist/vue-i18n.cjs.js', // Resolve the i18n warning issue
54 | },
55 | {
56 | find: 'vue',
57 | replacement: 'vue/dist/vue.esm-bundler.js', // compile template
58 | },
59 | ],
60 | extensions: ['.ts', '.js', '.css'],
61 | },
62 | define: {
63 | 'process.env': {},
64 | },
65 | css: {
66 | preprocessorOptions: {
67 | less: {
68 | modifyVars: {
69 | hack: `true; @import (reference) "${resolve(
70 | 'src/assets/style/breakpoint.less'
71 | )}";`,
72 | },
73 | javascriptEnabled: true,
74 | },
75 | },
76 | postcss: {
77 | plugins: [
78 | require('postcss-px-to-viewport')({
79 | viewportWidth: 2560, // 视口宽度,对应设计稿宽度
80 | viewporHeight: 1440, // 视口高度,对应设计稿高度
81 | unitPrecision: 3, // 指定px转换之后的小数位数
82 | viewportUnit: 'vw', // 转换的单位
83 | fontViewportUnit: 'vw', // 字体使用的单位
84 | replace: false, // 是否直接更换属性值,而不添加备用属性
85 | selectorBlackList: ['.ignore', '.hairlines', '.arco', '.layout', '.nav-btn'], // 指定不转换的类
86 | exclude: /(\/|\\)(node_modules)(\/|\\)/, //禁止更改第三方UI框架样式
87 | minPixelValue: 15, // 小于或等于1px不转换
88 | mediaQuery: true, // 允许在媒体查询中转换
89 | })
90 | ]
91 | }
92 | },
93 | });
94 |
--------------------------------------------------------------------------------
/config/vite.config.dev.ts:
--------------------------------------------------------------------------------
1 | import { mergeConfig } from 'vite';
2 | import baseConfig from './vite.config.base';
3 |
4 | export default mergeConfig(
5 | {
6 | mode: 'development',
7 | server: {
8 | host: "0.0.0.0",
9 | open: false,
10 | fs: {
11 | strict: true,
12 | },
13 | },
14 | plugins: [],
15 | },
16 | baseConfig
17 | );
18 |
--------------------------------------------------------------------------------
/config/vite.config.prod.ts:
--------------------------------------------------------------------------------
1 | import { mergeConfig } from 'vite';
2 | import baseConfig from './vite.config.base';
3 | import configCompressPlugin from './plugin/compress';
4 | import configVisualizerPlugin from './plugin/visualizer';
5 | import configImageminPlugin from './plugin/imagemin';
6 |
7 | export default mergeConfig(
8 | {
9 | mode: 'production',
10 | plugins: [
11 | configCompressPlugin('gzip'),
12 | configVisualizerPlugin(),
13 | configImageminPlugin(),
14 | ],
15 | build: {
16 | rollupOptions: {
17 | output: {
18 | manualChunks(id) {
19 | if (id.includes("node_modules")) {
20 | // 让每个插件都打包成独立的文件
21 | return id .toString() .split("node_modules/")[1] .split("/")[0] .toString();
22 | }
23 | }
24 | },
25 | },
26 | chunkSizeWarningLimit: 2000,
27 | },
28 | },
29 | baseConfig
30 | );
31 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.5"
2 | services:
3 | k5web:
4 | image: ghcr.io/silenty4ng/k5web:latest
5 | container_name: k5web
6 | network_mode: "bridge"
7 | ports:
8 | - "5173:80"
9 | environment:
10 | - TZ=Asia/Shanghai
11 | restart: always
12 |
--------------------------------------------------------------------------------
/forge.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | packagerConfig: {
3 | asar: true,
4 | },
5 | rebuildConfig: {},
6 | makers: [
7 | {
8 | name: '@electron-forge/maker-squirrel',
9 | config: {},
10 | },
11 | {
12 | name: '@electron-forge/maker-zip',
13 | platforms: ['darwin'],
14 | },
15 | {
16 | name: '@electron-forge/maker-deb',
17 | config: {},
18 | },
19 | {
20 | name: '@electron-forge/maker-rpm',
21 | config: {},
22 | },
23 | {
24 | name: '@rabbitholesyndrome/electron-forge-maker-portable',
25 | config: {
26 | appId: 'com.vicicode.k5web',
27 | productName: 'K5Web',
28 | icon: 'icon.ico'
29 | },
30 | portable: {
31 | artifactName: 'k5web.exe'
32 | }
33 | }
34 | ],
35 | plugins: [
36 | {
37 | name: '@electron-forge/plugin-auto-unpack-natives',
38 | config: {},
39 | },
40 | ],
41 | };
42 |
--------------------------------------------------------------------------------
/icon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/icon.ico
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | K5Web
9 |
10 |
11 |
28 |
124 |
125 |
126 |
127 |
128 |
135 |
136 |
149 |
150 |
151 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow, dialog } = require('electron/main');
2 |
3 | function createWindow() {
4 | const mainWindow = new BrowserWindow({
5 | width: 1280,
6 | height: 760,
7 | autoHideMenuBar: true
8 | });
9 |
10 | mainWindow.webContents.session.on('select-serial-port', async (event, portList, webContents, callback) => {
11 | // Add listeners to handle ports being added or removed before the callback for `select-serial-port` is called.
12 | mainWindow.webContents.session.on('serial-port-added', (event, port) => {
13 | console.log('serial-port-added FIRED WITH', port);
14 | // Optionally update portList to add the new port
15 | });
16 |
17 | mainWindow.webContents.session.on('serial-port-removed', (event, port) => {
18 | console.log('serial-port-removed FIRED WITH', port);
19 | // Optionally update portList to remove the port
20 | });
21 |
22 | event.preventDefault();
23 |
24 | if (portList && portList.length > 0) {
25 | // Only keep the last 5 ports
26 | const lastFivePorts = portList.slice(-5);
27 |
28 | // Prepare options for dialog box
29 | const options = {
30 | type: 'question',
31 | buttons: lastFivePorts.map(port => port.portName), // Display port names as choices
32 | title: 'Select Serial Port',
33 | message: 'Please select a serial port to use:'
34 | };
35 |
36 | const result = await dialog.showMessageBox(mainWindow, options);
37 | if (result.response >= 0) {
38 | callback(lastFivePorts[result.response].portId); // Callback with the selected port ID
39 | } else {
40 | callback(''); // No port selected
41 | }
42 | } else {
43 | callback(''); // No ports available
44 | }
45 | });
46 |
47 | mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
48 | if (permission === 'serial' && details.securityOrigin === 'file:///') {
49 | return true;
50 | }
51 |
52 | return false;
53 | });
54 |
55 | mainWindow.webContents.session.setDevicePermissionHandler((details) => {
56 | if (details.deviceType === 'serial' && details.origin === 'file://') {
57 | return true;
58 | }
59 |
60 | return false;
61 | });
62 |
63 | mainWindow.loadFile('./dist/index.html');
64 |
65 | // mainWindow.webContents.openDevTools();
66 | }
67 |
68 | app.whenReady().then(() => {
69 | createWindow();
70 |
71 | app.on('activate', function () {
72 | if (BrowserWindow.getAllWindows().length === 0) createWindow();
73 | });
74 | });
75 |
76 | app.on('window-all-closed', function () {
77 | if (process.platform !== 'darwin') app.quit();
78 | });
79 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "k5web",
3 | "description": "K5Web",
4 | "version": "1.0.0",
5 | "private": true,
6 | "author": "Silent YANG",
7 | "license": "MIT",
8 | "main": "main.js",
9 | "scripts": {
10 | "dev": "vite --config ./config/vite.config.dev.ts",
11 | "build": "vite build --config ./config/vite.config.prod.ts",
12 | "report": "cross-env REPORT=true npm run build",
13 | "preview": "npm run build && vite preview --host",
14 | "start": "electron-forge start",
15 | "package": "electron-forge package",
16 | "make": "electron-forge make"
17 | },
18 | "dependencies": {
19 | "@arco-design/web-vue": "^2.44.7",
20 | "@vueuse/core": "^9.3.0",
21 | "@zxing/text-encoding": "^0.9.0",
22 | "aegis-web-sdk": "^1.39.1",
23 | "axios": "^0.24.0",
24 | "chinese-s2t": "^1.0.0",
25 | "dayjs": "^1.11.5",
26 | "dompurify": "^3.1.7",
27 | "echarts": "^5.4.0",
28 | "electron-squirrel-startup": "^1.0.0",
29 | "lodash": "^4.17.21",
30 | "marked": "^14.1.2",
31 | "mitt": "^3.0.0",
32 | "nprogress": "^0.2.0",
33 | "pinia": "^2.0.23",
34 | "qrcode": "^1.5.3",
35 | "query-string": "^8.0.3",
36 | "sortablejs": "^1.15.0",
37 | "tdesign-vue-next": "^1.9.4",
38 | "uuid": "^9.0.1",
39 | "vue": "^3.2.40",
40 | "vue-echarts": "^6.2.3",
41 | "vue-i18n": "^9.2.2",
42 | "vue-matomo": "^4.2.0",
43 | "vue-router": "^4.0.14",
44 | "xlsx": "^0.18.5"
45 | },
46 | "devDependencies": {
47 | "@arco-plugins/vite-vue": "^1.4.5",
48 | "@commitlint/cli": "^17.1.2",
49 | "@commitlint/config-conventional": "^17.1.0",
50 | "@electron-forge/cli": "^7.2.0",
51 | "@electron-forge/maker-deb": "^7.2.0",
52 | "@electron-forge/maker-rpm": "^7.2.0",
53 | "@electron-forge/maker-squirrel": "^7.2.0",
54 | "@electron-forge/maker-zip": "^7.2.0",
55 | "@electron-forge/plugin-auto-unpack-natives": "^7.2.0",
56 | "@rabbitholesyndrome/electron-forge-maker-portable": "^0.2.0",
57 | "@types/dompurify": "^3.0.5",
58 | "@types/lodash": "^4.14.186",
59 | "@types/mockjs": "^1.0.7",
60 | "@types/nprogress": "^0.2.0",
61 | "@types/sortablejs": "^1.15.0",
62 | "@typescript-eslint/eslint-plugin": "^5.40.0",
63 | "@typescript-eslint/parser": "^5.40.0",
64 | "@vitejs/plugin-vue": "^3.1.2",
65 | "@vitejs/plugin-vue-jsx": "^2.0.1",
66 | "@vue/babel-plugin-jsx": "^1.1.1",
67 | "consola": "^2.15.3",
68 | "cross-env": "^7.0.3",
69 | "electron": "^28.2.1",
70 | "less": "^4.1.3",
71 | "mockjs": "^1.1.0",
72 | "postcss-html": "^1.5.0",
73 | "postcss-px-to-viewport": "^1.1.1",
74 | "prettier": "^2.7.1",
75 | "rollup": "^3.9.1",
76 | "rollup-plugin-visualizer": "^5.8.2",
77 | "typescript": "^4.8.4",
78 | "unplugin-auto-import": "^0.17.5",
79 | "unplugin-vue-components": "^0.26.0",
80 | "vite": "^3.2.5",
81 | "vite-plugin-compression": "^0.5.1",
82 | "vite-plugin-html-config": "^1.0.11",
83 | "vite-plugin-imagemin": "^0.6.1",
84 | "vite-svg-loader": "^3.6.0",
85 | "vue-tsc": "^1.0.14"
86 | },
87 | "engines": {
88 | "node": ">=14.0.0"
89 | },
90 | "resolutions": {
91 | "bin-wrapper": "npm:bin-wrapper-china",
92 | "rollup": "^2.56.3",
93 | "gifsicle": "5.2.0"
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/public/1714006925783.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/1714006925783.jpg
--------------------------------------------------------------------------------
/public/1722745910257.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/1722745910257.jpg
--------------------------------------------------------------------------------
/public/CNAME:
--------------------------------------------------------------------------------
1 | k5.vicicode.com
--------------------------------------------------------------------------------
/public/K5Channel.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/K5Channel.xlsx
--------------------------------------------------------------------------------
/public/K5Channel_EN.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/K5Channel_EN.xlsx
--------------------------------------------------------------------------------
/public/LOSEHU117P6.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/LOSEHU117P6.bin
--------------------------------------------------------------------------------
/public/LOSEHU117P6K.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/LOSEHU117P6K.bin
--------------------------------------------------------------------------------
/public/LOSEHU126.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/LOSEHU126.bin
--------------------------------------------------------------------------------
/public/LOSEHU126H.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/LOSEHU126H.bin
--------------------------------------------------------------------------------
/public/LOSEHU126K.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/LOSEHU126K.bin
--------------------------------------------------------------------------------
/public/L_BL001.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/L_BL001.bin
--------------------------------------------------------------------------------
/public/O1CN019Pat6v1ZuNgxL8CRt_!!6000000003254-0-tps-800-450.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/O1CN019Pat6v1ZuNgxL8CRt_!!6000000003254-0-tps-800-450.jpg
--------------------------------------------------------------------------------
/public/adimg1c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/adimg1c.png
--------------------------------------------------------------------------------
/public/adimg2c.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/adimg2c.png
--------------------------------------------------------------------------------
/public/gy.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/gy.png
--------------------------------------------------------------------------------
/public/img1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/img1.png
--------------------------------------------------------------------------------
/public/img2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/img2.png
--------------------------------------------------------------------------------
/public/img3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/img3.png
--------------------------------------------------------------------------------
/public/img4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/img4.png
--------------------------------------------------------------------------------
/public/img5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/img5.png
--------------------------------------------------------------------------------
/public/img6.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/img6.png
--------------------------------------------------------------------------------
/public/jjgg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/jjgg.jpg
--------------------------------------------------------------------------------
/public/k5web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/k5web.png
--------------------------------------------------------------------------------
/public/mm_facetoface_collect_qrcode_1714392837792.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/mm_facetoface_collect_qrcode_1714392837792.png
--------------------------------------------------------------------------------
/public/new_font_h.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/new_font_h.bin
--------------------------------------------------------------------------------
/public/new_font_k.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/new_font_k.bin
--------------------------------------------------------------------------------
/public/new_font_k_f.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/new_font_k_f.bin
--------------------------------------------------------------------------------
/public/old_font.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/old_font.bin
--------------------------------------------------------------------------------
/public/pinyin.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/pinyin.bin
--------------------------------------------------------------------------------
/public/pinyin_plus.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/pinyin_plus.bin
--------------------------------------------------------------------------------
/public/qrcode_1714310463601.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/qrcode_1714310463601.jpg
--------------------------------------------------------------------------------
/public/rhino-design-800x450.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/rhino-design-800x450.png
--------------------------------------------------------------------------------
/public/sms_test.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/sms_test.bin
--------------------------------------------------------------------------------
/public/ssb.bin:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/public/ssb.bin
--------------------------------------------------------------------------------
/src/App.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
如需浏览,请长按网址复制后使用浏览器访问
5 |
{{ link }}
6 |
7 |
{{ ua }}
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
98 |
--------------------------------------------------------------------------------
/src/api/interceptor.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import type { AxiosRequestConfig, AxiosResponse } from 'axios';
3 | import { Message, Modal } from '@arco-design/web-vue';
4 | import { useUserStore } from '@/store';
5 | import { getToken } from '@/utils/auth';
6 |
7 | export interface HttpResponse {
8 | status: number;
9 | msg: string;
10 | code: number;
11 | data: T;
12 | }
13 |
14 | if (import.meta.env.VITE_API_BASE_URL) {
15 | axios.defaults.baseURL = import.meta.env.VITE_API_BASE_URL;
16 | }
17 |
18 | axios.interceptors.request.use(
19 | (config: AxiosRequestConfig) => {
20 | // let each request carry token
21 | // this example using the JWT token
22 | // Authorization is a custom headers key
23 | // please modify it according to the actual situation
24 | const token = getToken();
25 | if (token) {
26 | if (!config.headers) {
27 | config.headers = {};
28 | }
29 | config.headers.Authorization = `Bearer ${token}`;
30 | }
31 | return config;
32 | },
33 | (error) => {
34 | // do something
35 | return Promise.reject(error);
36 | }
37 | );
38 | // add response interceptors
39 | axios.interceptors.response.use(
40 | (response: AxiosResponse) => {
41 | const res = response.data;
42 | // if the custom code is not 20000, it is judged as an error.
43 | if (res.code !== 200 && res.code !== 1) {
44 | Message.error({
45 | content: res.msg || 'Error',
46 | duration: 5 * 1000,
47 | });
48 | // 50008: Illegal token; 50012: Other clients logged in; 50014: Token expired;
49 | if (
50 | [50008, 50012, 50014].includes(res.code) &&
51 | response.config.url !== '/api/user/info'
52 | ) {
53 | Modal.error({
54 | title: 'Confirm logout',
55 | content:
56 | 'You have been logged out, you can cancel to stay on this page, or log in again',
57 | okText: 'Re-Login',
58 | async onOk() {
59 | const userStore = useUserStore();
60 |
61 | await userStore.logout();
62 | window.location.reload();
63 | },
64 | });
65 | }
66 | return Promise.reject(new Error(res.msg || 'Error'));
67 | }
68 | return res;
69 | },
70 | (error) => {
71 | Message.error({
72 | content: error.msg || 'Request Error',
73 | duration: 5 * 1000,
74 | });
75 | return Promise.reject(error);
76 | }
77 | );
78 |
--------------------------------------------------------------------------------
/src/api/user.ts:
--------------------------------------------------------------------------------
1 | import axios from 'axios';
2 | import type { RouteRecordNormalized } from 'vue-router';
3 | import { UserState } from '@/store/modules/user/types';
4 |
5 | export interface LoginData {
6 | username: string;
7 | password: string;
8 | }
9 |
10 | export interface LoginRes {
11 | token: string;
12 | }
13 | export function login(data: LoginData) {
14 | return axios.post('/api/user/login', data);
15 | }
16 |
17 | export function logout() {
18 | return axios.post('/api/user/logout');
19 | }
20 |
21 | export function getUserInfo() {
22 | return axios.post('/api/user/info');
23 | }
24 |
25 | export function getMenuList() {
26 | return axios.post('/api/user/menu');
27 | }
28 |
--------------------------------------------------------------------------------
/src/assets/images/login-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/src/assets/images/login-banner.png
--------------------------------------------------------------------------------
/src/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
13 |
--------------------------------------------------------------------------------
/src/assets/style/breakpoint.less:
--------------------------------------------------------------------------------
1 | // ==============breakpoint============
2 |
3 | // Extra small screen / phone
4 | @screen-xs: 480px;
5 |
6 | // Small screen / tablet
7 | @screen-sm: 576px;
8 |
9 | // Medium screen / desktop
10 | @screen-md: 768px;
11 |
12 | // Large screen / wide desktop
13 | @screen-lg: 992px;
14 |
15 | // Extra large screen / full hd
16 | @screen-xl: 1200px;
17 |
18 | // Extra extra large screen / large desktop
19 | @screen-xxl: 1600px;
20 |
--------------------------------------------------------------------------------
/src/assets/style/global.less:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 |
5 | html,
6 | body {
7 | width: 100%;
8 | height: 100%;
9 | margin: 0;
10 | padding: 0;
11 | font-size: 14px;
12 | background-color: var(--color-bg-1);
13 | -moz-osx-font-smoothing: grayscale;
14 | -webkit-font-smoothing: antialiased;
15 | }
16 |
17 | .echarts-tooltip-diy {
18 | background: linear-gradient(
19 | 304.17deg,
20 | rgba(253, 254, 255, 0.6) -6.04%,
21 | rgba(244, 247, 252, 0.6) 85.2%
22 | ) !important;
23 | border: none !important;
24 | backdrop-filter: blur(10px) !important;
25 | /* Note: backdrop-filter has minimal browser support */
26 |
27 | border-radius: 6px !important;
28 | .content-panel {
29 | display: flex;
30 | justify-content: space-between;
31 | padding: 0 9px;
32 | background: rgba(255, 255, 255, 0.8);
33 | width: 164px;
34 | height: 32px;
35 | line-height: 32px;
36 | box-shadow: 6px 0px 20px rgba(34, 87, 188, 0.1);
37 | border-radius: 4px;
38 | margin-bottom: 4px;
39 | }
40 | .tooltip-title {
41 | margin: 0 0 10px 0;
42 | }
43 | p {
44 | margin: 0;
45 | }
46 | .tooltip-title,
47 | .tooltip-value {
48 | font-size: 13px;
49 | line-height: 15px;
50 | display: flex;
51 | align-items: center;
52 | text-align: right;
53 | color: #1d2129;
54 | font-weight: bold;
55 | }
56 | .tooltip-item-icon {
57 | display: inline-block;
58 | margin-right: 8px;
59 | width: 10px;
60 | height: 10px;
61 | border-radius: 50%;
62 | }
63 | }
64 |
65 | .general-card {
66 | border-radius: 4px;
67 | border: none;
68 | & > .arco-card-header {
69 | height: auto;
70 | padding: 20px;
71 | border: none;
72 | }
73 | & > .arco-card-body {
74 | padding: 0 20px 20px 20px;
75 | }
76 | }
77 |
78 | .split-line {
79 | border-color: rgb(var(--gray-2));
80 | }
81 |
82 | .arco-table-cell {
83 | .circle {
84 | display: inline-block;
85 | margin-right: 4px;
86 | width: 6px;
87 | height: 6px;
88 | border-radius: 50%;
89 | background-color: rgb(var(--blue-6));
90 | &.pass {
91 | background-color: rgb(var(--green-6));
92 | }
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/src/components/breadcrumb/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{ $t(item) }}
8 |
9 |
10 |
11 |
12 |
24 |
25 |
36 |
--------------------------------------------------------------------------------
/src/components/chart/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/components/footer/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
11 |
21 |
22 |
32 |
--------------------------------------------------------------------------------
/src/components/global-setting/block.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{ title }}
4 |
5 | {{ $t(option.name) }}
6 |
12 |
13 |
14 |
15 |
16 |
61 |
62 |
80 |
--------------------------------------------------------------------------------
/src/components/global-setting/form-wrapper.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
15 |
16 |
17 |
40 |
--------------------------------------------------------------------------------
/src/components/global-setting/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
9 |
18 | {{ $t('settings.title') }}
19 |
20 |
21 | {{ $t('settings.alertContent') }}
22 |
23 |
24 |
25 |
86 |
87 |
99 |
--------------------------------------------------------------------------------
/src/components/index.ts:
--------------------------------------------------------------------------------
1 | import { App } from 'vue';
2 | import { use } from 'echarts/core';
3 | import { CanvasRenderer } from 'echarts/renderers';
4 | import { BarChart, LineChart, PieChart, RadarChart } from 'echarts/charts';
5 | import {
6 | GridComponent,
7 | TooltipComponent,
8 | LegendComponent,
9 | DataZoomComponent,
10 | GraphicComponent,
11 | } from 'echarts/components';
12 | import Chart from './chart/index.vue';
13 | import Breadcrumb from './breadcrumb/index.vue';
14 |
15 | // Manually introduce ECharts modules to reduce packing size
16 |
17 | use([
18 | CanvasRenderer,
19 | BarChart,
20 | LineChart,
21 | PieChart,
22 | RadarChart,
23 | GridComponent,
24 | TooltipComponent,
25 | LegendComponent,
26 | DataZoomComponent,
27 | GraphicComponent,
28 | ]);
29 |
30 | export default {
31 | install(Vue: App) {
32 | Vue.component('Chart', Chart);
33 | Vue.component('Breadcrumb', Breadcrumb);
34 | },
35 | };
36 |
--------------------------------------------------------------------------------
/src/components/menu/index.vue:
--------------------------------------------------------------------------------
1 |
147 |
148 |
161 |
--------------------------------------------------------------------------------
/src/components/menu/use-menu-tree.ts:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue';
2 | import { RouteRecordRaw, RouteRecordNormalized } from 'vue-router';
3 | import usePermission from '@/hooks/permission';
4 | import { useAppStore } from '@/store';
5 | import appClientMenus from '@/router/app-menus';
6 | import { cloneDeep } from 'lodash';
7 |
8 | export default function useMenuTree() {
9 | const permission = usePermission();
10 | const appStore = useAppStore();
11 | const appRoute = computed(() => {
12 | if (appStore.menuFromServer) {
13 | return appStore.appAsyncMenus;
14 | }
15 | return appClientMenus;
16 | });
17 | const menuTree = computed(() => {
18 | const copyRouter = cloneDeep(appRoute.value) as RouteRecordNormalized[];
19 | copyRouter.sort((a: RouteRecordNormalized, b: RouteRecordNormalized) => {
20 | return (a.meta.order || 0) - (b.meta.order || 0);
21 | });
22 | function travel(_routes: RouteRecordRaw[], layer: number) {
23 | if (!_routes) return null;
24 |
25 | const collector: any = _routes.map((element) => {
26 | // no access
27 | if (!permission.accessRouter(element)) {
28 | return null;
29 | }
30 |
31 | // leaf node
32 | if (element.meta?.hideChildrenInMenu || !element.children) {
33 | element.children = [];
34 | return element;
35 | }
36 |
37 | // route filter hideInMenu true
38 | element.children = element.children.filter(
39 | (x) => x.meta?.hideInMenu !== true
40 | );
41 |
42 | // Associated child node
43 | const subItem = travel(element.children, layer + 1);
44 |
45 | if (subItem.length) {
46 | element.children = subItem;
47 | return element;
48 | }
49 | // the else logic
50 | if (layer > 1) {
51 | element.children = subItem;
52 | return element;
53 | }
54 |
55 | if (element.meta?.hideInMenu === false) {
56 | return element;
57 | }
58 |
59 | return null;
60 | });
61 | return collector.filter(Boolean);
62 | }
63 | return travel(copyRouter, 0);
64 | });
65 |
66 | return {
67 | menuTree,
68 | };
69 | }
70 |
--------------------------------------------------------------------------------
/src/components/tab-bar/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 |
61 |
62 |
102 |
--------------------------------------------------------------------------------
/src/components/tab-bar/readme.md:
--------------------------------------------------------------------------------
1 | ## 组件说明
2 |
3 | 该组件非官方最终设计规范,以单独组件存在。
4 |
5 | 同时仅仅提供最基本的功能,后续进行优化及更改。
6 |
7 |
8 | ## Component description
9 |
10 | The component unofficial final design specification exists as a separate component.
11 |
12 | At the same time, only the most basic functions are provided, and subsequent optimizations and changes will be made.
--------------------------------------------------------------------------------
/src/components/tab-bar/tab-item.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
12 |
13 | {{ $t(itemData.title) }}
14 |
15 |
19 |
20 |
21 |
22 |
23 |
24 |
25 | 重新加载
26 |
27 |
32 |
33 | 关闭当前标签页
34 |
35 |
36 |
37 | 关闭左侧标签页
38 |
39 |
44 |
45 | 关闭右侧标签页
46 |
47 |
48 |
49 | 关闭其它标签页
50 |
51 |
52 |
53 | 关闭全部标签页
54 |
55 |
56 |
57 |
58 |
59 |
169 |
170 |
201 |
--------------------------------------------------------------------------------
/src/config/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "theme": "light",
3 | "colorWeak": false,
4 | "navbar": true,
5 | "menu": true,
6 | "topMenu": false,
7 | "hideMenu": false,
8 | "menuCollapse": false,
9 | "footer": true,
10 | "themeColor": "#165DFF",
11 | "menuWidth": 220,
12 | "globalSettings": false,
13 | "device": "desktop",
14 | "tabBar": false,
15 | "menuFromServer": false,
16 | "serverMenu": [],
17 | "connectState": false,
18 | "firmwareVersion": "",
19 | "connectPort": null,
20 | "configuration": null
21 | }
22 |
--------------------------------------------------------------------------------
/src/directive/index.ts:
--------------------------------------------------------------------------------
1 | import { App } from 'vue';
2 | import permission from './permission';
3 |
4 | export default {
5 | install(Vue: App) {
6 | Vue.directive('permission', permission);
7 | },
8 | };
9 |
--------------------------------------------------------------------------------
/src/directive/permission/index.ts:
--------------------------------------------------------------------------------
1 | import { DirectiveBinding } from 'vue';
2 | import { useUserStore } from '@/store';
3 |
4 | function checkPermission(el: HTMLElement, binding: DirectiveBinding) {
5 | const { value } = binding;
6 | const userStore = useUserStore();
7 | const { role } = userStore;
8 |
9 | if (Array.isArray(value)) {
10 | if (value.length > 0) {
11 | const permissionValues = value;
12 |
13 | const hasPermission = permissionValues.includes(role);
14 | if (!hasPermission && el.parentNode) {
15 | el.parentNode.removeChild(el);
16 | }
17 | }
18 | } else {
19 | throw new Error(`need roles! Like v-permission="['admin','user']"`);
20 | }
21 | }
22 |
23 | export default {
24 | mounted(el: HTMLElement, binding: DirectiveBinding) {
25 | checkPermission(el, binding);
26 | },
27 | updated(el: HTMLElement, binding: DirectiveBinding) {
28 | checkPermission(el, binding);
29 | },
30 | };
31 |
--------------------------------------------------------------------------------
/src/drivers/losehu117.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu 117 历史版本",
3 | "uart": "official",
4 | "charset": "official"
5 | }
--------------------------------------------------------------------------------
/src/drivers/losehu117k.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu 117 历史版本扩容版",
3 | "uart": "losehu",
4 | "charset": "losehu",
5 | "K": true
6 | }
--------------------------------------------------------------------------------
/src/drivers/losehu118.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu 118+",
3 | "uart": "official",
4 | "charset": "official"
5 | }
--------------------------------------------------------------------------------
/src/drivers/losehu118h.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu 118+ 2Mbit 扩容版",
3 | "uart": "losehu",
4 | "charset": "gb2312",
5 | "H": true,
6 | "sat": true
7 | }
8 |
--------------------------------------------------------------------------------
/src/drivers/losehu118k.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu 118+ 扩容版",
3 | "uart": "losehu",
4 | "charset": "gb2312",
5 | "K": true
6 | }
--------------------------------------------------------------------------------
/src/drivers/losehu120k.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu 120+ 扩容版",
3 | "uart": "losehu",
4 | "charset": "gb2312",
5 | "K": true,
6 | "sat": true
7 | }
--------------------------------------------------------------------------------
/src/drivers/losehu124h.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu 124+ 2Mbit 扩容版",
3 | "uart": "losehu",
4 | "charset": "gb2312",
5 | "H": true,
6 | "sat": true,
7 | "newpinyin": true
8 | }
9 |
--------------------------------------------------------------------------------
/src/drivers/losehubl.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu 引导程序",
3 | "uart": "losehu",
4 | "charset": "gb2312",
5 | "H": true,
6 | "sat": true,
7 | "newpinyin": true
8 | }
9 |
--------------------------------------------------------------------------------
/src/drivers/losehud.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu Doppler",
3 | "uart": "losehu",
4 | "charset": "gb2312",
5 | "H": true,
6 | "sat": true,
7 | "sat2": true,
8 | "newpinyin": true
9 | }
10 |
--------------------------------------------------------------------------------
/src/drivers/lts.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu Patch LTS(BD8DFN)",
3 | "uart": "official",
4 | "charset": "official",
5 | "localmdc": true
6 | }
--------------------------------------------------------------------------------
/src/drivers/ltsk.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "LoseHu Patch LTS(BD8DFN)扩容版",
3 | "uart": "losehu",
4 | "charset": "losehu",
5 | "K": true,
6 | "localmdc": true,
7 | "fm30": true
8 | }
--------------------------------------------------------------------------------
/src/drivers/todo.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Official"
3 | }
--------------------------------------------------------------------------------
/src/env.d.ts:
--------------------------------------------------------------------------------
1 | ///
2 |
3 | declare module '*.vue' {
4 | import { DefineComponent } from 'vue';
5 | // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
6 | const component: DefineComponent<{}, {}, any>;
7 | export default component;
8 | }
9 | interface ImportMetaEnv {
10 | readonly VITE_API_BASE_URL: string;
11 | }
12 |
--------------------------------------------------------------------------------
/src/hooks/chart-option.ts:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue';
2 | import { EChartsOption } from 'echarts';
3 | import { useAppStore } from '@/store';
4 |
5 | // for code hints
6 | // import { SeriesOption } from 'echarts';
7 | // Because there are so many configuration items, this provides a relatively convenient code hint.
8 | // When using vue, pay attention to the reactive issues. It is necessary to ensure that corresponding functions can be triggered, TypeScript does not report errors, and code writing is convenient.
9 | interface optionsFn {
10 | (isDark: boolean): EChartsOption;
11 | }
12 |
13 | export default function useChartOption(sourceOption: optionsFn) {
14 | const appStore = useAppStore();
15 | const isDark = computed(() => {
16 | return appStore.theme === 'dark';
17 | });
18 | // echarts support https://echarts.apache.org/zh/theme-builder.html
19 | // It's not used here
20 | // TODO echarts themes
21 | const chartOption = computed(() => {
22 | return sourceOption(isDark.value);
23 | });
24 | return {
25 | chartOption,
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/src/hooks/loading.ts:
--------------------------------------------------------------------------------
1 | import { ref } from 'vue';
2 |
3 | export default function useLoading(initValue = false) {
4 | const loading = ref(initValue);
5 | const setLoading = (value: boolean) => {
6 | loading.value = value;
7 | };
8 | const toggle = () => {
9 | loading.value = !loading.value;
10 | };
11 | return {
12 | loading,
13 | setLoading,
14 | toggle,
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/src/hooks/locale.ts:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue';
2 | import { useI18n } from 'vue-i18n';
3 | import { Message } from '@arco-design/web-vue';
4 |
5 | export default function useLocale() {
6 | const i18 = useI18n();
7 | const currentLocale = computed(() => {
8 | return i18.locale.value;
9 | });
10 | const changeLocale = (value: string) => {
11 | if (i18.locale.value === value) {
12 | return;
13 | }
14 | i18.locale.value = value;
15 | localStorage.setItem('arco-locale', value);
16 | Message.success(i18.t('navbar.action.locale'));
17 | };
18 | return {
19 | currentLocale,
20 | changeLocale,
21 | };
22 | }
23 |
--------------------------------------------------------------------------------
/src/hooks/permission.ts:
--------------------------------------------------------------------------------
1 | import { RouteLocationNormalized, RouteRecordRaw } from 'vue-router';
2 | import { useUserStore } from '@/store';
3 |
4 | export default function usePermission() {
5 | const userStore = useUserStore();
6 | return {
7 | accessRouter(route: RouteLocationNormalized | RouteRecordRaw) {
8 | return (
9 | !route.meta?.requiresAuth ||
10 | !route.meta?.roles ||
11 | route.meta?.roles?.includes('*') ||
12 | route.meta?.roles?.includes(userStore.role)
13 | );
14 | },
15 | findFirstPermissionRoute(_routers: any, role = 'admin') {
16 | const cloneRouters = [..._routers];
17 | while (cloneRouters.length) {
18 | const firstElement = cloneRouters.shift();
19 | if (
20 | firstElement?.meta?.roles?.find((el: string[]) => {
21 | return el.includes('*') || el.includes(role);
22 | })
23 | )
24 | return { name: firstElement.name };
25 | if (firstElement?.children) {
26 | cloneRouters.push(...firstElement.children);
27 | }
28 | }
29 | return null;
30 | },
31 | // You can add any rules you want
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/src/hooks/request.ts:
--------------------------------------------------------------------------------
1 | import { ref, UnwrapRef } from 'vue';
2 | import { AxiosResponse } from 'axios';
3 | import { HttpResponse } from '@/api/interceptor';
4 | import useLoading from './loading';
5 |
6 | // use to fetch list
7 | // Don't use async function. It doesn't work in async function.
8 | // Use the bind function to add parameters
9 | // example: useRequest(api.bind(null, {}))
10 |
11 | export default function useRequest(
12 | api: () => Promise>,
13 | defaultValue = [] as unknown as T,
14 | isLoading = true
15 | ) {
16 | const { loading, setLoading } = useLoading(isLoading);
17 | const response = ref(defaultValue);
18 | api()
19 | .then((res) => {
20 | response.value = res.data as unknown as UnwrapRef;
21 | })
22 | .finally(() => {
23 | setLoading(false);
24 | });
25 | return { loading, response };
26 | }
27 |
--------------------------------------------------------------------------------
/src/hooks/responsive.ts:
--------------------------------------------------------------------------------
1 | import { onMounted, onBeforeMount, onBeforeUnmount } from 'vue';
2 | import { useDebounceFn } from '@vueuse/core';
3 | import { useAppStore } from '@/store';
4 | import { addEventListen, removeEventListen } from '@/utils/event';
5 |
6 | const WIDTH = 992; // https://arco.design/vue/component/grid#responsivevalue
7 |
8 | function queryDevice() {
9 | const rect = document.body.getBoundingClientRect();
10 | return rect.width - 1 < WIDTH;
11 | }
12 |
13 | export default function useResponsive(immediate?: boolean) {
14 | const appStore = useAppStore();
15 | function resizeHandler() {
16 | if (!document.hidden) {
17 | const isMobile = queryDevice();
18 | appStore.toggleDevice(isMobile ? 'mobile' : 'desktop');
19 | appStore.toggleMenu(isMobile);
20 | }
21 | }
22 | const debounceFn = useDebounceFn(resizeHandler, 100);
23 | onMounted(() => {
24 | if (immediate) debounceFn();
25 | });
26 | onBeforeMount(() => {
27 | addEventListen(window, 'resize', debounceFn);
28 | });
29 | onBeforeUnmount(() => {
30 | removeEventListen(window, 'resize', debounceFn);
31 | });
32 | }
33 |
--------------------------------------------------------------------------------
/src/hooks/themes.ts:
--------------------------------------------------------------------------------
1 | import { computed } from 'vue';
2 | import { useAppStore } from '@/store';
3 |
4 | export default function useThemes() {
5 | const appStore = useAppStore();
6 | const isDark = computed(() => {
7 | return appStore.theme === 'dark';
8 | });
9 | return {
10 | isDark,
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/src/hooks/user.ts:
--------------------------------------------------------------------------------
1 | import { useRouter } from 'vue-router';
2 | import { Message } from '@arco-design/web-vue';
3 |
4 | import { useUserStore } from '@/store';
5 |
6 | export default function useUser() {
7 | const router = useRouter();
8 | const userStore = useUserStore();
9 | const logout = async (logoutTo?: string) => {
10 | await userStore.logout();
11 | const currentRoute = router.currentRoute.value;
12 | Message.success('登出成功');
13 | router.push({
14 | name: logoutTo && typeof logoutTo === 'string' ? logoutTo : 'login',
15 | query: {
16 | ...router.currentRoute.value.query,
17 | redirect: currentRoute.name as string,
18 | },
19 | });
20 | };
21 | return {
22 | logout,
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/src/hooks/visible.ts:
--------------------------------------------------------------------------------
1 | import { ref } from 'vue';
2 |
3 | export default function useVisible(initValue = false) {
4 | const visible = ref(initValue);
5 | const setVisible = (value: boolean) => {
6 | visible.value = value;
7 | };
8 | const toggle = () => {
9 | visible.value = !visible.value;
10 | };
11 | return {
12 | visible,
13 | setVisible,
14 | toggle,
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/src/layout/page-layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | import localeWorkplace from '@/views/dashboard/workplace/locale/en-US';
2 |
3 | import localeSearchTable from '@/views/list/search-table/locale/en-US';
4 | import localeCardList from '@/views/list/card/locale/en-US';
5 |
6 | import localeSettings from './en-US/settings';
7 |
8 | export default {
9 | 'On': 'On',
10 | 'Off': 'Off',
11 | 'menu.dashboard': 'CPS',
12 | 'menu.cps.channel': 'Channel',
13 | 'menu.cps.settings': 'Settings',
14 | 'menu.server.dashboard': 'Dashboard-Server',
15 | 'menu.server.workplace': 'Workplace-Server',
16 | 'menu.server.monitor': 'Monitor-Server',
17 | 'menu.list': 'Tools',
18 | 'menu.result': 'Result',
19 | 'menu.exception': 'Exception',
20 | 'menu.form': 'Form',
21 | 'menu.profile': 'Profile',
22 | 'menu.visualization': 'Data Visualization',
23 | 'menu.user': 'User Center',
24 | 'menu.arcoWebsite': 'Arco Design',
25 | 'menu.faq': 'FAQ',
26 | 'navbar.docs': 'Docs',
27 | 'navbar.action.locale': 'Switch to English',
28 | 'navbar.author': 'Author: BD8DFN',
29 | 'navbar.connect': 'Connect',
30 | 'navbar.disconnect': 'Disconnect',
31 | 'navbar.qa': 'Feedback',
32 | 'global.8kb': '8KB (64Kbit)',
33 | 'global.128kb': '128KB (1Mbit)',
34 | 'global.256kb': '256KB (2Mbit)',
35 | 'global.384kb': '384KB (3Mbit)',
36 | 'global.512kb': '512KB (4Mbit)',
37 | 'menu.rb': 'Backup/Restore',
38 | 'menu.flash': 'Flasher',
39 | 'menu.image': 'Startup Image',
40 | 'menu.font': 'Set Patch',
41 | 'menu.satellite': 'Satcom',
42 | 'global.onStart': ' (Put the UV-K5 into normal mode)',
43 | 'global.onBoot': ' (Put the UV-K5 into programming mode)',
44 | 'cps.onDeviceRead': 'Read from device',
45 | 'cps.onDeviceWrite': 'Write to device',
46 | 'cps.downloadImportTemplate': 'Download Import Templates',
47 | 'cps.import': 'Import',
48 | 'cps.export': 'Export',
49 | 'cps.save': 'Save',
50 | 'cps.load': 'Load',
51 | 'cps.line1': 'First line of text on startup screen',
52 | 'cps.line2': 'Second line of text on startup screen',
53 | 'cps.mdclocplay': 'Local MDC Play (Only support my firmware)',
54 | 'cps.sort': 'Sort',
55 | 'cps.name': 'Name',
56 | 'cps.bandwidth': 'Bandwidth',
57 | 'cps.tx': 'TX Frequency',
58 | 'cps.rx': 'RX Frequency',
59 | 'cps.power': 'Power',
60 | 'cps.rxToneType': 'RX Tone Type',
61 | 'cps.rxToneCTCSS': 'RX CTCSS(Hz)',
62 | 'cps.rxToneDCS': 'RX DCS',
63 | 'cps.txToneType': 'TX Tone Type',
64 | 'cps.txToneCTCSS': 'TX CTCSS(Hz)',
65 | 'cps.txToneDCS': 'TX DCS',
66 | 'cps.step': 'Frequency Step',
67 | 'cps.reverse': 'Reverse',
68 | 'cps.scramb': 'Scramb',
69 | 'cps.busy': 'Busy',
70 | 'cps.pttid': 'PTTID',
71 | 'cps.mode': 'Mode',
72 | 'cps.dtmf': 'DTMF Decode',
73 | 'cps.scanlist': 'Scanlist',
74 | 'cps.operate': 'Operate',
75 | 'cps.clear': 'Clear',
76 | 'tool.quickbackup': 'Quick Backup',
77 | 'tool.fullbackup': 'Full Backup',
78 | 'tool.cleardata': 'Clear EEPROM',
79 | 'tool.backupConfig': 'Backup Config',
80 | 'tool.restoreConfig': 'Restore Config',
81 | 'tool.backupCalibration': 'Backup Calibration',
82 | 'tool.restoreCalibration': 'Restore Calibration',
83 | 'tool.backup': 'Backup',
84 | 'tool.restore': 'Restore',
85 | 'tool.autocheck': 'AUTO',
86 | 'tool.selectSize': 'Select EEPROM size',
87 | 'tool.first': 'Warning ',
88 | 'tool.firstTitle': '',
89 | 'tool.last': '(LAST WARNING)',
90 | 'tool.clearMessage': 'This will clear the EEPROM of all contents, including configuration and calibration data!!!!',
91 | 'tool.selectFirmware': 'Select Firmware',
92 | 'tool.flash': 'FLASH',
93 | 'tool.selectImage': 'Select Image',
94 | 'tool.write': 'Write to device',
95 | 'tool.fontwrite': 'LOSEHU Chinese Character Set Write',
96 | 'tool.pinyinwrite': 'LOSEHU H Chinese Pinyin Set Write',
97 | 'tool.writefontwrite': 'Character Set Write',
98 | 'tool.Simplified_Chinese': 'CHS',
99 | 'tool.Traditional_Chinese': 'CHT',
100 | 'tool.writepinyin': 'Pinyin Set Write',
101 | 'tool.brtime': 'Browser Time',
102 | 'tool.selectSatellite': 'Select satellite',
103 | 'tool.longitude': 'Longitude',
104 | 'tool.latitude': 'Latitude',
105 | 'tool.altitude': 'Altitude',
106 | 'tool.brlonlat': 'Get browser location',
107 | 'tool.phonelonlat': 'Get phone location',
108 | 'tool.satpasstime': 'Get satellite pass time',
109 | 'tool.selectPassTime': 'Select pass time',
110 | 'tool.txFreq': 'TX Frequency',
111 | 'tool.txTone': 'TX Tone',
112 | 'tool.rxFreq': 'RX Frequency',
113 | 'tool.rxTone': 'RX Tone',
114 | 'tool.writeData': 'Write to device',
115 | 'tool.off': 'Off',
116 | 'tool.scanqr': 'Scan QR Code',
117 | 'tool.scannotice': 'Uploaded location information will be cached by the server for 10 minutes',
118 | 'tool.scaned': 'Scanned and uploaded',
119 | 'global.nosupport': 'Current browser does not support WebSerial function, please use Chrome, Edge, Opera browser.',
120 | 'global.connectFail': 'Connect Failure',
121 | 'global.handshakeFail': 'Handshake Failure',
122 | 'menu.workshop': 'Workshop',
123 | 'menu.firmware': 'Firmware Store',
124 | 'menu.channel': 'Channel Share',
125 | 'global.use': 'Use',
126 | 'global.download': 'Download',
127 | 'tool.ssbpatch': 'LOSEHU S Firmware SI4732 SSB Patch',
128 | 'tool.writessbpatch': 'SSB Patch Write',
129 | 'global.login': 'Login',
130 | 'global.register': 'Register',
131 | 'global.motto': 'Motto',
132 | 'global.logout': 'Logout',
133 | 'global.username': 'Username',
134 | 'global.nickname': 'Nickname',
135 | 'global.password': 'Password',
136 | 'global.password2': 'Retype password ',
137 | 'image.negative': 'Negative',
138 | 'workplace.clickNotice': ' (Official firmware can only detect 8KB/64Kbit)',
139 | 'menu.cps.radio': 'Radio',
140 | 'menu.cps.mdc': 'MDC Contact',
141 | 'menu.cps.dtmf': 'DTMF Contact',
142 | 'cps.contact': 'Name',
143 | 'cps.mdcid': 'MDC ID',
144 | 'cps.dtmf.up': 'DTMF Up Code',
145 | 'cps.dtmf.down': 'DTMF Down Code',
146 | 'idea.diy': 'LOSEHU DIY',
147 | 'diy.generate': 'Generate',
148 | 'cps.dtmfid': 'DTMF ID',
149 | 'global.upload': 'Upload',
150 | 'global.loginUpload': '(Login to upload and share)',
151 | 'bl': 'Multi-booting',
152 | 'cs': 'Coming Soon',
153 | 'oi': 'Operating Instructions: ',
154 | 'bl.warning': '⚠: Experimental feature Use may damage radio station',
155 | 'bl.readme': 'Readme: ',
156 | 'bl.clear': 'Clear',
157 | 'bl.onlyEnglish': 'Firmware names are supported in English only',
158 | 'bl.drag': 'Select the firmware and drag the firmware card to the EEPROM grid',
159 | 'bl.bootloader': 'Bootloader Use',
160 | 'sat.selfSatInfo': 'My satellite parameters',
161 | 'sat.addSelfSat': 'Add my satellite',
162 | 'chat': 'Radio Chat',
163 | 'menu.cps.writeNoticeTitle': 'Confirm',
164 | 'menu.cps.writeNoticeContent': "Confirmation to write the channel shown on the web page to the device? (will override the device's current channel configuration)",
165 | 'menu.satellite2': 'Satcom 2.0',
166 | ...localeSettings,
167 | ...localeWorkplace,
168 |
169 | ...localeSearchTable,
170 | ...localeCardList,
171 | };
172 |
--------------------------------------------------------------------------------
/src/locale/en-US/settings.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'settings.title': 'Settings',
3 | 'settings.themeColor': 'Theme Color',
4 | 'settings.content': 'Content Setting',
5 | 'settings.search': 'Search',
6 | 'settings.language': 'Language',
7 | 'settings.navbar': 'Navbar',
8 | 'settings.menuWidth': 'Menu Width (px)',
9 | 'settings.navbar.theme.toLight': 'Click to use light mode',
10 | 'settings.navbar.theme.toDark': 'Click to use dark mode',
11 | 'settings.navbar.screen.toFull': 'Click to switch to full screen mode',
12 | 'settings.navbar.screen.toExit': 'Click to exit the full screen mode',
13 | 'settings.navbar.alerts': 'alerts',
14 | 'settings.menu': 'Menu',
15 | 'settings.topMenu': 'Top Menu',
16 | 'settings.tabBar': 'Tab Bar',
17 | 'settings.footer': 'Footer',
18 | 'settings.otherSettings': 'Other Settings',
19 | 'settings.colorWeak': 'Color Weak',
20 | 'settings.alertContent':
21 | 'After the configuration is only temporarily effective, if you want to really affect the project, click the "Copy Settings" button below and replace the configuration in settings.json.',
22 | 'settings.copySettings': 'Copy Settings',
23 | 'settings.copySettings.message':
24 | 'Copy succeeded, please paste to file src/settings.json.',
25 | 'settings.close': 'Close',
26 | 'settings.color.tooltip':
27 | '10 gradient colors generated according to the theme color',
28 | 'settings.menuFromServer': 'Menu From Server',
29 | };
30 |
--------------------------------------------------------------------------------
/src/locale/index.ts:
--------------------------------------------------------------------------------
1 | import { createI18n } from 'vue-i18n';
2 | import en from './en-US';
3 | import cn from './zh-CN';
4 |
5 | export const LOCALE_OPTIONS = [
6 | { label: '中文', value: 'zh-CN' },
7 | { label: 'English', value: 'en-US' },
8 | ];
9 | const defaultLocale = localStorage.getItem('arco-locale') || navigator.language || 'en-US';
10 |
11 | const i18n = createI18n({
12 | locale: defaultLocale,
13 | fallbackLocale: 'en-US',
14 | legacy: false,
15 | allowComposition: true,
16 | messages: {
17 | 'en-US': en,
18 | 'zh-CN': cn,
19 | },
20 | });
21 |
22 | export default i18n;
23 |
--------------------------------------------------------------------------------
/src/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | import localeWorkplace from '@/views/dashboard/workplace/locale/zh-CN';
2 |
3 | import localeSearchTable from '@/views/list/search-table/locale/zh-CN';
4 | import localeCardList from '@/views/list/card/locale/zh-CN';
5 |
6 | import localeSettings from './zh-CN/settings';
7 |
8 | export default {
9 | 'On': '开',
10 | 'Off': '关',
11 | 'menu.dashboard': '写频',
12 | 'menu.cps.channel': '信道管理',
13 | 'menu.cps.settings': '设置管理',
14 | 'menu.server.dashboard': '仪表盘-服务端',
15 | 'menu.server.workplace': '工作台-服务端',
16 | 'menu.server.monitor': '实时监控-服务端',
17 | 'menu.list': '小工具',
18 | 'menu.result': '结果页',
19 | 'menu.exception': '异常页',
20 | 'menu.form': '表单页',
21 | 'menu.profile': '详情页',
22 | 'menu.visualization': '固件更新',
23 | 'menu.user': '个人中心',
24 | 'menu.arcoWebsite': 'Arco Design',
25 | 'menu.faq': '常见问题',
26 | 'navbar.docs': '文档中心',
27 | 'navbar.action.locale': '切换为中文',
28 | 'navbar.author': '作者:BD8DFN',
29 | 'navbar.connect': '连接',
30 | 'navbar.disconnect': '断开',
31 | 'navbar.qa': '问题反馈',
32 | 'global.8kb': '8KB(64Kbit)',
33 | 'global.128kb': '128KB(1Mbit)',
34 | 'global.256kb': '256KB(2Mbit)',
35 | 'global.384kb': '384KB(3Mbit)',
36 | 'global.512kb': '512KB(4Mbit)',
37 | 'menu.rb': '备份/还原',
38 | 'menu.flash': '固件升级',
39 | 'menu.image': '开机图片',
40 | 'menu.font': '字库写入',
41 | 'menu.satellite': '星历写入',
42 | 'global.onStart': '(手台应在开机状态下)',
43 | 'global.onBoot': '(手台应在刷机模式下)',
44 | 'cps.onDeviceRead': '从设备读取',
45 | 'cps.onDeviceWrite': '写入设备',
46 | 'cps.downloadImportTemplate': '下载导入模板',
47 | 'cps.import': '导入',
48 | 'cps.export': '导出',
49 | 'cps.save': '保存',
50 | 'cps.load': '加载',
51 | 'cps.line1': '启动画面首行文字',
52 | 'cps.line2': '启动画面次行文字',
53 | 'cps.mdclocplay': '本地播放首尾音(仅117P6)',
54 | 'cps.sort': '排序',
55 | 'cps.name': '信道名称',
56 | 'cps.bandwidth': '带宽',
57 | 'cps.tx': '发送频率',
58 | 'cps.rx': '接收频率',
59 | 'cps.power': '发送功率',
60 | 'cps.rxToneType': '接收亚音类型',
61 | 'cps.rxToneCTCSS': '接收亚音频(Hz)',
62 | 'cps.rxToneDCS': '接收亚音数码',
63 | 'cps.txToneType': '发送亚音类型',
64 | 'cps.txToneCTCSS': '发送亚音频(Hz)',
65 | 'cps.txToneDCS': '发送亚音数码',
66 | 'cps.step': '频率步进',
67 | 'cps.reverse': '倒频',
68 | 'cps.scramb': '加密',
69 | 'cps.busy': '繁忙禁发',
70 | 'cps.pttid': '信令码',
71 | 'cps.mode': '信道模式',
72 | 'cps.dtmf': 'DTMF解码',
73 | 'cps.scanlist': '扫描列表',
74 | 'cps.operate': '操作',
75 | 'cps.clear': '清空',
76 | 'tool.quickbackup': '快捷备份',
77 | 'tool.fullbackup': '完整备份',
78 | 'tool.cleardata': '清空数据',
79 | 'tool.backupConfig': '备份配置',
80 | 'tool.restoreConfig': '恢复配置',
81 | 'tool.backupCalibration': '备份校准',
82 | 'tool.restoreCalibration': '恢复校准',
83 | 'tool.backup': '备份',
84 | 'tool.restore': '恢复',
85 | 'tool.autocheck': '自动检测',
86 | 'tool.selectSize': '选择 EEPROM 大小',
87 | 'tool.first': '第 ',
88 | 'tool.firstTitle': ' 次警告',
89 | 'tool.last': '(最后警告)',
90 | 'tool.clearMessage': '这将会清空 EEPROM 所有内容,包括配置及校准数据!!!',
91 | 'tool.selectFirmware': '选择固件',
92 | 'tool.flash': '更新',
93 | 'tool.selectImage': '选择图片',
94 | 'tool.write': '写入',
95 | 'tool.fontwrite': 'LOSEHU 固件字库写入',
96 | 'tool.pinyinwrite': 'LOSEHU H 版固件拼音索引表',
97 | 'tool.writefontwrite': '自动写入字库',
98 | 'tool.Simplified_Chinese': '简体',
99 | 'tool.Traditional_Chinese': '繁体',
100 | 'tool.writepinyin': '写入拼音检索表',
101 | 'tool.brtime': '浏览器时间',
102 | 'tool.selectSatellite': '选择卫星',
103 | 'tool.longitude': '经度',
104 | 'tool.latitude': '纬度',
105 | 'tool.altitude': '海拔',
106 | 'tool.brlonlat': '浏览器获取经纬度',
107 | 'tool.phonelonlat': '手机扫码获取经纬度',
108 | 'tool.satpasstime': '获取卫星过境时间',
109 | 'tool.selectPassTime': '选择过境时间',
110 | 'tool.txFreq': '上行频率',
111 | 'tool.txTone': '上行亚音',
112 | 'tool.rxFreq': '下行频率',
113 | 'tool.rxTone': '下行亚音',
114 | 'tool.writeData': '写入数据',
115 | 'tool.off': '关闭',
116 | 'tool.scanqr': '手机扫码获取经纬度',
117 | 'tool.scannotice': '上传经纬度信息将被服务器缓存十分钟',
118 | 'tool.scaned': '已扫码上传',
119 | 'global.nosupport': '当前浏览器不支持网页串口功能,请使用 Chrome, Edge, Opera 浏览器。',
120 | 'global.connectFail': '连接失败',
121 | 'global.handshakeFail': '握手失败',
122 | 'menu.workshop': '创意工坊',
123 | 'menu.firmware': '固件市场',
124 | 'menu.channel': '信道分享',
125 | 'global.use': '使用',
126 | 'global.download': '下载',
127 | 'tool.ssbpatch': 'LOSEHU S 版固件 SI4732 单边带补丁',
128 | 'tool.writessbpatch': '写入单边带补丁',
129 | 'global.login': '登录',
130 | 'global.register': '注册',
131 | 'global.motto': '联系方式(用于找回密码)',
132 | 'global.logout': '退出',
133 | 'global.username': '*请输入用户名',
134 | 'global.nickname': '*请输入昵称',
135 | 'global.password': '*请输入密码',
136 | 'global.password2': '*请再次输入密码',
137 | 'image.negative': '反色',
138 | 'workplace.clickNotice': '(官方固件只能检测 8KB/64Kbit)',
139 | 'menu.cps.radio': '收音机',
140 | 'menu.cps.mdc': 'MDC 联系人',
141 | 'menu.cps.dtmf': 'DTMF 联系人',
142 | 'cps.contact': '联系人',
143 | 'cps.mdcid': 'MDC ID',
144 | 'idea.diy': '自定义萝卜固件',
145 | 'diy.generate': '生成',
146 | 'cps.dtmfid': 'DTMF ID',
147 | 'cps.dtmf.up': 'DTMF 上线码',
148 | 'cps.dtmf.down': 'DTMF 下线码',
149 | 'global.upload': '上传',
150 | 'global.loginUpload': '(登录可上传分享)',
151 | 'bl': '多系统',
152 | 'cs': '敬请期待',
153 | 'oi': '操作说明:',
154 | 'bl.warning': '⚠:实验性功能 使用可能会损坏手台',
155 | 'bl.readme': '使用说明:',
156 | 'bl.clear': '清空',
157 | 'bl.onlyEnglish': '固件名称仅支持英文',
158 | 'bl.drag': '选择固件后将固件卡片拖拽到上方 EEPROM',
159 | 'bl.bootloader': '引导程序占用区',
160 | 'sat.selfSatInfo': '我的卫星参数',
161 | 'sat.addSelfSat': '添加我的卫星',
162 | 'chat': '无线电聊天',
163 | 'menu.cps.writeNoticeTitle': '确认',
164 | 'menu.cps.writeNoticeContent': '确认将网页显示的信道写入设备吗?(将覆盖设备当前信道配置)',
165 | 'menu.satellite2': '星历写入 2.0',
166 | ...localeSettings,
167 | ...localeWorkplace,
168 |
169 | ...localeSearchTable,
170 | ...localeCardList,
171 | };
172 |
--------------------------------------------------------------------------------
/src/locale/zh-CN/settings.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'settings.title': '页面配置',
3 | 'settings.themeColor': '主题色',
4 | 'settings.content': '内容区域',
5 | 'settings.search': '搜索',
6 | 'settings.language': '语言',
7 | 'settings.navbar': '导航栏',
8 | 'settings.menuWidth': '菜单宽度 (px)',
9 | 'settings.navbar.theme.toLight': '点击切换为亮色模式',
10 | 'settings.navbar.theme.toDark': '点击切换为暗黑模式',
11 | 'settings.navbar.screen.toFull': '点击切换全屏模式',
12 | 'settings.navbar.screen.toExit': '点击退出全屏模式',
13 | 'settings.navbar.alerts': '消息通知',
14 | 'settings.menu': '菜单栏',
15 | 'settings.topMenu': '顶部菜单栏',
16 | 'settings.tabBar': '多页签',
17 | 'settings.footer': '底部',
18 | 'settings.otherSettings': '其他设置',
19 | 'settings.colorWeak': '色弱模式',
20 | 'settings.alertContent':
21 | '配置之后仅是临时生效,要想真正作用于项目,点击下方的 "复制配置" 按钮,将配置替换到 settings.json 中即可。',
22 | 'settings.copySettings': '复制配置',
23 | 'settings.copySettings.message':
24 | '复制成功,请粘贴到 src/settings.json 文件中',
25 | 'settings.close': '关闭',
26 | 'settings.color.tooltip':
27 | '根据主题颜色生成的 10 个梯度色(将配置复制到项目中,主题色才能对亮色 / 暗黑模式同时生效)',
28 | 'settings.menuFromServer': '菜单来源于后台',
29 | };
30 |
--------------------------------------------------------------------------------
/src/main.ts:
--------------------------------------------------------------------------------
1 | import { createApp } from 'vue';
2 | import ArcoVueIcon from '@arco-design/web-vue/es/icon';
3 | import globalComponents from '@/components';
4 | import router from './router';
5 | import store from './store';
6 | import i18n from './locale';
7 | import directive from './directive';
8 | import App from './App.vue';
9 | // Styles are imported via arco-plugin. See config/plugin/arcoStyleImport.ts in the directory for details
10 | // 样式通过 arco-plugin 插件导入。详见目录文件 config/plugin/arcoStyleImport.ts
11 | // https://arco.design/docs/designlab/use-theme-package
12 | import '@/assets/style/global.less';
13 | import '@/api/interceptor';
14 | import 'tdesign-vue-next/es/style/index.css';
15 | import Updater from "./utils/AutoUpdate.js";
16 | import VueMatomo from 'vue-matomo';
17 |
18 | const AutoUpdate = new Updater()
19 | AutoUpdate.on('update',()=>{
20 | setTimeout(async()=>{
21 | if(process.env.NODE_ENV == 'development'){
22 | return
23 | }
24 | const result = confirm('当前网站有更新,请点击确定刷新页面体验');
25 | if(result){
26 | location.reload();
27 | }
28 | },500)
29 | })
30 |
31 | const app = createApp(App);
32 |
33 | app.use(ArcoVueIcon);
34 |
35 | app.use(router);
36 | app.use(store);
37 | app.use(i18n);
38 | app.use(globalComponents);
39 | app.use(directive);
40 |
41 | if(import.meta.env.VITE_METER_SITE.split(',').indexOf(location.hostname) !== -1){
42 | app.use(VueMatomo, {
43 | host: '//analytics.vicicode.com',
44 | siteId: 2,
45 | router: router
46 | })
47 | }
48 |
49 | app.mount('#app');
50 |
--------------------------------------------------------------------------------
/src/router/app-menus/index.ts:
--------------------------------------------------------------------------------
1 | import { appRoutes, appExternalRoutes } from '../routes';
2 |
3 | const mixinRoutes = [...appRoutes, ...appExternalRoutes];
4 |
5 | const appClientMenus = mixinRoutes.map((el) => {
6 | const { name, path, meta, redirect, children } = el;
7 | return {
8 | name,
9 | path,
10 | meta,
11 | redirect,
12 | children,
13 | };
14 | });
15 |
16 | export default appClientMenus;
17 |
--------------------------------------------------------------------------------
/src/router/constants.ts:
--------------------------------------------------------------------------------
1 | export const WHITE_LIST = [
2 | { name: 'notFound', children: [] },
3 | { name: 'login', children: [] },
4 | ];
5 |
6 | export const NOT_FOUND = {
7 | name: 'notFound',
8 | };
9 |
10 | export const REDIRECT_ROUTE_NAME = 'Redirect';
11 |
12 | export const DEFAULT_ROUTE_NAME = 'Workplace';
13 |
14 | export const DEFAULT_ROUTE = {
15 | title: 'menu.dashboard.workplace',
16 | name: DEFAULT_ROUTE_NAME,
17 | fullPath: '/chirp/base',
18 | };
19 |
--------------------------------------------------------------------------------
/src/router/guard/index.ts:
--------------------------------------------------------------------------------
1 | import type { Router } from 'vue-router';
2 | import { setRouteEmitter } from '@/utils/route-listener';
3 | import setupUserLoginInfoGuard from './userLoginInfo';
4 | import setupPermissionGuard from './permission';
5 |
6 | function setupPageGuard(router: Router) {
7 | router.beforeEach(async (to) => {
8 | // emit route change
9 | setRouteEmitter(to);
10 | });
11 | }
12 |
13 | export default function createRouteGuard(router: Router) {
14 | setupPageGuard(router);
15 | setupUserLoginInfoGuard(router);
16 | setupPermissionGuard(router);
17 | }
18 |
--------------------------------------------------------------------------------
/src/router/guard/permission.ts:
--------------------------------------------------------------------------------
1 | import type { Router, RouteRecordNormalized } from 'vue-router';
2 | import NProgress from 'nprogress'; // progress bar
3 |
4 | import usePermission from '@/hooks/permission';
5 | import { useUserStore, useAppStore } from '@/store';
6 | import { appRoutes } from '../routes';
7 | import { WHITE_LIST, NOT_FOUND } from '../constants';
8 |
9 | export default function setupPermissionGuard(router: Router) {
10 | router.beforeEach(async (to, from, next) => {
11 | const appStore = useAppStore();
12 | const userStore = useUserStore();
13 | const Permission = usePermission();
14 | const permissionsAllow = Permission.accessRouter(to);
15 | if (appStore.menuFromServer) {
16 | // 针对来自服务端的菜单配置进行处理
17 | // Handle routing configuration from the server
18 |
19 | // 根据需要自行完善来源于服务端的菜单配置的permission逻辑
20 | // Refine the permission logic from the server's menu configuration as needed
21 | if (
22 | !appStore.appAsyncMenus.length &&
23 | !WHITE_LIST.find((el) => el.name === to.name)
24 | ) {
25 | await appStore.fetchServerMenuConfig();
26 | }
27 | const serverMenuConfig = [...appStore.appAsyncMenus, ...WHITE_LIST];
28 |
29 | let exist = false;
30 | while (serverMenuConfig.length && !exist) {
31 | const element = serverMenuConfig.shift();
32 | if (element?.name === to.name) exist = true;
33 |
34 | if (element?.children) {
35 | serverMenuConfig.push(
36 | ...(element.children as unknown as RouteRecordNormalized[])
37 | );
38 | }
39 | }
40 | if (exist && permissionsAllow) {
41 | next();
42 | } else next(NOT_FOUND);
43 | } else {
44 | // eslint-disable-next-line no-lonely-if
45 | if (permissionsAllow) next();
46 | else {
47 | const destination =
48 | Permission.findFirstPermissionRoute(appRoutes, userStore.role) ||
49 | NOT_FOUND;
50 | next(destination);
51 | }
52 | }
53 | NProgress.done();
54 | });
55 | }
56 |
--------------------------------------------------------------------------------
/src/router/guard/userLoginInfo.ts:
--------------------------------------------------------------------------------
1 | import type { Router, LocationQueryRaw } from 'vue-router';
2 | import NProgress from 'nprogress'; // progress bar
3 |
4 | export default function setupUserLoginInfoGuard(router: Router) {
5 | router.beforeEach(async (to, from, next) => {
6 | NProgress.start();
7 | next();
8 | });
9 | }
10 |
--------------------------------------------------------------------------------
/src/router/index.ts:
--------------------------------------------------------------------------------
1 | import { createRouter, createWebHashHistory } from 'vue-router';
2 | import NProgress from 'nprogress'; // progress bar
3 | import 'nprogress/nprogress.css';
4 |
5 | import { appRoutes } from './routes';
6 | import { REDIRECT_MAIN, NOT_FOUND_ROUTE, SATLOC } from './routes/base';
7 | import createRouteGuard from './guard';
8 |
9 | NProgress.configure({ showSpinner: false }); // NProgress Configuration
10 |
11 | const router = createRouter({
12 | history: createWebHashHistory(),
13 | routes: [
14 | {
15 | path: '/',
16 | redirect: 'chirp/base',
17 | },
18 | ...appRoutes,
19 | REDIRECT_MAIN,
20 | SATLOC,
21 | NOT_FOUND_ROUTE,
22 | ],
23 | scrollBehavior() {
24 | return { top: 0 };
25 | },
26 | });
27 |
28 | createRouteGuard(router);
29 |
30 | export default router;
31 |
--------------------------------------------------------------------------------
/src/router/routes/base.ts:
--------------------------------------------------------------------------------
1 | import type { RouteRecordRaw } from 'vue-router';
2 | import { REDIRECT_ROUTE_NAME } from '@/router/constants';
3 |
4 | export const DEFAULT_LAYOUT = () => import('@/layout/default-layout.vue');
5 |
6 | export const REDIRECT_MAIN: RouteRecordRaw = {
7 | path: '/redirect',
8 | name: 'redirectWrapper',
9 | component: DEFAULT_LAYOUT,
10 | meta: {
11 | requiresAuth: true,
12 | hideInMenu: true,
13 | },
14 | children: [
15 | {
16 | path: '/redirect/:path',
17 | name: REDIRECT_ROUTE_NAME,
18 | component: () => import('@/views/redirect/index.vue'),
19 | meta: {
20 | requiresAuth: true,
21 | hideInMenu: true,
22 | },
23 | },
24 | ],
25 | };
26 |
27 | export const SATLOC: RouteRecordRaw = {
28 | path: '/satloc',
29 | name: 'satloc',
30 | component: () => import('@/views/list/satloc/index.vue'),
31 | }
32 |
33 | export const NOT_FOUND_ROUTE: RouteRecordRaw = {
34 | path: '/:pathMatch(.*)*',
35 | name: 'notFound',
36 | component: () => import('@/views/not-found/index.vue'),
37 | };
38 |
--------------------------------------------------------------------------------
/src/router/routes/externalModules/faq.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | path: 'https://www.vicicode.com/',
3 | name: 'BD8DFN',
4 | meta: {
5 | locale: 'navbar.author',
6 | requiresAuth: true,
7 | order: 8,
8 | },
9 | };
10 |
--------------------------------------------------------------------------------
/src/router/routes/index.ts:
--------------------------------------------------------------------------------
1 | import type { RouteRecordNormalized } from 'vue-router';
2 |
3 | const modules = import.meta.glob('./modules/*.ts', { eager: true });
4 | const externalModules = import.meta.glob('./externalModules/*.ts', {
5 | eager: true,
6 | });
7 |
8 | function formatModules(_modules: any, result: RouteRecordNormalized[]) {
9 | Object.keys(_modules).forEach((key) => {
10 | const defaultModule = _modules[key].default;
11 | if (!defaultModule) return;
12 | const moduleList = Array.isArray(defaultModule)
13 | ? [...defaultModule]
14 | : [defaultModule];
15 | result.push(...moduleList);
16 | });
17 | return result;
18 | }
19 |
20 | export const appRoutes: RouteRecordNormalized[] = formatModules(modules, []);
21 |
22 | export const appExternalRoutes: RouteRecordNormalized[] = formatModules(
23 | externalModules,
24 | []
25 | );
26 |
--------------------------------------------------------------------------------
/src/router/routes/modules/dashboard.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_LAYOUT } from '../base';
2 | import { AppRouteRecordRaw } from '../types';
3 |
4 | const DASHBOARD: AppRouteRecordRaw = {
5 | path: '/chirp',
6 | name: 'dashboard',
7 | component: DEFAULT_LAYOUT,
8 | meta: {
9 | locale: 'menu.dashboard',
10 | requiresAuth: true,
11 | icon: 'icon-dashboard',
12 | order: 0,
13 | },
14 | children: [
15 | {
16 | path: 'base',
17 | name: 'Workplace',
18 | component: () => import('@/views/dashboard/workplace/index.vue'),
19 | meta: {
20 | locale: 'menu.dashboard.workplace',
21 | requiresAuth: true,
22 | roles: ['*'],
23 | },
24 | },
25 | {
26 | path: 'channel',
27 | name: 'Channel',
28 | component: () => import('@/views/list/search-table/index.vue'),
29 | meta: {
30 | locale: 'menu.cps.channel',
31 | requiresAuth: true,
32 | roles: ['*'],
33 | },
34 | },
35 | {
36 | path: 'radio',
37 | name: 'Radio',
38 | component: () => import('@/views/list/radio/index.vue'),
39 | meta: {
40 | locale: 'menu.cps.radio',
41 | requiresAuth: true,
42 | roles: ['*'],
43 | },
44 | },
45 | {
46 | path: 'mdc',
47 | name: 'Mdc',
48 | component: () => import('@/views/list/mdc/index.vue'),
49 | meta: {
50 | locale: 'menu.cps.mdc',
51 | requiresAuth: true,
52 | roles: ['*'],
53 | },
54 | },
55 | {
56 | path: 'dtmf',
57 | name: 'Dtmf',
58 | component: () => import('@/views/list/dtmf/index.vue'),
59 | meta: {
60 | locale: 'menu.cps.dtmf',
61 | requiresAuth: true,
62 | roles: ['*'],
63 | },
64 | },
65 | {
66 | path: 'settings',
67 | name: 'Settings',
68 | component: () => import('@/views/list/settings/index.vue'),
69 | meta: {
70 | locale: 'menu.cps.settings',
71 | requiresAuth: true,
72 | roles: ['*'],
73 | },
74 | },
75 | {
76 | path: 'thanks',
77 | name: 'Thanks',
78 | component: () => import('@/views/thanks/index.vue'),
79 | meta: {
80 | hideInMenu: true,
81 | locale: '感谢列表',
82 | requiresAuth: true,
83 | roles: ['*'],
84 | },
85 | }
86 | ],
87 | };
88 |
89 | export default DASHBOARD;
90 |
--------------------------------------------------------------------------------
/src/router/routes/modules/idea.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_LAYOUT } from '../base';
2 | import { AppRouteRecordRaw } from '../types';
3 |
4 | const IDEA: AppRouteRecordRaw = {
5 | path: '/idea',
6 | name: 'idea',
7 | component: DEFAULT_LAYOUT,
8 | meta: {
9 | locale: 'menu.workshop',
10 | requiresAuth: true,
11 | icon: 'icon-list',
12 | order: 3,
13 | },
14 | children: [
15 | {
16 | path: 'firmware',
17 | name: 'ideaFirmware',
18 | component: () => import('@/views/idea/firmware/index.vue'),
19 | meta: {
20 | locale: 'menu.firmware',
21 | requiresAuth: true,
22 | roles: ['*'],
23 | },
24 | },
25 | {
26 | path: 'Image',
27 | name: 'ideaImage',
28 | component: () => import('@/views/idea/image/index.vue'),
29 | meta: {
30 | locale: 'menu.image',
31 | requiresAuth: true,
32 | roles: ['*'],
33 | },
34 | },
35 | {
36 | path: 'channel',
37 | name: 'ideaChannel',
38 | component: () => import('@/views/idea/channel/index.vue'),
39 | meta: {
40 | locale: 'menu.channel',
41 | requiresAuth: true,
42 | roles: ['*'],
43 | },
44 | },
45 | {
46 | path: 'losehu',
47 | name: 'ideaLosehu',
48 | component: () => import('@/views/idea/losehu/index.vue'),
49 | meta: {
50 | locale: 'idea.diy',
51 | requiresAuth: true,
52 | roles: ['*'],
53 | },
54 | },
55 | ],
56 | };
57 |
58 | export default IDEA;
59 |
--------------------------------------------------------------------------------
/src/router/routes/modules/list.ts:
--------------------------------------------------------------------------------
1 | import { DEFAULT_LAYOUT } from '../base';
2 | import { AppRouteRecordRaw } from '../types';
3 |
4 | const LIST: AppRouteRecordRaw = {
5 | path: '/tool',
6 | name: 'list',
7 | component: DEFAULT_LAYOUT,
8 | meta: {
9 | locale: 'menu.list',
10 | requiresAuth: true,
11 | icon: 'icon-apps',
12 | order: 2,
13 | },
14 | children: [
15 | {
16 | path: 'backup',
17 | name: 'Backup',
18 | component: () => import('@/views/list/card/index.vue'),
19 | meta: {
20 | locale: 'menu.rb',
21 | requiresAuth: true,
22 | roles: ['*'],
23 | },
24 | },
25 | {
26 | path: 'flash',
27 | name: 'Flash',
28 | component: () => import('@/views/list/flash/index.vue'),
29 | meta: {
30 | locale: 'menu.flash',
31 | requiresAuth: true,
32 | roles: ['*'],
33 | },
34 | },
35 | {
36 | path: 'image',
37 | name: 'Image',
38 | component: () => import('@/views/list/image/index.vue'),
39 | meta: {
40 | locale: 'menu.image',
41 | requiresAuth: true,
42 | roles: ['*'],
43 | },
44 | },
45 | {
46 | path: 'chi',
47 | name: 'Chi',
48 | component: () => import('@/views/list/chi/index.vue'),
49 | meta: {
50 | locale: 'menu.font',
51 | requiresAuth: true,
52 | roles: ['*'],
53 | },
54 | },
55 | {
56 | path: 'sat',
57 | name: 'Sat',
58 | component: () => import('@/views/list/sat/index.vue'),
59 | meta: {
60 | locale: 'menu.satellite',
61 | requiresAuth: true,
62 | roles: ['*'],
63 | },
64 | },
65 | {
66 | path: 'sat2',
67 | name: 'Sat2',
68 | component: () => import('@/views/list/sat2/index.vue'),
69 | meta: {
70 | locale: 'menu.satellite2',
71 | requiresAuth: true,
72 | roles: ['*'],
73 | },
74 | },
75 | {
76 | path: 'bl',
77 | name: 'BL',
78 | component: () => import('@/views/list/bl/index.vue'),
79 | meta: {
80 | locale: 'bl',
81 | requiresAuth: true,
82 | roles: ['*'],
83 | },
84 | },
85 | {
86 | path: 'chat',
87 | name: 'Chat',
88 | component: () => import('@/views/list/chat/index.vue'),
89 | meta: {
90 | locale: 'chat',
91 | requiresAuth: true,
92 | roles: ['*'],
93 | },
94 | }
95 | ],
96 | };
97 |
98 | export default LIST;
99 |
--------------------------------------------------------------------------------
/src/router/routes/types.ts:
--------------------------------------------------------------------------------
1 | import { defineComponent } from 'vue';
2 | import type { RouteMeta, NavigationGuard } from 'vue-router';
3 |
4 | export type Component =
5 | | ReturnType
6 | | (() => Promise)
7 | | (() => Promise);
8 |
9 | export interface AppRouteRecordRaw {
10 | path: string;
11 | name?: string | symbol;
12 | meta?: RouteMeta;
13 | redirect?: string;
14 | component: Component | string;
15 | children?: AppRouteRecordRaw[];
16 | alias?: string | string[];
17 | props?: Record;
18 | beforeEnter?: NavigationGuard | NavigationGuard[];
19 | fullPath?: string;
20 | }
21 |
--------------------------------------------------------------------------------
/src/router/typings.d.ts:
--------------------------------------------------------------------------------
1 | import 'vue-router';
2 |
3 | declare module 'vue-router' {
4 | interface RouteMeta {
5 | roles?: string[]; // Controls roles that have access to the page
6 | requiresAuth: boolean; // Whether login is required to access the current page (every route must declare)
7 | icon?: string; // The icon show in the side menu
8 | locale?: string; // The locale name show in side menu and breadcrumb
9 | hideInMenu?: boolean; // If true, it is not displayed in the side menu
10 | hideChildrenInMenu?: boolean; // if set true, the children are not displayed in the side menu
11 | activeMenu?: string; // if set name, the menu will be highlighted according to the name you set
12 | order?: number; // Sort routing menu items. If set key, the higher the value, the more forward it is
13 | noAffix?: boolean; // if set true, the tag will not affix in the tab-bar
14 | ignoreCache?: boolean; // if set true, the page will not be cached
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/store/index.ts:
--------------------------------------------------------------------------------
1 | import { createPinia } from 'pinia';
2 | import useAppStore from './modules/app';
3 | import useUserStore from './modules/user';
4 | import useTabBarStore from './modules/tab-bar';
5 |
6 | const pinia = createPinia();
7 |
8 | export { useAppStore, useUserStore, useTabBarStore };
9 | export default pinia;
10 |
--------------------------------------------------------------------------------
/src/store/modules/app/index.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia';
2 | import { Notification } from '@arco-design/web-vue';
3 | import type { NotificationReturn } from '@arco-design/web-vue/es/notification/interface';
4 | import type { RouteRecordNormalized } from 'vue-router';
5 | import defaultSettings from '@/config/settings.json';
6 | import { getMenuList } from '@/api/user';
7 | import { AppState } from './types';
8 |
9 | const useAppStore = defineStore('app', {
10 | state: (): AppState => ({ ...defaultSettings }),
11 |
12 | getters: {
13 | appCurrentSetting(state: AppState): AppState {
14 | return { ...state };
15 | },
16 | appDevice(state: AppState) {
17 | return state.device;
18 | },
19 | appAsyncMenus(state: AppState): RouteRecordNormalized[] {
20 | return state.serverMenu as unknown as RouteRecordNormalized[];
21 | },
22 | },
23 |
24 | actions: {
25 | // Update app settings
26 | updateSettings(partial: Partial) {
27 | // @ts-ignore-next-line
28 | this.$patch(partial);
29 | },
30 |
31 | // Change theme color
32 | toggleTheme(dark: boolean) {
33 | if (dark) {
34 | this.theme = 'dark';
35 | document.documentElement.setAttribute('theme-mode', 'dark');
36 | document.body.setAttribute('arco-theme', 'dark');
37 | } else {
38 | this.theme = 'light';
39 | document.documentElement.removeAttribute('theme-mode');
40 | document.body.removeAttribute('arco-theme');
41 | }
42 | },
43 | toggleDevice(device: string) {
44 | this.device = device;
45 | },
46 | toggleMenu(value: boolean) {
47 | this.hideMenu = value;
48 | },
49 | async fetchServerMenuConfig() {
50 | let notifyInstance: NotificationReturn | null = null;
51 | try {
52 | notifyInstance = Notification.info({
53 | id: 'menuNotice', // Keep the instance id the same
54 | content: 'loading',
55 | closable: true,
56 | });
57 | const { data } = await getMenuList();
58 | this.serverMenu = data;
59 | notifyInstance = Notification.success({
60 | id: 'menuNotice',
61 | content: 'success',
62 | closable: true,
63 | });
64 | } catch (error) {
65 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
66 | notifyInstance = Notification.error({
67 | id: 'menuNotice',
68 | content: 'error',
69 | closable: true,
70 | });
71 | }
72 | },
73 | clearServerMenu() {
74 | this.serverMenu = [];
75 | },
76 | },
77 | });
78 |
79 | export default useAppStore;
80 |
--------------------------------------------------------------------------------
/src/store/modules/app/types.ts:
--------------------------------------------------------------------------------
1 | import type { RouteRecordNormalized } from 'vue-router';
2 |
3 | export interface AppState {
4 | theme: string;
5 | colorWeak: boolean;
6 | navbar: boolean;
7 | menu: boolean;
8 | topMenu: boolean;
9 | hideMenu: boolean;
10 | menuCollapse: boolean;
11 | footer: boolean;
12 | themeColor: string;
13 | menuWidth: number;
14 | globalSettings: boolean;
15 | device: string;
16 | tabBar: boolean;
17 | menuFromServer: boolean;
18 | serverMenu: RouteRecordNormalized[];
19 | [key: string]: unknown;
20 | }
21 |
--------------------------------------------------------------------------------
/src/store/modules/tab-bar/index.ts:
--------------------------------------------------------------------------------
1 | import type { RouteLocationNormalized } from 'vue-router';
2 | import { defineStore } from 'pinia';
3 | import {
4 | DEFAULT_ROUTE,
5 | DEFAULT_ROUTE_NAME,
6 | REDIRECT_ROUTE_NAME,
7 | } from '@/router/constants';
8 | import { isString } from '@/utils/is';
9 | import { TabBarState, TagProps } from './types';
10 |
11 | const formatTag = (route: RouteLocationNormalized): TagProps => {
12 | const { name, meta, fullPath, query } = route;
13 | return {
14 | title: meta.locale || '',
15 | name: String(name),
16 | fullPath,
17 | query,
18 | ignoreCache: meta.ignoreCache,
19 | };
20 | };
21 |
22 | const BAN_LIST = [REDIRECT_ROUTE_NAME];
23 |
24 | const useAppStore = defineStore('tabBar', {
25 | state: (): TabBarState => ({
26 | cacheTabList: new Set([DEFAULT_ROUTE_NAME]),
27 | tagList: [DEFAULT_ROUTE],
28 | }),
29 |
30 | getters: {
31 | getTabList(): TagProps[] {
32 | return this.tagList;
33 | },
34 | getCacheList(): string[] {
35 | return Array.from(this.cacheTabList);
36 | },
37 | },
38 |
39 | actions: {
40 | updateTabList(route: RouteLocationNormalized) {
41 | if (BAN_LIST.includes(route.name as string)) return;
42 | this.tagList.push(formatTag(route));
43 | if (!route.meta.ignoreCache) {
44 | this.cacheTabList.add(route.name as string);
45 | }
46 | },
47 | deleteTag(idx: number, tag: TagProps) {
48 | this.tagList.splice(idx, 1);
49 | this.cacheTabList.delete(tag.name);
50 | },
51 | addCache(name: string) {
52 | if (isString(name) && name !== '') this.cacheTabList.add(name);
53 | },
54 | deleteCache(tag: TagProps) {
55 | this.cacheTabList.delete(tag.name);
56 | },
57 | freshTabList(tags: TagProps[]) {
58 | this.tagList = tags;
59 | this.cacheTabList.clear();
60 | // 要先判断ignoreCache
61 | this.tagList
62 | .filter((el) => !el.ignoreCache)
63 | .map((el) => el.name)
64 | .forEach((x) => this.cacheTabList.add(x));
65 | },
66 | resetTabList() {
67 | this.tagList = [DEFAULT_ROUTE];
68 | this.cacheTabList.clear();
69 | this.cacheTabList.add(DEFAULT_ROUTE_NAME);
70 | },
71 | },
72 | });
73 |
74 | export default useAppStore;
75 |
--------------------------------------------------------------------------------
/src/store/modules/tab-bar/types.ts:
--------------------------------------------------------------------------------
1 | export interface TagProps {
2 | title: string;
3 | name: string;
4 | fullPath: string;
5 | query?: any;
6 | ignoreCache?: boolean;
7 | }
8 |
9 | export interface TabBarState {
10 | tagList: TagProps[];
11 | cacheTabList: Set;
12 | }
13 |
--------------------------------------------------------------------------------
/src/store/modules/user/index.ts:
--------------------------------------------------------------------------------
1 | import { defineStore } from 'pinia';
2 | import {
3 | login as userLogin,
4 | logout as userLogout,
5 | getUserInfo,
6 | LoginData,
7 | } from '@/api/user';
8 | import { setToken, clearToken } from '@/utils/auth';
9 | import { removeRouteListener } from '@/utils/route-listener';
10 | import { UserState } from './types';
11 | import useAppStore from '../app';
12 |
13 | const useUserStore = defineStore('user', {
14 | state: (): UserState => ({
15 | name: undefined,
16 | avatar: undefined,
17 | job: undefined,
18 | organization: undefined,
19 | location: undefined,
20 | email: undefined,
21 | introduction: undefined,
22 | personalWebsite: undefined,
23 | jobName: undefined,
24 | organizationName: undefined,
25 | locationName: undefined,
26 | phone: undefined,
27 | registrationDate: undefined,
28 | accountId: undefined,
29 | certification: undefined,
30 | role: '',
31 | showLogin: false,
32 | showRegister: false
33 | }),
34 |
35 | getters: {
36 | userInfo(state: UserState): UserState {
37 | return { ...state };
38 | },
39 | },
40 |
41 | actions: {
42 | switchRoles() {
43 | return new Promise((resolve) => {
44 | this.role = this.role === 'user' ? 'admin' : 'user';
45 | resolve(this.role);
46 | });
47 | },
48 | // Set user's information
49 | setInfo(partial: Partial) {
50 | this.$patch(partial);
51 | },
52 |
53 | // Reset user's information
54 | resetInfo() {
55 | this.$reset();
56 | },
57 |
58 | // Get user's information
59 | async info() {
60 | const res = await getUserInfo();
61 |
62 | this.setInfo(res.data);
63 | },
64 |
65 | // Login
66 | async login(loginForm: LoginData) {
67 | try {
68 | const res = await userLogin(loginForm);
69 | setToken(res.data.token);
70 | } catch (err) {
71 | clearToken();
72 | throw err;
73 | }
74 | },
75 | logoutCallBack() {
76 | const appStore = useAppStore();
77 | this.resetInfo();
78 | clearToken();
79 | removeRouteListener();
80 | appStore.clearServerMenu();
81 | },
82 | // Logout
83 | async logout() {
84 | // try {
85 | // await userLogout();
86 | // } finally {
87 | // this.logoutCallBack();
88 | // }
89 | this.logoutCallBack();
90 | },
91 | },
92 | });
93 |
94 | export default useUserStore;
95 |
--------------------------------------------------------------------------------
/src/store/modules/user/types.ts:
--------------------------------------------------------------------------------
1 | export type RoleType = '' | '*' | 'admin' | 'user';
2 | export interface UserState {
3 | name?: string;
4 | avatar?: string;
5 | job?: string;
6 | organization?: string;
7 | location?: string;
8 | email?: string;
9 | introduction?: string;
10 | personalWebsite?: string;
11 | jobName?: string;
12 | organizationName?: string;
13 | locationName?: string;
14 | phone?: string;
15 | registrationDate?: string;
16 | accountId?: string;
17 | certification?: number;
18 | role: RoleType;
19 | showLogin: boolean;
20 | showRegister: boolean;
21 | }
22 |
--------------------------------------------------------------------------------
/src/types/echarts.ts:
--------------------------------------------------------------------------------
1 | import { CallbackDataParams } from 'echarts/types/dist/shared';
2 |
3 | export interface ToolTipFormatterParams extends CallbackDataParams {
4 | axisDim: string;
5 | axisIndex: number;
6 | axisType: string;
7 | axisId: string;
8 | axisValue: string;
9 | axisValueLabel: string;
10 | }
11 |
--------------------------------------------------------------------------------
/src/types/global.ts:
--------------------------------------------------------------------------------
1 | export interface AnyObject {
2 | [key: string]: unknown;
3 | }
4 |
5 | export interface Options {
6 | value: unknown;
7 | label: string;
8 | }
9 |
10 | export interface NodeOptions extends Options {
11 | children?: NodeOptions[];
12 | }
13 |
14 | export interface GetParams {
15 | body: null;
16 | type: string;
17 | url: string;
18 | }
19 |
20 | export interface PostData {
21 | body: string;
22 | type: string;
23 | url: string;
24 | }
25 |
26 | export interface Pagination {
27 | current: number;
28 | pageSize: number;
29 | total?: number;
30 | }
31 |
32 | export type TimeRanger = [string, string];
33 |
34 | export interface GeneralChart {
35 | xAxis: string[];
36 | data: Array<{ name: string; value: number[] }>;
37 | }
38 |
--------------------------------------------------------------------------------
/src/types/mock.ts:
--------------------------------------------------------------------------------
1 | export interface MockParams {
2 | url: string;
3 | type: string;
4 | body: string;
5 | }
6 |
--------------------------------------------------------------------------------
/src/utils/AutoUpdate.js:
--------------------------------------------------------------------------------
1 |
2 | /**
3 | * 前端重新部署通知用户刷新网页
4 | */
5 |
6 | class Updater {
7 | oldScript = []; // 存储第一次值也就是script 的hash 信息
8 | newScript = []; // 获取新的值 也就是新的script 的hash信息
9 | dispatch = {}; // 小型发布订阅通知用户更新了
10 |
11 | constructor() {
12 | this.oldScript = [];
13 | this.newScript = [];
14 | this.dispatch = {};
15 | this.init(); // 初始化
16 | this.timing();
17 | }
18 |
19 | async init() {
20 | const html = await this.getHtml();
21 | this.oldScript = this.parserScript(html);
22 | };
23 |
24 | async getHtml() {
25 | const html = await fetch('/').then(res => res.text());//读取index html
26 | return html
27 | };
28 |
29 | parserScript(html) {
30 | const reg = new RegExp(/
14 |
15 |
20 |
21 |
92 |
93 |
107 |
--------------------------------------------------------------------------------
/src/views/dashboard/workplace/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.dashboard.workplace': 'Basic Information',
3 | 'workplace.welcome': 'Welcome~, click the "Connect" button in the upper right corner to connect the UV-K5.',
4 | 'workplace.welcomeSuc': 'Welcome~, connection successful.',
5 | 'workplace.info': 'Information',
6 | 'workplace.current': 'Current Firmware Version: ',
7 | 'workplace.writeconfig': 'Write Configuration: ',
8 | 'workplace.eepromSize': 'EEPROM Size: ',
9 | 'workplace.clickCheck': 'Click the TEST button to test',
10 | 'workplace.checkIt': 'TEST',
11 | 'workplace.unk': 'Unknown / Faulty / Unavailable',
12 | 'workplace.balance': 'Balance (CNY)',
13 | 'workplace.order.pending': 'Pending',
14 | 'workplace.order.pendingRenewal': 'Renewal Order',
15 | 'workplace.onlineContent': 'Online Content',
16 | 'workplace.putIn': 'Put In',
17 | 'workplace.newDay': 'Daily Additional Comments',
18 | 'workplace.newFromYesterday': 'New From Yesterday',
19 | 'workplace.minute': 'Min',
20 | 'workplace.docs': 'Documents',
21 | 'workplace.docs.productOverview': 'Product Overview',
22 | 'workplace.docs.userGuide': 'User Guide',
23 | 'workplace.docs.workflow': 'Workflow',
24 | 'workplace.docs.interfaceDocs': 'Interface Docs',
25 | //
26 | 'workplace.contentManagement': 'Content Management',
27 | 'workplace.contentStatistical': 'Content Statistical',
28 | 'workplace.advanced': 'Advanced',
29 | 'workplace.onlinePromotion': 'Online Promotion',
30 | 'workplace.contentPutIn': 'Put In',
31 | 'workplace.announcement': 'Announcement',
32 | 'workplace.recently.visited': 'Recently Visited',
33 | 'workplace.record.nodata': 'No data',
34 | 'workplace.quick.operation': 'Quick Operation',
35 | 'workplace.quickOperation.setup': 'Setup',
36 | 'workplace.allProject': 'All',
37 | 'workplace.loadMore': 'More',
38 | 'workplace.viewMore': 'More',
39 | 'workplace.contentData': 'Content Data',
40 | 'workplace.popularContent': 'Popular Content',
41 | 'workplace.popularContent.text': 'text',
42 | 'workplace.popularContent.image': 'image',
43 | 'workplace.popularContent.video': 'video',
44 | 'workplace.categoriesPercent': 'Categories Percent',
45 | 'workplace.pecs': 'pecs',
46 | };
47 |
--------------------------------------------------------------------------------
/src/views/dashboard/workplace/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.dashboard.workplace': '基础信息',
3 | 'workplace.welcome': '欢迎你~,点击右上角“连接”按钮连接手台。',
4 | 'workplace.welcomeSuc': '欢迎你~,连接成功!',
5 | 'workplace.info': '手台信息',
6 | 'workplace.current': '当前固件版本:',
7 | 'workplace.writeconfig': '匹配写频配置:',
8 | 'workplace.eepromSize': '存储大小:',
9 | 'workplace.clickCheck': '点击检测按钮检测',
10 | 'workplace.checkIt': '检测',
11 | 'workplace.unk': '未知、故障、不可用',
12 | 'workplace.balance': '余额(元)',
13 | 'workplace.order.pending': '待支付',
14 | 'workplace.order.pendingRenewal': '待续费订单',
15 | 'workplace.onlineContent': '线上总内容',
16 | 'workplace.putIn': '投放中内容',
17 | 'workplace.newDay': '日新增评论',
18 | 'workplace.newFromYesterday': '较昨日新增',
19 | 'workplace.minute': '分钟',
20 | 'workplace.docs': '帮助文档',
21 | 'workplace.docs.productOverview': '产品概要',
22 | 'workplace.docs.userGuide': '使用指南',
23 | 'workplace.docs.workflow': '接入流程',
24 | 'workplace.docs.interfaceDocs': '接口文档',
25 | 'workplace.contentManagement': '内容管理',
26 | 'workplace.contentStatistical': '内容分析',
27 | 'workplace.advanced': '高级管理',
28 | 'workplace.onlinePromotion': '线上推广',
29 | 'workplace.contentPutIn': '内容投放',
30 | 'workplace.announcement': '公告',
31 | 'workplace.recently.visited': '最近访问',
32 | 'workplace.record.nodata': '暂无数据',
33 | 'workplace.quick.operation': '快捷操作',
34 | 'workplace.quickOperation.setup': '管理',
35 | 'workplace.allProject': '所有项目',
36 | 'workplace.loadMore': '加载更多',
37 | 'workplace.viewMore': '查看更多',
38 | 'workplace.contentData': '内容数据',
39 | 'workplace.popularContent': '线上热门内容',
40 | 'workplace.popularContent.text': '文本',
41 | 'workplace.popularContent.image': '图片',
42 | 'workplace.popularContent.video': '视频',
43 | 'workplace.categoriesPercent': '内容类型占比',
44 | 'workplace.pecs': '个',
45 | };
46 |
--------------------------------------------------------------------------------
/src/views/guide/f117/assets/cj1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/src/views/guide/f117/assets/cj1.png
--------------------------------------------------------------------------------
/src/views/guide/f117/assets/cj2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/src/views/guide/f117/assets/cj2.png
--------------------------------------------------------------------------------
/src/views/guide/f117/assets/cj3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/silenty4ng/k5web/1bfc9a05ce3975d8fb24c6dee9aae6b0afc2b474/src/views/guide/f117/assets/cj3.png
--------------------------------------------------------------------------------
/src/views/guide/f117/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': 'Card List',
3 | 'cardList.tab.title.all': 'All',
4 | 'cardList.tab.title.content': 'Quality Inspection',
5 | 'cardList.tab.title.service': 'The service',
6 | 'cardList.tab.title.preset': 'Rules Preset',
7 | 'cardList.searchInput.placeholder': 'Search',
8 | 'cardList.enable': 'Enable',
9 | 'cardList.disable': 'Disable',
10 | 'cardList.content.delete': 'Delete',
11 | 'cardList.content.inspection': 'Inspection',
12 | 'cardList.content.action': 'Click Create Qc Content queue',
13 | 'cardList.service.open': 'Open',
14 | 'cardList.service.cancel': 'Cancel',
15 | 'cardList.service.renew': 'Contract of service',
16 | 'cardList.service.tag': 'Opened',
17 | 'cardList.service.expiresTag': 'Expired',
18 | 'cardList.preset.tag': 'Enable',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/guide/f117/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': '卡片列表',
3 | 'cardList.tab.title.all': '全部',
4 | 'cardList.tab.title.content': '内容质检',
5 | 'cardList.tab.title.service': '开通服务',
6 | 'cardList.tab.title.preset': '规则预置',
7 | 'cardList.searchInput.placeholder': '搜索',
8 | // 'cardList.statistic.enable': '已启用',
9 | // 'cardList.statistic.disable': '未启用',
10 | 'cardList.content.delete': '删除',
11 | 'cardList.content.inspection': '质检',
12 | 'cardList.content.action': '点击创建质检内容队列',
13 | 'cardList.service.open': '开通服务',
14 | 'cardList.service.cancel': '取消服务',
15 | 'cardList.service.renew': '续约服务',
16 | 'cardList.service.tag': '已开通',
17 | 'cardList.service.expiresTag': '已过期',
18 | 'cardList.preset.tag': '已启用',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/idea/losehu/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {{ $t('oi') }}https://github.com/losehu/uv-k5-firmware-custom
9 |
10 | {{ state.disName[item].get(subItem[0]) }}
12 |
13 |
14 |
{{ $t('diy.generate') }}
15 |
{{$t('global.download')}}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
144 |
145 |
150 |
151 |
--------------------------------------------------------------------------------
/src/views/list/card/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': 'Card List',
3 | 'cardList.tab.title.all': 'All',
4 | 'cardList.tab.title.content': 'Quality Inspection',
5 | 'cardList.tab.title.service': 'The service',
6 | 'cardList.tab.title.preset': 'Rules Preset',
7 | 'cardList.searchInput.placeholder': 'Search',
8 | 'cardList.enable': 'Enable',
9 | 'cardList.disable': 'Disable',
10 | 'cardList.content.delete': 'Delete',
11 | 'cardList.content.inspection': 'Inspection',
12 | 'cardList.content.action': 'Click Create Qc Content queue',
13 | 'cardList.service.open': 'Open',
14 | 'cardList.service.cancel': 'Cancel',
15 | 'cardList.service.renew': 'Contract of service',
16 | 'cardList.service.tag': 'Opened',
17 | 'cardList.service.expiresTag': 'Expired',
18 | 'cardList.preset.tag': 'Enable',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/card/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': '卡片列表',
3 | 'cardList.tab.title.all': '全部',
4 | 'cardList.tab.title.content': '内容质检',
5 | 'cardList.tab.title.service': '开通服务',
6 | 'cardList.tab.title.preset': '规则预置',
7 | 'cardList.searchInput.placeholder': '搜索',
8 | // 'cardList.statistic.enable': '已启用',
9 | // 'cardList.statistic.disable': '未启用',
10 | 'cardList.content.delete': '删除',
11 | 'cardList.content.inspection': '质检',
12 | 'cardList.content.action': '点击创建质检内容队列',
13 | 'cardList.service.open': '开通服务',
14 | 'cardList.service.cancel': '取消服务',
15 | 'cardList.service.renew': '续约服务',
16 | 'cardList.service.tag': '已开通',
17 | 'cardList.service.expiresTag': '已过期',
18 | 'cardList.preset.tag': '已启用',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/chat/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 【无法正常使用】【开发中!!!】无线电聊天(需使用这个固件)
7 |
8 |
9 |
呼号:
10 |
11 |
12 |
设备号:
13 |
15 |
16 |
{{ state.startChat }}
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
{{ item.callsign }}({{
25 | item.devid.toString().padStart(2, "0") }})
>
26 |
{{ item.content }}
27 |
28 |
29 |
30 |
31 |
32 |
33 | 发送
34 |
35 |
36 |
37 |
38 |
39 |
40 |
193 |
194 |
199 |
200 |
--------------------------------------------------------------------------------
/src/views/list/chi/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | {state.showHide += 1}">{{ $t('menu.font') + $t('global.onStart') }}
9 |
10 |
11 |
12 |
13 | {{ $t('tool.fontwrite') }}
14 |
15 |
16 | {{$t('tool.Simplified_Chinese')}}
17 | {{$t('tool.Traditional_Chinese')}}
18 |
19 |
20 |
21 |
22 |
{{ $t('tool.writefontwrite') }}
23 |
{{ $t('tool.writefontwrite') }}
24 |
25 |
26 |
27 |
28 | {{ $t('tool.pinyinwrite') }}
29 |
30 |
31 | {{ $t('tool.writepinyin') }}
32 |
33 |
34 |
35 |
36 | {{ $t('tool.ssbpatch') }}
37 |
38 |
39 | {{ $t('tool.writessbpatch') }}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
179 |
180 |
185 |
186 |
230 |
--------------------------------------------------------------------------------
/src/views/list/chi/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': 'Card List',
3 | 'cardList.tab.title.all': 'All',
4 | 'cardList.tab.title.content': 'Quality Inspection',
5 | 'cardList.tab.title.service': 'The service',
6 | 'cardList.tab.title.preset': 'Rules Preset',
7 | 'cardList.searchInput.placeholder': 'Search',
8 | 'cardList.enable': 'Enable',
9 | 'cardList.disable': 'Disable',
10 | 'cardList.content.delete': 'Delete',
11 | 'cardList.content.inspection': 'Inspection',
12 | 'cardList.content.action': 'Click Create Qc Content queue',
13 | 'cardList.service.open': 'Open',
14 | 'cardList.service.cancel': 'Cancel',
15 | 'cardList.service.renew': 'Contract of service',
16 | 'cardList.service.tag': 'Opened',
17 | 'cardList.service.expiresTag': 'Expired',
18 | 'cardList.preset.tag': 'Enable',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/chi/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': '卡片列表',
3 | 'cardList.tab.title.all': '全部',
4 | 'cardList.tab.title.content': '内容质检',
5 | 'cardList.tab.title.service': '开通服务',
6 | 'cardList.tab.title.preset': '规则预置',
7 | 'cardList.searchInput.placeholder': '搜索',
8 | // 'cardList.statistic.enable': '已启用',
9 | // 'cardList.statistic.disable': '未启用',
10 | 'cardList.content.delete': '删除',
11 | 'cardList.content.inspection': '质检',
12 | 'cardList.content.action': '点击创建质检内容队列',
13 | 'cardList.service.open': '开通服务',
14 | 'cardList.service.cancel': '取消服务',
15 | 'cardList.service.renew': '续约服务',
16 | 'cardList.service.tag': '已开通',
17 | 'cardList.service.expiresTag': '已过期',
18 | 'cardList.preset.tag': '已启用',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/flash/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | {{ state.binaryFile ? state.binaryName : $t('tool.selectFirmware') }}
11 | {{ $t('tool.flash') }}
12 |
13 |
14 |
15 |
16 | Official
17 |
18 |
19 |
20 |
21 |
22 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
143 |
144 |
149 |
150 |
200 |
--------------------------------------------------------------------------------
/src/views/list/flash/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': 'Card List',
3 | 'cardList.tab.title.all': 'All',
4 | 'cardList.tab.title.content': 'Quality Inspection',
5 | 'cardList.tab.title.service': 'The service',
6 | 'cardList.tab.title.preset': 'Rules Preset',
7 | 'cardList.searchInput.placeholder': 'Search',
8 | 'cardList.enable': 'Enable',
9 | 'cardList.disable': 'Disable',
10 | 'cardList.content.delete': 'Delete',
11 | 'cardList.content.inspection': 'Inspection',
12 | 'cardList.content.action': 'Click Create Qc Content queue',
13 | 'cardList.service.open': 'Open',
14 | 'cardList.service.cancel': 'Cancel',
15 | 'cardList.service.renew': 'Contract of service',
16 | 'cardList.service.tag': 'Opened',
17 | 'cardList.service.expiresTag': 'Expired',
18 | 'cardList.preset.tag': 'Enable',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/flash/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': '卡片列表',
3 | 'cardList.tab.title.all': '全部',
4 | 'cardList.tab.title.content': '内容质检',
5 | 'cardList.tab.title.service': '开通服务',
6 | 'cardList.tab.title.preset': '规则预置',
7 | 'cardList.searchInput.placeholder': '搜索',
8 | // 'cardList.statistic.enable': '已启用',
9 | // 'cardList.statistic.disable': '未启用',
10 | 'cardList.content.delete': '删除',
11 | 'cardList.content.inspection': '质检',
12 | 'cardList.content.action': '点击创建质检内容队列',
13 | 'cardList.service.open': '开通服务',
14 | 'cardList.service.cancel': '取消服务',
15 | 'cardList.service.renew': '续约服务',
16 | 'cardList.service.tag': '已开通',
17 | 'cardList.service.expiresTag': '已过期',
18 | 'cardList.preset.tag': '已启用',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/image/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': 'Card List',
3 | 'cardList.tab.title.all': 'All',
4 | 'cardList.tab.title.content': 'Quality Inspection',
5 | 'cardList.tab.title.service': 'The service',
6 | 'cardList.tab.title.preset': 'Rules Preset',
7 | 'cardList.searchInput.placeholder': 'Search',
8 | 'cardList.enable': 'Enable',
9 | 'cardList.disable': 'Disable',
10 | 'cardList.content.delete': 'Delete',
11 | 'cardList.content.inspection': 'Inspection',
12 | 'cardList.content.action': 'Click Create Qc Content queue',
13 | 'cardList.service.open': 'Open',
14 | 'cardList.service.cancel': 'Cancel',
15 | 'cardList.service.renew': 'Contract of service',
16 | 'cardList.service.tag': 'Opened',
17 | 'cardList.service.expiresTag': 'Expired',
18 | 'cardList.preset.tag': 'Enable',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/image/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': '卡片列表',
3 | 'cardList.tab.title.all': '全部',
4 | 'cardList.tab.title.content': '内容质检',
5 | 'cardList.tab.title.service': '开通服务',
6 | 'cardList.tab.title.preset': '规则预置',
7 | 'cardList.searchInput.placeholder': '搜索',
8 | // 'cardList.statistic.enable': '已启用',
9 | // 'cardList.statistic.disable': '未启用',
10 | 'cardList.content.delete': '删除',
11 | 'cardList.content.inspection': '质检',
12 | 'cardList.content.action': '点击创建质检内容队列',
13 | 'cardList.service.open': '开通服务',
14 | 'cardList.service.cancel': '取消服务',
15 | 'cardList.service.renew': '续约服务',
16 | 'cardList.service.tag': '已开通',
17 | 'cardList.service.expiresTag': '已过期',
18 | 'cardList.preset.tag': '已启用',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/sat/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': 'Card List',
3 | 'cardList.tab.title.all': 'All',
4 | 'cardList.tab.title.content': 'Quality Inspection',
5 | 'cardList.tab.title.service': 'The service',
6 | 'cardList.tab.title.preset': 'Rules Preset',
7 | 'cardList.searchInput.placeholder': 'Search',
8 | 'cardList.enable': 'Enable',
9 | 'cardList.disable': 'Disable',
10 | 'cardList.content.delete': 'Delete',
11 | 'cardList.content.inspection': 'Inspection',
12 | 'cardList.content.action': 'Click Create Qc Content queue',
13 | 'cardList.service.open': 'Open',
14 | 'cardList.service.cancel': 'Cancel',
15 | 'cardList.service.renew': 'Contract of service',
16 | 'cardList.service.tag': 'Opened',
17 | 'cardList.service.expiresTag': 'Expired',
18 | 'cardList.preset.tag': 'Enable',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/sat/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': '卡片列表',
3 | 'cardList.tab.title.all': '全部',
4 | 'cardList.tab.title.content': '内容质检',
5 | 'cardList.tab.title.service': '开通服务',
6 | 'cardList.tab.title.preset': '规则预置',
7 | 'cardList.searchInput.placeholder': '搜索',
8 | // 'cardList.statistic.enable': '已启用',
9 | // 'cardList.statistic.disable': '未启用',
10 | 'cardList.content.delete': '删除',
11 | 'cardList.content.inspection': '质检',
12 | 'cardList.content.action': '点击创建质检内容队列',
13 | 'cardList.service.open': '开通服务',
14 | 'cardList.service.cancel': '取消服务',
15 | 'cardList.service.renew': '续约服务',
16 | 'cardList.service.tag': '已开通',
17 | 'cardList.service.expiresTag': '已过期',
18 | 'cardList.preset.tag': '已启用',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/satloc/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 请点击网页“已扫码上传”按钮
5 | Please click on the "Scanned and Uploaded" button on PC page.
6 |
7 |
8 |
9 | 获取信息(Information)
10 |
11 |
12 |
13 | 台站经度(Longitude):{{ state.lng }}
14 |
15 |
16 |
17 | 台站纬度(Latitude):{{ state.lat }}
18 |
19 |
20 |
21 | 台站海拔(Altitude):{{ state.alt }}
22 |
23 |
24 |
上传(Upload)
25 |
26 |
27 |
28 |
29 |
76 |
77 |
82 |
83 |
--------------------------------------------------------------------------------
/src/views/list/search-table/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.searchTable': 'Search Table',
3 | 'searchTable.form.number': 'Set Number',
4 | 'searchTable.form.number.placeholder': 'Please enter Set Number',
5 | 'searchTable.form.name': 'Set Name',
6 | 'searchTable.form.name.placeholder': 'Please enter Set Name',
7 | 'searchTable.form.contentType': 'Content Type',
8 | 'searchTable.form.contentType.img': 'image-text',
9 | 'searchTable.form.contentType.horizontalVideo': 'Horizontal short video',
10 | 'searchTable.form.contentType.verticalVideo': 'Vertical short video',
11 | 'searchTable.form.filterType': 'Filter Type',
12 | 'searchTable.form.filterType.artificial': 'artificial',
13 | 'searchTable.form.filterType.rules': 'Rules',
14 | 'searchTable.form.createdTime': 'Create Date',
15 | 'searchTable.form.status': 'Status',
16 | 'searchTable.form.status.online': 'Online',
17 | 'searchTable.form.status.offline': 'Offline',
18 | 'searchTable.form.search': 'Search',
19 | 'searchTable.form.reset': 'Reset',
20 | 'searchTable.form.selectDefault': 'All',
21 | 'searchTable.operation.create': 'Create',
22 | 'searchTable.operation.import': 'Import',
23 | 'searchTable.operation.download': 'Download',
24 | // columns
25 | 'searchTable.columns.index': '#',
26 | 'searchTable.columns.number': 'Set Number',
27 | 'searchTable.columns.name': 'Set Name',
28 | 'searchTable.columns.contentType': 'Content Type',
29 | 'searchTable.columns.filterType': 'Filter Type',
30 | 'searchTable.columns.count': 'Count',
31 | 'searchTable.columns.createdTime': 'CreatedTime',
32 | 'searchTable.columns.status': 'Status',
33 | 'searchTable.columns.operations': 'Operations',
34 | 'searchTable.columns.operations.view': 'View',
35 | // size
36 | 'searchTable.size.mini': 'mini',
37 | 'searchTable.size.small': 'small',
38 | 'searchTable.size.medium': 'middle',
39 | 'searchTable.size.large': 'large',
40 | // actions
41 | 'searchTable.actions.refresh': 'refresh',
42 | 'searchTable.actions.density': 'density',
43 | 'searchTable.actions.columnSetting': 'columnSetting',
44 | };
45 |
--------------------------------------------------------------------------------
/src/views/list/search-table/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.searchTable': '查询表格',
3 | 'searchTable.form.number': '集合编号',
4 | 'searchTable.form.number.placeholder': '请输入集合编号',
5 | 'searchTable.form.name': '集合名称',
6 | 'searchTable.form.name.placeholder': '请输入集合名称',
7 | 'searchTable.form.contentType': '内容体裁',
8 | 'searchTable.form.contentType.img': '图文',
9 | 'searchTable.form.contentType.horizontalVideo': '横版短视频',
10 | 'searchTable.form.contentType.verticalVideo': '竖版小视频',
11 | 'searchTable.form.filterType': '筛选方式',
12 | 'searchTable.form.filterType.artificial': '人工筛选',
13 | 'searchTable.form.filterType.rules': '规则筛选',
14 | 'searchTable.form.createdTime': '创建时间',
15 | 'searchTable.form.status': '状态',
16 | 'searchTable.form.status.online': '已上线',
17 | 'searchTable.form.status.offline': '已下线',
18 | 'searchTable.form.search': '查询',
19 | 'searchTable.form.reset': '重置',
20 | 'searchTable.form.selectDefault': '全部',
21 | 'searchTable.operation.create': '新建',
22 | 'searchTable.operation.import': '批量导入',
23 | 'searchTable.operation.download': '下载',
24 | // columns
25 | 'searchTable.columns.index': '#',
26 | 'searchTable.columns.number': '集合编号',
27 | 'searchTable.columns.name': '集合名称',
28 | 'searchTable.columns.contentType': '内容体裁',
29 | 'searchTable.columns.filterType': '筛选方式',
30 | 'searchTable.columns.count': '内容量',
31 | 'searchTable.columns.createdTime': '创建时间',
32 | 'searchTable.columns.status': '状态',
33 | 'searchTable.columns.operations': '操作',
34 | 'searchTable.columns.operations.view': '查看',
35 |
36 | // size
37 | 'searchTable.size.mini': '迷你',
38 | 'searchTable.size.small': '偏小',
39 | 'searchTable.size.medium': '中等',
40 | 'searchTable.size.large': '偏大',
41 | // actions
42 | 'searchTable.actions.refresh': '刷新',
43 | 'searchTable.actions.density': '密度',
44 | 'searchTable.actions.columnSetting': '列设置',
45 | };
46 |
--------------------------------------------------------------------------------
/src/views/list/settings/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | {{ $t('cps.onDeviceRead') }}
15 |
16 |
17 |
18 |
19 |
20 | {{ $t('cps.onDeviceWrite') }}
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
183 |
184 |
189 |
190 |
234 |
--------------------------------------------------------------------------------
/src/views/list/settings/locale/en-US.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': 'Card List',
3 | 'cardList.tab.title.all': 'All',
4 | 'cardList.tab.title.content': 'Quality Inspection',
5 | 'cardList.tab.title.service': 'The service',
6 | 'cardList.tab.title.preset': 'Rules Preset',
7 | 'cardList.searchInput.placeholder': 'Search',
8 | 'cardList.enable': 'Enable',
9 | 'cardList.disable': 'Disable',
10 | 'cardList.content.delete': 'Delete',
11 | 'cardList.content.inspection': 'Inspection',
12 | 'cardList.content.action': 'Click Create Qc Content queue',
13 | 'cardList.service.open': 'Open',
14 | 'cardList.service.cancel': 'Cancel',
15 | 'cardList.service.renew': 'Contract of service',
16 | 'cardList.service.tag': 'Opened',
17 | 'cardList.service.expiresTag': 'Expired',
18 | 'cardList.preset.tag': 'Enable',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/list/settings/locale/zh-CN.ts:
--------------------------------------------------------------------------------
1 | export default {
2 | 'menu.list.cardList': '卡片列表',
3 | 'cardList.tab.title.all': '全部',
4 | 'cardList.tab.title.content': '内容质检',
5 | 'cardList.tab.title.service': '开通服务',
6 | 'cardList.tab.title.preset': '规则预置',
7 | 'cardList.searchInput.placeholder': '搜索',
8 | // 'cardList.statistic.enable': '已启用',
9 | // 'cardList.statistic.disable': '未启用',
10 | 'cardList.content.delete': '删除',
11 | 'cardList.content.inspection': '质检',
12 | 'cardList.content.action': '点击创建质检内容队列',
13 | 'cardList.service.open': '开通服务',
14 | 'cardList.service.cancel': '取消服务',
15 | 'cardList.service.renew': '续约服务',
16 | 'cardList.service.tag': '已开通',
17 | 'cardList.service.expiresTag': '已过期',
18 | 'cardList.preset.tag': '已启用',
19 | };
20 |
--------------------------------------------------------------------------------
/src/views/not-found/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
19 |
20 |
31 |
--------------------------------------------------------------------------------
/src/views/redirect/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/src/views/thanks/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | 如有漏记可以联系我微信 silenty4ng 补充,另外感谢泉盛送的无线电设备;傅总送的无线电设备、天线、茶叶;小胖.log送的无线电设备;拓朋送的无线电设备。
9 |
10 |
11 |
12 |
13 |
14 |
15 |
94 |
95 |
100 |
101 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2020",
4 | "module": "ES2020",
5 | "moduleResolution": "node",
6 | "strict": true,
7 | "jsx": "preserve",
8 | "sourceMap": true,
9 | "resolveJsonModule": true,
10 | "esModuleInterop": true,
11 | "baseUrl": ".",
12 | "paths": {
13 | "@/*": ["src/*"]
14 | },
15 | "lib": ["es2020", "dom"],
16 | "skipLibCheck": true
17 | },
18 | "include": ["src/**/*", "src/**/*.vue"],
19 | "exclude": ["node_modules"]
20 | }
21 |
--------------------------------------------------------------------------------