Merge branch 'silenty4ng:master' into master

This commit is contained in:
sysuid 2024-06-18 20:16:44 +08:00 committed by GitHub
commit 9246d91aeb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 1324 additions and 16979 deletions

View file

@ -7,6 +7,12 @@
K5Web 用于对兼容业余无线电台 UV-K5 写频、更新固件、写入星历等。
## 讨论
- QQ 群957225277 K5Web相关
- QQ 群201308015 (固件相关)
- Telegram Group: https://t.me/losehu
- Matrix Group: https://matrix.to/#/#losehu:mozilla.org
## 功能列表
- 固件版本检测
@ -19,6 +25,9 @@ K5Web 用于对兼容业余无线电台 UV-K5 写频、更新固件、写入星
- 开机图片LOSEHU 固件)
- 字库写入LOSEHU 固件)
- 星历写入LOSEHU 固件)
- DTMF ID 设置
- 收音机频道管理
- MDC 联系人管理LOSEHU 固件)
## 开发
### 安装依赖

View file

@ -2,7 +2,6 @@
<html lang="zh-cmn">
<head>
<meta charset="UTF-8" />
<link rel="shortcut icon" type="image/x-icon" href="https://unpkg.byted-static.com/latest/byted/arco-config/assets/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>K5Web</title>
<style>

16928
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -37,6 +37,7 @@
"vue": "^3.2.40",
"vue-echarts": "^6.2.3",
"vue-i18n": "^9.2.2",
"vue-matomo": "^4.2.0",
"vue-router": "^4.0.14",
"xlsx": "^0.18.5"
},

Binary file not shown.

Before

Width:  |  Height:  |  Size: 236 KiB

After

Width:  |  Height:  |  Size: 44 KiB

BIN
public/new_font_k_f.bin Normal file

Binary file not shown.

View file

@ -1,12 +1,12 @@
<template>
<div v-if="isWeixin || isQQ" style="text-align: center;">
<div v-if="(isWeixin || isQQ) && route.path !== '/satloc'" style="text-align: center;">
<div style="height: 75vh; display: flex; flex-direction: column; align-items: center;">
<div style="padding: 20px; padding-top: 35vh; font-size: 1.5rem;">如需浏览请长按网址复制后使用浏览器访问</div>
<p style="padding: 20px; background-color: #F1F1F1;">{{ link }}</p>
</div>
<div style="color: #AAAAAA;">{{ ua }}</div>
</div>
<t-config-provider v-if="reloadLang && !isWeixin && !isQQ" :global-config="locale">
<t-config-provider v-if="reloadLang && !((isWeixin || isQQ) && route.path !== '/satloc')" :global-config="locale">
<a-config-provider :locale="locale">
<router-view />
<global-setting />
@ -25,6 +25,9 @@
import Aegis from 'aegis-web-sdk';
import { encodingIndexes } from "@zxing/text-encoding/es2015/encoding-indexes";
import { TextEncoder, TextDecoder } from "@zxing/text-encoding";
import { useRoute } from 'vue-router';
const route = useRoute();
window.TextEncodingIndexes = { encodingIndexes: encodingIndexes };
window.TextEncoder = TextEncoder;
@ -39,10 +42,6 @@
spa: true, // spa pv
hostUrl: 'https://rumt-zh.com'
});
const shynet = document.createElement('script');
shynet.defer;
shynet.src = "https://shynet.vicicode.com/ingress/4c1dcea4-75c5-45e2-a641-25f211adbad6/script.js";
document.body.append(shynet);
}
const { currentLocale } = useLocale();

View file

@ -1,6 +1,6 @@
<template>
<a-layout-footer class="footer">
<a href="https://github.com/silenty4ng/k5web" target="_blank">K5Web - V0.1.202405130020</a>
<t-link href="https://github.com/silenty4ng/k5web" target="_blank">K5Web - V0.1.202406100220</t-link>
</a-layout-footer>
</template>

View file

@ -24,7 +24,7 @@
<t-link href="https://txc.qq.com/products/647342" target="_blank">{{ $t('navbar.qa') }}</t-link>
</li>
<li>
<a-button type="primary" @click="connectIt">{{ appStore.connectState ? $t('navbar.disconnect') : $t('navbar.connect') }}</a-button>
<a-button v-show="route.path !== '/tool/flash'" type="primary" @click="connectIt">{{ appStore.connectState ? $t('navbar.disconnect') : $t('navbar.connect') }}</a-button>
</li>
<li>
<a-tooltip :content="$t('settings.language')">
@ -226,6 +226,11 @@
const configuration_list : any = {
"LOSEHU.*P.*K" : "ltsk.json",
"LOSEHU.*P.*" : "lts.json",
"LOSEHU13[0-9].*HS" : "losehu124h.json",
"LOSEHU13[0-9].*H" : "losehu124h.json",
"LOSEHU13[0-9].*KS" : "losehu120k.json",
"LOSEHU13[0-9].*K" : "losehu120k.json",
"LOSEHU13[0-9].*" : "losehu118.json",
"LOSEHU12[4-9].*HS" : "losehu124h.json",
"LOSEHU12[4-9].*H" : "losehu124h.json",
"LOSEHU12[0-3].*H" : "losehu118h.json",
@ -282,7 +287,8 @@
"H": false,
"localmdc": false,
"sat": false,
"newpinyin": false
"newpinyin": false,
'fm30': false
}
Object.keys(configuration_list).some(e=>{

View file

@ -3,5 +3,6 @@
"uart": "losehu",
"charset": "losehu",
"K": true,
"localmdc": true
"localmdc": true,
"fm30": true
}

View file

@ -112,9 +112,11 @@ export default {
'tool.flash': 'FLASH',
'tool.selectImage': 'Select Image',
'tool.write': 'Write to device',
'tool.fontwrite': 'LOSEHU Firmware Character Set Write',
'tool.pinyinwrite': 'LOSEHU H Firmware Pinyin Set Write',
'tool.fontwrite': 'LOSEHU Chinese Character Set Write',
'tool.pinyinwrite': 'LOSEHU H Chinese Pinyin Set Write',
'tool.writefontwrite': 'Character Set Write',
'tool.Simplified_Chinese': 'CHS',
'tool.Traditional_Chinese': 'CHT',
'tool.writepinyin': 'Pinyin Set Write',
'tool.brtime': 'Browser Time',
'tool.selectSatellite': 'Select satellite',
@ -137,7 +139,8 @@ export default {
'global.nosupport': 'Current browser does not support WebSerial function, please use Chrome, Edge, Opera browser.',
'global.connectFail': 'Connect Fail',
'menu.workshop': 'Workshop',
'menu.firmware': 'Firmware',
'menu.firmware': 'Firmware Store',
'menu.channel': 'Channel Share',
'global.use': 'Use',
'tool.ssbpatch': 'LOSEHU S Firmware SI4732 SSB Patch',
'tool.writessbpatch': 'SSB Patch Write',
@ -149,6 +152,13 @@ export default {
'global.password2': 'Retype password ',
'image.negative': 'Negative',
'workplace.clickNotice': ' (Official firmware can only detect 8KB/64Kbit)',
'menu.cps.radio': 'Radio',
'menu.cps.mdc': 'MDC Contact',
'cps.contact': 'Name',
'cps.mdcid': 'MDC ID',
'idea.diy': 'LOSEHU DIY',
'diy.generate': 'Generate',
'cps.dtmfid': 'DTMF ID',
...localeSettings,
...localeMessageBox,
...localeLogin,

View file

@ -115,6 +115,8 @@ export default {
'tool.fontwrite': 'LOSEHU 固件字库写入',
'tool.pinyinwrite': 'LOSEHU H 版固件拼音索引表',
'tool.writefontwrite': '自动写入字库',
'tool.Simplified_Chinese': '简体',
'tool.Traditional_Chinese': '繁体',
'tool.writepinyin': '写入拼音检索表',
'tool.brtime': '浏览器时间',
'tool.selectSatellite': '选择卫星',
@ -138,6 +140,7 @@ export default {
'global.connectFail': '连接失败',
'menu.workshop': '创意工坊',
'menu.firmware': '固件市场',
'menu.channel': '信道分享',
'global.use': '使用',
'tool.ssbpatch': 'LOSEHU S 版固件 SI4732 单边带补丁',
'tool.writessbpatch': '写入单边带补丁',
@ -149,6 +152,13 @@ export default {
'global.password2': '请再次输入密码',
'image.negative': '反色',
'workplace.clickNotice': '(官方固件只能检测 8KB/64Kbit',
'menu.cps.radio': '收音机',
'menu.cps.mdc': 'MDC 联系人',
'cps.contact': '联系人',
'cps.mdcid': 'MDC ID',
'idea.diy': '自定义萝卜固件',
'diy.generate': '生成',
'cps.dtmfid': 'DTMF ID',
...localeSettings,
...localeMessageBox,
...localeLogin,

View file

@ -13,6 +13,18 @@ import App from './App.vue';
import '@/assets/style/global.less';
import '@/api/interceptor';
import 'tdesign-vue-next/es/style/index.css';
import Updater from "./utils/AutoUpdate.js";
import VueMatomo from 'vue-matomo';
const AutoUpdate = new Updater()
AutoUpdate.on('update',()=>{
setTimeout(async()=>{
const result = confirm('当前网站有更新,请点击确定刷新页面体验');
if(result){
location.reload();
}
},500)
})
const app = createApp(App);
@ -24,4 +36,12 @@ app.use(i18n);
app.use(globalComponents);
app.use(directive);
if(location.hostname == 'k5.vicicode.com' || location.hostname == 'k5.lhw711.cn' || location.hostname == 'mm.md' || location.hostname == 'k5.mm.md'){
app.use(VueMatomo, {
host: '//analytics.vicicode.com',
siteId: 2,
router: router
})
}
app.mount('#app');

View file

@ -32,6 +32,26 @@ const DASHBOARD: AppRouteRecordRaw = {
roles: ['*'],
},
},
{
path: 'radio',
name: 'Radio',
component: () => import('@/views/list/radio/index.vue'),
meta: {
locale: 'menu.cps.radio',
requiresAuth: true,
roles: ['*'],
},
},
{
path: 'mdc',
name: 'Mdc',
component: () => import('@/views/list/mdc/index.vue'),
meta: {
locale: 'menu.cps.mdc',
requiresAuth: true,
roles: ['*'],
},
},
{
path: 'settings',
name: 'Settings',

View file

@ -31,7 +31,27 @@ const IDEA: AppRouteRecordRaw = {
requiresAuth: true,
roles: ['*'],
},
}
},
{
path: 'channel',
name: 'ideaChannel',
component: () => import('@/views/idea/channel/index.vue'),
meta: {
locale: 'menu.channel',
requiresAuth: true,
roles: ['*'],
},
},
{
path: 'losehu',
name: 'ideaLosehu',
component: () => import('@/views/idea/losehu/index.vue'),
meta: {
locale: 'idea.diy',
requiresAuth: true,
roles: ['*'],
},
},
],
};

66
src/utils/AutoUpdate.js Normal file
View file

@ -0,0 +1,66 @@
/**
* 前端重新部署通知用户刷新网页
*/
class Updater {
oldScript = []; // 存储第一次值也就是script 的hash 信息
newScript = []; // 获取新的值 也就是新的script 的hash信息
dispatch = {}; // 小型发布订阅通知用户更新了
constructor() {
this.oldScript = [];
this.newScript = [];
this.dispatch = {};
this.init(); // 初始化
this.timing();
}
async init() {
const html = await this.getHtml();
this.oldScript = this.parserScript(html);
};
async getHtml() {
const html = await fetch('/').then(res => res.text());//读取index html
return html
};
parserScript(html) {
const reg = new RegExp(/<script(?:\s+[^>]*)?>(.*?)<\/script\s*>/ig) //script正则
return html.match(reg) //匹配script标签
}
//发布订阅通知
on(key, fn) {
(this.dispatch[key] || (this.dispatch[key] = [])).push(fn)
return this;
}
compare(oldArr, newArr) {
const base = oldArr.length;
const arr = Array.from(new Set(oldArr.concat(newArr)));
//如果新旧length 一样无更新
if (arr.length === base) {
// this.dispatch['no-update'].forEach(fn => {
// fn();
// })
} else {
// 否则通知更新
this.dispatch['update'].forEach(fn => {
fn();
})
}
};
async timing() {
setInterval(async () => {
const newHtml = await this.getHtml();
this.newScript = this.parserScript(newHtml);
this.compare(this.oldScript, this.newScript)
//这边给的是默认值15000,也可以自定义秒数
}, 15000);
};
}
export default Updater;

View file

@ -14,7 +14,16 @@
</a-card>
</a-space>
<div>
<img style="margin-bottom: 10px;" width="600px" src="/gy.png" />
<a-typography-title :heading="5">说明</a-typography-title>
<a-typography-text> 使用应第一时间<t-link theme="primary" href="/#/tool/backup">备份</t-link>配置及校准数据</a-typography-text><br>
<a-typography-text> 固件升级功能手台应处于刷机模式点击更新按钮选择设备更新其余功能手台均需要在正常模式连接</a-typography-text><br>
<a-typography-text> 萝卜LOSEHU固件相关问题请移步<t-link theme="primary" href="https://github.com/losehu/uv-k5-firmware-custom" target="_blank">https://github.com/losehu/uv-k5-firmware-custom</t-link> </a-typography-text><br>
<a-typography-text> K5Web 使用视频教程BG7QJV<t-link theme="primary" href="https://www.douyin.com/video/7378314511419313458" target="_blank">https://www.douyin.com/video/7378314511419313458</t-link> </a-typography-text><br>
<a-typography-text> K5Web 使用视频教程BG3ODZ<t-link theme="primary" href="https://www.bilibili.com/video/BV1Q4421D75x" target="_blank">https://www.bilibili.com/video/BV1Q4421D75x</t-link> </a-typography-text>
</div>
<div>
<a-typography-title :heading="5">希望工程1+1助学行动</a-typography-title>
<img class="tencent" style="margin-bottom: 10px;" width="200px" src="/gy.png" />
</div>
</a-col>
</template>
@ -102,4 +111,10 @@
:deep(.arco-icon-home) {
margin-right: 6px;
}
body[arco-theme='dark'] {
.tencent {
filter: invert(1) hue-rotate(180deg);
}
}
</style>

View file

@ -0,0 +1,263 @@
<template>
<div class="container">
<Breadcrumb :items="[$t('menu.workshop'), $t('menu.channel')]" />
<a-row :gutter="20" align="stretch">
<a-col :span="24">
<a-card class="general-card" :title="$t('menu.channel')">
<template #extra>
<div style="margin-right: 20px;">
<template v-if="userStore.name">
<a-link @click="showPanel">&nbsp;&nbsp;{{ userStore.name }}&nbsp;&nbsp;</a-link>
<a-link @click="userStore.logout()">&nbsp;&nbsp;{{ $t('global.logout') }}&nbsp;&nbsp;</a-link>
</template>
<template v-else>
<a-link @click="userStore.setInfo({ showLogin: true })">&nbsp;&nbsp;{{ $t('global.login') }}&nbsp;&nbsp;</a-link>
<a-link @click="userStore.setInfo({ showRegister: true })">&nbsp;&nbsp;{{ $t('global.register') }}&nbsp;&nbsp;</a-link>
</template>
</div>
</template>
<a-list>
<a-list-item style="width: 100%;" v-for="item in state.nowpage">
<a-list-item-meta
:description="item.desc"
>
<template #title>
<t-tag theme="primary" variant="outline">{{ item.upload }}</t-tag> {{ item.title }}
</template>
</a-list-item-meta>
<template #actions>
<a-link @click="onStar(item.id)">👍</a-link>
<a-link @click="useFirmware('https://k5.vicicode.com/wsapi/download?id=' + item.id)">{{$t('global.use')}}</a-link>
</template>
</a-list-item>
</a-list>
<t-pagination @change="loadit" style="margin: 10px;" :total="state.total" :current="state.page" :pageSize="12" showPageNumber :showPageSize="false" />
</a-card>
</a-col>
</a-row>
<t-drawer v-model:visible="state.showPanel" size="50%" header="我的分享" :footer="false">
<div style="display: flex; align-items: center; justify-content: space-between;">
<t-button style="margin: 10px" @click="showUpload">上传新分享</t-button>
<t-button :loading="state.refLoading" shape="circle" theme="outline" @click="refit">
<template #icon> <RefreshIcon /> </template>
</t-button>
</div>
<t-list :split="true">
<t-list-item v-for="item in state.myList">
<div style="display: flex; width: 100%;">
<div style="width: 90%;">
<t-tag theme="primary" variant="outline">{{ item.audit ? '已审核' : '审核中' }}</t-tag>
{{ item.title }}
<br>
{{ item.desc }}
</div>
<div style="width: 10%; margin: auto; text-align: center;">
<t-link theme="primary" hover="color" @click="onDT(item.id)">删除</t-link>
</div>
</div>
</t-list-item>
</t-list>
</t-drawer>
<t-drawer v-model:visible="state.showUpload" size="25%" header="上传新固件" :footer="false">
<t-form
:data="formData"
reset-type="initial"
colon
@submit="onUF"
>
<t-form-item label="分享名称" name="title" label-align="top">
<t-input v-model="formData.title"></t-input>
</t-form-item>
<t-form-item label="分享描述" name="desc" label-align="top">
<t-textarea :autosize="{ minRows: 5, maxRows: 10 }" v-model="formData.desc" clearable />
</t-form-item>
<t-form-item label="信道文件" name="firmware" label-align="top">
<t-upload
v-model="formData.firmware"
action="https://k5.vicicode.com/wsapi/base64"
:abridge-name="[8, 6]"
theme="file-input"
placeholder="未选择文件"
></t-upload>
</t-form-item>
<t-form-item label-align="top">
<t-button theme="primary" type="submit" block>提交审核</t-button>
</t-form-item>
</t-form>
</t-drawer>
</div>
</template>
<script lang="ts" setup>
import { reactive, nextTick, onMounted, watch } from 'vue';
import { useAppStore, useUserStore } from '@/store';
import { useRouter } from 'vue-router';
import { RefreshIcon } from 'tdesign-icons-vue-next';
import axios from 'axios';
import { Message } from '@arco-design/web-vue';
const appStore = useAppStore();
const userStore = useUserStore();
const router = useRouter()
const state : {
binaryFile: any,
loading: boolean,
showPanel: boolean,
showUpload: boolean,
refLoading: boolean,
myList: any,
total: number,
page: number,
nowpage: any
} = reactive({
binaryFile: undefined,
loading: false,
showPanel: false,
showUpload: false,
refLoading: false,
myList: [],
total: 0,
page: 1,
nowpage: []
})
const formData = reactive({
title: '',
desc: '',
firmware: []
})
onMounted(async ()=>{
loadit({current: 1})
})
const loadit = async (page: any) => {
state.page = page.current
const resp : any = await axios.get("https://k5.vicicode.com/wsapi/list?type=2&limit=12&page=" + page.current + "&t=" + Date.now())
state.total = resp.total
state.nowpage = resp.data
}
const showPanel = async () => {
state.refLoading = true;
state.showPanel = true
const resp : any = await axios.post("https://k5.vicicode.com/wsapi/my_list", {
'type': 2,
'token': userStore.accountId
})
state.myList = resp.data
state.refLoading = false;
}
const showUpload = () => {
formData.title = ''
formData.desc = ''
formData.firmware = []
state.showUpload = true
}
const onUF = async () => {
if(formData.title == "" || formData.firmware.length == 0){
Message.error({
content: '未填写名称及上传文件',
duration: 5 * 1000,
});
return;
}
await axios.post("https://k5.vicicode.com/wsapi/upload", {
'type': 2,
'token': userStore.accountId,
'title': formData.title,
'desc': formData.desc,
'data': formData.firmware[0].url
})
state.showUpload = false;
showPanel()
}
const onDT = async (id: any) => {
await axios.post("https://k5.vicicode.com/wsapi/delete", {
'id': id,
'token': userStore.accountId,
})
showPanel()
}
const onStar = async (id: any) => {
await axios.post("https://k5.vicicode.com/wsapi/star", {
'id': id
})
Message.success({
content: '点赞成功',
duration: 5 * 1000,
});
}
const refit = () => {
showPanel()
}
const useFirmware = (url: string) => {
router.push({
path: '/chirp/channel',
query: {
url
}
});
}
</script>
<script lang="ts">
export default {
name: 'Backup',
};
</script>
<style scoped lang="less">
.container {
padding: 0 20px 20px 20px;
:deep(.arco-list-content) {
overflow-x: hidden;
}
:deep(.arco-card-meta-title) {
font-size: 14px;
}
}
:deep(.arco-list-col) {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
:deep(.arco-list-item) {
width: 33%;
}
:deep(.block-title) {
margin: 0 0 12px 0;
font-size: 14px;
}
:deep(.list-wrap) {
// min-height: 140px;
.list-row {
align-items: stretch;
.list-col {
margin-bottom: 16px;
}
}
:deep(.arco-space) {
width: 100%;
.arco-space-item {
&:last-child {
flex: 1;
}
}
}
}
</style>

View file

@ -19,9 +19,11 @@
<a-list>
<a-list-item style="width: 100%;" v-for="item in state.nowpage">
<a-list-item-meta
:title="item.title"
:description="item.desc"
>
<template #title>
<t-tag theme="primary" variant="outline">{{ item.upload }}</t-tag> {{ item.title }}
</template>
</a-list-item-meta>
<template #actions>
<a-link @click="onStar(item.id)">👍</a-link>
@ -67,7 +69,7 @@
<t-input v-model="formData.title"></t-input>
</t-form-item>
<t-form-item label="固件描述" name="desc" label-align="top">
<t-textarea :autosize="{ minRows: 5, maxRows: 10 }" v-model="formData.desc" placeholder="请输入" clearable />
<t-textarea :autosize="{ minRows: 5, maxRows: 10 }" v-model="formData.desc" clearable />
</t-form-item>
<t-form-item label="固件文件" name="firmware" label-align="top">
<t-upload

View file

@ -20,7 +20,7 @@
<a-col :span="4" v-for="i in state.nowpage">
<t-card :style="{ width: '100%', marginBottom: '10px' }">
<template #cover>
<img style="height: 6.75vw;" :title="i.title" :src="'https://k5.vicicode.com/wsapi/download?id=' + i.id + '&n=' + i.title + '.jpg'">
<img style="height: 6.75vw;" :title="i.title + ' [' + i.upload + ']'" :src="'https://k5.vicicode.com/wsapi/download?id=' + i.id + '&n=' + i.title + '.jpg'">
</template>
<template #footer>
<t-row :align="'middle'" justify="center" style="gap: 24px">
@ -77,7 +77,7 @@
<t-input v-model="formData.title"></t-input>
</t-form-item>
<t-form-item label="图片描述" name="desc" label-align="top">
<t-textarea :autosize="{ minRows: 5, maxRows: 10 }" v-model="formData.desc" placeholder="请输入" clearable />
<t-textarea :autosize="{ minRows: 5, maxRows: 10 }" v-model="formData.desc" clearable />
</t-form-item>
<t-form-item label="图片文件" name="firmware" label-align="top">
<t-upload

View file

@ -0,0 +1,164 @@
<template>
<div class="container">
<Breadcrumb :items="[$t('menu.workshop'), $t('idea.diy')]" />
<a-row :gutter="20" align="stretch">
<a-col :span="24">
<a-card class="general-card" :title="$t('idea.diy')" :loading="loading">
<t-space direction="vertical">
<div>操作说明<t-link theme="primary" href="https://github.com/losehu/uv-k5-firmware-custom" target="_blank">https://github.com/losehu/uv-k5-firmware-custom</t-link></div>
<a-radio-group v-for="item in state.showSort" v-model="state.flag[item]" type="button">
<a-radio v-for="subItem in state.disMatrix[item]" :value="subItem[0]"
:disabled="subItem[1]">{{ state.disName[item].get(subItem[0]) }}</a-radio>
</a-radio-group>
<a-button type="primary" @click="useFirmware">{{ $t('diy.generate') }}</a-button>
</t-space>
</a-card>
</a-col>
</a-row>
</div>
</template>
<script lang="ts" setup>
import { reactive, onMounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import useLoading from '@/hooks/loading';
import { useI18n } from 'vue-i18n';
const router = useRouter()
const { t } = useI18n();
const useFirmware = () => {
router.push({
path: '/tool/flash',
query: {
url: 'https://k5.vicicode.com/diyapi/LOSEHU' + state.flag.join('') + '.bin?v=' + (new Date()).getTime()
}
});
}
const state: {
versions: any,
flag: any,
disMatrix: any,
disName: any,
showSort: any
} = reactive({
versions: [],
flag: [],
disMatrix: [],
disName: [],
showSort: [],
})
watch(() => [...state.flag], () => { updateMatrix() })
const updateMatrix = () => {
state.flag.map((e: any, i: any) => {
state.disMatrix[i].forEach((value: any, key: any) => {
if(state.versions.indexOf('LOSEHU' + state.flag.join('').substring(0, i) + key + state.flag.join('').substring(i+1) + '.bin') == -1){
state.disMatrix[i].set(key, true)
}else{
state.disMatrix[i].set(key, false)
}
});
})
}
const { loading, setLoading } = useLoading(true);
onMounted(async () => {
setLoading(true)
let functions = await (await fetch('https://k5.vicicode.com/diyapi/function.json?v=' + (new Date()).getTime())).text()
functions = JSON.parse(functions)
let _newfunc: any = []
let _showSort: any = []
functions.map((e: any) => {
_newfunc[e[e.length - 1] - 1] = e
_showSort.push(e[e.length - 1] - 1)
})
functions = _newfunc
let disMatrix: any = []
let disName: any = []
functions.map((e: any) => {
let _conf: any = new Map()
let _confName: any = new Map();
for (let i = e[0] * 2 + 1; i < e[0] * 3 + 1; i++) {
_conf.set(e[i], false)
if(t('idea.diy') !== 'LOSEHU DIY'){
_confName.set(e[i], e[i - e[0] - e[0]])
}else{
_confName.set(e[i], e[i - e[0]])
}
}
disMatrix.push(_conf)
disName.push(_confName)
})
state.flag = new Array(functions.length).fill('0')
state.disName = disName
state.disMatrix = disMatrix
state.showSort = _showSort
const versions = await (await fetch('https://k5.vicicode.com/diyapi/version.json?v=' + (new Date()).getTime())).text()
state.versions = JSON.parse(versions)
updateMatrix()
setLoading(false)
})
</script>
<script lang="ts">
export default {
name: 'DIY',
};
</script>
<style scoped lang="less">
.container {
padding: 0 20px 20px 20px;
:deep(.arco-list-content) {
overflow-x: hidden;
}
:deep(.arco-card-meta-title) {
font-size: 14px;
}
}
:deep(.arco-list-col) {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
:deep(.arco-list-item) {
width: 33%;
}
:deep(.block-title) {
margin: 0 0 12px 0;
font-size: 14px;
}
:deep(.list-wrap) {
// min-height: 140px;
.list-row {
align-items: stretch;
.list-col {
margin-bottom: 16px;
}
}
:deep(.arco-space) {
width: 100%;
.arco-space-item {
&:last-child {
flex: 1;
}
}
}
}
</style>

View file

@ -105,7 +105,7 @@ const checkEeprom = async () => {
const clearEEPROM = async () => {
if(appStore.connectState != true){alert(sessionStorage.getItem('noticeConnectK5')); return;};
const eepromSize = await check_eeprom(appStore.connectPort, appStore.configuration?.uart);
let rawEEPROM = new Uint8Array(0x80);
let rawEEPROM = new Uint8Array(0x80).fill(0xff);
for (let i = 0; i < eepromSize; i += 0x80) {
await eeprom_write(appStore.connectPort, i, rawEEPROM, 0x80, appStore.configuration?.uart);
state.status = state.status + "清空进度:" + (((i - 0) / eepromSize) * 100).toFixed(1) + "%<br/>";

View file

@ -8,11 +8,20 @@
<span @click="()=>{state.showHide += 1}">{{ $t('menu.font') + $t('global.onStart') }}</span>
</template>
<a-space>
<t-card bordered>
<t-card bordered style="width: 420px;">
<template #header>
{{ $t('tool.fontwrite') }}
<div>
<a-radio-group type="button" size="mini" v-model="state.lang">
<a-radio value="Simplified_Chinese">{{$t('tool.Simplified_Chinese')}}</a-radio>
<a-radio value="Traditional_Chinese">{{$t('tool.Traditional_Chinese')}}</a-radio>
</a-radio-group>
</div>
</template>
<a-button @click="restore(1)">{{ $t('tool.writefontwrite') }}</a-button>
<div>
<a-button v-show="state.lang == 'Simplified_Chinese'" @click="restore(1)">{{ $t('tool.writefontwrite') }}</a-button>
<a-button v-show="state.lang == 'Traditional_Chinese'" @click="restore(6)">{{ $t('tool.writefontwrite') }}</a-button>
</div>
</t-card>
<t-card bordered>
<template #header>
@ -49,7 +58,8 @@ const appStore = useAppStore();
const state = reactive({
status: "点击写入按钮写入字库到设备<br/><br/>",
eepromType: "",
showHide: 0
showHide: 0,
lang: 'Simplified_Chinese'
})
const restoreRange = async (start: any = 0, uint8Array: any) => {
@ -145,6 +155,25 @@ const restore = async(type: any = 1) => {
return;
}
}
if(type == 6){
if(appStore.configuration?.charset == "gb2312"){
fontPacket = await fetch('/new_font_k_f.bin')
const reader = fontPacket.body.getReader();
const chunks = [];
while(true) {
const {done, value} = await reader.read();
if (done) {
break;
}
chunks.push(...value)
}
const binary = new Uint8Array(chunks)
await restoreRange(0x02480, binary)
return;
}else{
alert('不支持的版本')
}
}
}
</script>

View file

@ -52,7 +52,7 @@ onMounted(async ()=>{
}
const binary = new Uint8Array(chunks)
state.binaryFile = binary
state.binaryName = route.query.url.substring(route.query.url.lastIndexOf('/') + 1)
state.binaryName = route.query.url.substring(route.query.url.lastIndexOf('/') + 1).split('?')[0] + ' '
}
}
})

View file

@ -0,0 +1,272 @@
<template>
<div class="container">
<Breadcrumb :items="[$t('menu.dashboard'), $t('menu.cps.mdc')]" />
<a-card class="general-card">
<template #title>
<span @click="()=>{istate.showHide += 1}">{{ $t('menu.cps.mdc') + $t('global.onStart') }}</span>
</template>
<a-row style="margin-bottom: 16px">
<a-col :span="12">
<a-space>
<a-button type="primary" @click="readChannel">
<template #icon>
<icon-plus />
</template>
{{ $t('cps.onDeviceRead') }}
</a-button>
<a-button @click="writeChannel">
<template #icon>
<icon-plus />
</template>
{{ $t('cps.onDeviceWrite') }}
</a-button>
</a-space>
</a-col>
</a-row>
<t-table
class="ttable"
:loading="loading"
size="medium"
:columns="columns"
:data="cstate.renderData"
:pagination="{
defaultPageSize: cstate.pageSize,
total: cstate.renderData.length,
defaultCurrent: 1,
pageSizeOptions: [15, 30, 50, 100, 200]
}"
@change="(e: any)=>{cstate.pageSize = e.pagination.pageSize, cstate.nowPage = e.pagination.current}"
bordered
lazy-load
:headerAffixedTop="{ offsetTop: 60 }"
:hover="true"
drag-sort="row-handler"
@drag-sort="onDragSort"
>
<template #drag="{ row, rowIndex }">
<span>
<MoveIcon />
</span>
</template>
<template #index="{ row, rowIndex }">
{{ (cstate.nowPage - 1) * cstate.pageSize + rowIndex + 1 }}
</template>
<template #operate="{ row, rowIndex }">
<t-button theme="default" variant="dashed" @click="clearRow((cstate.nowPage - 1) * cstate.pageSize + rowIndex)">{{ $t('cps.clear') }}</t-button>
</template>
</t-table>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, watch } from 'vue';
import { Input } from 'tdesign-vue-next';
import useLoading from '@/hooks/loading';
import { eeprom_read, uint8ArrayToHexReverseString, hexReverseStringToUint8Array, stringToUint8Array, uint8ArrayToString, eeprom_write, eeprom_reboot, eeprom_init } from '@/utils/serial.js';
import { useAppStore } from '@/store';
import { MoveIcon } from 'tdesign-icons-vue-next';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const appStore = useAppStore();
const { loading, setLoading } = useLoading(false);
const cstate : {
renderData: any[],
pageSize: number,
nowPage: number
} = reactive({
renderData: Array.from({length: 16}).map(e=>{return {}}),
pageSize: 16,
nowPage: 1,
})
const istate = reactive({
showHide: 0
})
const onDragSort = (params: any) => {
cstate.renderData = params.newData
}
const columns = computed(() => [
{
colKey: 'drag', //
title: t('cps.sort'),
width: 46,
},
{
title: '#',
colKey: 'index',
align: 'left',
width: 100
},
{
title: t('cps.contact'),
colKey: 'name',
width: 250,
align: 'left',
cell: (h, { row }) => row.name ? row.name.replace(/[^a-zA-Z0-9_]/g, '') : undefined,
edit: {
component: Input,
props: {
clearable: true
},
onEdited: (context: any) => {
const newData = [...cstate.renderData];
newData.splice((cstate.nowPage - 1) * cstate.pageSize + context.rowIndex, 1, context.newRowData);
cstate.renderData = newData;
},
},
},
{
title: t('cps.mdcid'),
colKey: 'mdcid',
align: 'left',
width: 200,
cell: (h, { row }) => parseInt(row.mdcid, 16) <= 65535 ? parseInt(row.mdcid, 16).toString(16) : undefined,
edit: {
component: Input,
props: {
clearable: true
},
onEdited: (context: any) => {
context.newRowData.mdcid = context.newRowData.mdcid ? context.newRowData.mdcid.toLowerCase() : undefined
const newData = [...cstate.renderData];
newData.splice((cstate.nowPage - 1) * cstate.pageSize + context.rowIndex, 1, context.newRowData);
cstate.renderData = newData;
},
},
},
{
title: t('cps.operate'),
colKey: 'operate',
align: 'left',
width: 150
}
]);
const readChannel = async() => {
if(appStore.connectState != true){alert(sessionStorage.getItem('noticeConnectK5')); return;};
await eeprom_init(appStore.connectPort);
setLoading(true)
let rawEEPROM = new Uint8Array(0x100);
for (let i = 0x1D00; i < 0x1E00; i += 0x10) {
const _data = await eeprom_read(appStore.connectPort, i, 0x10, appStore.configuration?.uart)
rawEEPROM.set(_data, i - 0x1D00)
}
const _renderData : any = [];
for (let i = 0; i < 0x100; i += 0x10) {
if(uint8ArrayToHexReverseString(rawEEPROM.subarray(i, i + 0x02)) != 'ffff'){
_renderData.push({
name: uint8ArrayToString(rawEEPROM.subarray(i + 0x02, i + 0x10), appStore.configuration?.charset).trim(),
mdcid: uint8ArrayToHexReverseString(rawEEPROM.subarray(i, i + 0x01)) + uint8ArrayToHexReverseString(rawEEPROM.subarray(i + 0x01, i + 0x02))
})
}else{
_renderData.push({})
}
}
cstate.renderData = _renderData;
setLoading(false)
}
const writeChannel = async() =>{
if(appStore.connectState != true){alert(sessionStorage.getItem('noticeConnectK5')); return;};
await eeprom_init(appStore.connectPort);
setLoading(true)
for (let i = 0; i < 0x100; i += 0x10) {
if(cstate.renderData[i / 0x10].mdcid){
const _data = new Uint8Array(0x10).fill(0x20)
_data.set(hexReverseStringToUint8Array(cstate.renderData[i / 0x10].mdcid.padStart(4, '0').substring(0, 2)))
_data.set(hexReverseStringToUint8Array(cstate.renderData[i / 0x10].mdcid.padStart(4, '0').substring(2, 4)), 0x01)
_data.set(stringToUint8Array(cstate.renderData[i / 0x10].name), 0x02)
await eeprom_write(
appStore.connectPort,
i + 0x1D00,
_data,
0x10,
appStore.configuration?.uart
);
}else{
await eeprom_write(
appStore.connectPort,
i + 0x1D00,
hexReverseStringToUint8Array('ffffffffffffffffffffffffffffffff'),
0x10,
appStore.configuration?.uart
);
}
}
const _tmp = await eeprom_read(appStore.connectPort, 0x1FF0, 0x10, appStore.configuration?.uart)
_tmp.set([0x10], 0x10 - 1)
await eeprom_write(
appStore.connectPort,
0x1FF0,
_tmp,
0x10,
appStore.configuration?.uart
);
await eeprom_reboot(appStore.connectPort);
setLoading(false)
}
const clearRow = async (row: any) =>{
const newData = [...cstate.renderData];
newData.splice(row, 1, {scanlist: []});
cstate.renderData = newData;
}
</script>
<script lang="ts">
export default {
name: 'Radio',
};
</script>
<style scoped lang="less">
:deep(::-webkit-scrollbar-thumb){
border-radius: 0 !important;
}
:deep(.scrollbar::-webkit-scrollbar) {
height: 10px;
}
:deep(.t-table__content::-webkit-scrollbar) {
height: 15px;
}
.container {
padding: 0 20px 20px 20px;
}
:deep(.arco-table-th) {
&:last-child {
.arco-table-th-item-title {
margin-left: 16px;
}
}
}
.action-icon {
margin-left: 12px;
cursor: pointer;
}
.active {
color: #0960bd;
background-color: #e3f4fc;
}
.setting {
display: flex;
align-items: center;
width: 200px;
.title {
margin-left: 12px;
cursor: pointer;
}
}
.ttable {
:deep(.t-table__affixed-header-elm-wrap){
height: 60px !important;
}
:deep(.t-table__content){
scrollbar-width: auto !important;
}
}
</style>

View file

@ -0,0 +1,272 @@
<template>
<div class="container">
<Breadcrumb :items="[$t('menu.dashboard'), $t('menu.cps.radio')]" />
<a-card class="general-card">
<template #title>
<span @click="()=>{istate.showHide += 1}">{{ $t('menu.cps.radio') + $t('global.onStart') }}</span>
</template>
<a-row style="margin-bottom: 16px">
<a-col :span="12">
<a-space>
<a-button type="primary" @click="readChannel">
<template #icon>
<icon-plus />
</template>
{{ $t('cps.onDeviceRead') }}
</a-button>
<a-button @click="writeChannel">
<template #icon>
<icon-plus />
</template>
{{ $t('cps.onDeviceWrite') }}
</a-button>
</a-space>
</a-col>
<!-- <a-col :span="12" style="text-align: right;">
<a-space>
<a-button type="text" @click="downloadExcelTemplate">
{{ $t('cps.downloadImportTemplate') }}
</a-button>
<a-button type="primary" @click="restoreExcelChannel">
{{ $t('cps.import') }}
</a-button>
<a-button @click="saveExcelChannel">
{{ $t('cps.export') }}
</a-button>
</a-space>
</a-col> -->
</a-row>
<t-table
class="ttable"
:loading="loading"
size="medium"
:columns="columns"
:data="cstate.renderData"
:pagination="{
defaultPageSize: cstate.pageSize,
total: cstate.renderData.length,
defaultCurrent: 1,
pageSizeOptions: [15, 30, 50, 100, 200]
}"
@change="(e: any)=>{cstate.pageSize = e.pagination.pageSize, cstate.nowPage = e.pagination.current}"
bordered
lazy-load
:headerAffixedTop="{ offsetTop: 60 }"
:hover="true"
drag-sort="row-handler"
@drag-sort="onDragSort"
>
<template #drag="{ row, rowIndex }">
<span>
<MoveIcon />
</span>
</template>
<template #index="{ row, rowIndex }">
{{ (cstate.nowPage - 1) * cstate.pageSize + rowIndex + 1 }}
</template>
<template #operate="{ row, rowIndex }">
<t-button theme="default" variant="dashed" @click="clearRow((cstate.nowPage - 1) * cstate.pageSize + rowIndex)">{{ $t('cps.clear') }}</t-button>
</template>
</t-table>
</a-card>
</div>
</template>
<script lang="ts" setup>
import { computed, reactive, watch } from 'vue';
import { Input } from 'tdesign-vue-next';
import useLoading from '@/hooks/loading';
import { eeprom_read, uint8ArrayToHexReverseString, hexReverseStringToUint8Array, eeprom_write, eeprom_reboot, eeprom_init } from '@/utils/serial.js';
import { useAppStore } from '@/store';
import { MoveIcon } from 'tdesign-icons-vue-next';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const appStore = useAppStore();
const { loading, setLoading } = useLoading(false);
const cstate : {
renderData: any[],
pageSize: number,
nowPage: number
} = reactive({
renderData: Array.from({length: 20}).map(e=>{return {}}),
pageSize: 50,
nowPage: 1,
})
const istate = reactive({
showHide: 0
})
const onDragSort = (params: any) => {
cstate.renderData = params.newData
}
const columns = computed(() => [
{
colKey: 'drag', //
title: t('cps.sort'),
width: 46,
},
{
title: '#',
colKey: 'index',
align: 'left',
width: 100
},
{
title: t('cps.rx'),
colKey: 'rx',
align: 'left',
width: 200,
cell: (h, { row }) => parseFloat(row.rx) ? parseFloat(row.rx).toFixed(2) : undefined,
edit: {
component: Input,
props: {
clearable: true
},
onEdited: (context: any) => {
context.newRowData.rx = context.newRowData.rx ? context.newRowData.rx : undefined
const newData = [...cstate.renderData];
newData.splice((cstate.nowPage - 1) * cstate.pageSize + context.rowIndex, 1, context.newRowData);
cstate.renderData = newData;
},
},
},
{
title: t('cps.operate'),
colKey: 'operate',
align: 'left',
width: 150
}
]);
const readChannel = async() => {
if(appStore.connectState != true){alert(sessionStorage.getItem('noticeConnectK5')); return;};
await eeprom_init(appStore.connectPort);
setLoading(true)
if(appStore.configuration?.fm30){
let rawEEPROM = new Uint8Array(0x03C);
for (let i = 0x1FFC0; i < 0x1FFF1; i += 0x08) {
const _data = await eeprom_read(appStore.connectPort, i, 0x08, appStore.configuration?.uart)
rawEEPROM.set(_data, i - 0x1FFC0)
}
const _renderData : any = [];
for (let i = 0; i < 0x03C; i += 0x02) {
const rx = uint8ArrayToHexReverseString(rawEEPROM.subarray(i, i + 0x02))
if(rx != 'ffff'){
_renderData.push({
rx: parseInt(rx, 16) / 10
})
}else{
_renderData.push({})
}
}
cstate.renderData = _renderData;
}else{
let rawEEPROM = new Uint8Array(0x028);
for (let i = 0x0E40; i < 0x0E61; i += 0x08) {
const _data = await eeprom_read(appStore.connectPort, i, 0x08, appStore.configuration?.uart)
rawEEPROM.set(_data, i - 0x0E40)
}
const _renderData : any = [];
for (let i = 0; i < 0x028; i += 0x02) {
const rx = uint8ArrayToHexReverseString(rawEEPROM.subarray(i, i + 0x02))
if(rx != 'ffff'){
_renderData.push({
rx: parseInt(rx, 16) / 10
})
}else{
_renderData.push({})
}
}
cstate.renderData = _renderData;
}
setLoading(false)
}
const writeChannel = async() =>{
if(appStore.connectState != true){alert(sessionStorage.getItem('noticeConnectK5')); return;};
await eeprom_init(appStore.connectPort);
setLoading(true)
if(appStore.configuration?.fm30){
for (let i = 0; i < 0x03C; i += 0x02) {
if(cstate.renderData[i / 0x02].rx){
await eeprom_write(appStore.connectPort, i + 0x1FFC0, hexReverseStringToUint8Array((parseInt(cstate.renderData[i / 0x02].rx * 10)).toString(16).padStart(4, '0')), 0x02, appStore.configuration?.uart);
}else{
await eeprom_write(appStore.connectPort, i + 0x1FFC0, hexReverseStringToUint8Array('0000'), 0x02, appStore.configuration?.uart);
}
}
}else{
for (let i = 0; i < 0x028; i += 0x02) {
if(cstate.renderData[i / 0x02].rx){
await eeprom_write(appStore.connectPort, i + 0x0E40, hexReverseStringToUint8Array((parseInt(cstate.renderData[i / 0x02].rx * 10)).toString(16).padStart(4, '0')), 0x02, appStore.configuration?.uart);
}else{
await eeprom_write(appStore.connectPort, i + 0x0E40, hexReverseStringToUint8Array('0000'), 0x02, appStore.configuration?.uart);
}
}
}
await eeprom_reboot(appStore.connectPort);
setLoading(false)
}
const clearRow = async (row: any) =>{
const newData = [...cstate.renderData];
newData.splice(row, 1, {scanlist: []});
cstate.renderData = newData;
}
</script>
<script lang="ts">
export default {
name: 'Radio',
};
</script>
<style scoped lang="less">
:deep(::-webkit-scrollbar-thumb){
border-radius: 0 !important;
}
:deep(.scrollbar::-webkit-scrollbar) {
height: 10px;
}
:deep(.t-table__content::-webkit-scrollbar) {
height: 15px;
}
.container {
padding: 0 20px 20px 20px;
}
:deep(.arco-table-th) {
&:last-child {
.arco-table-th-item-title {
margin-left: 16px;
}
}
}
.action-icon {
margin-left: 12px;
cursor: pointer;
}
.active {
color: #0960bd;
background-color: #e3f4fc;
}
.setting {
display: flex;
align-items: center;
width: 200px;
.title {
margin-left: 12px;
cursor: pointer;
}
}
.ttable {
:deep(.t-table__affixed-header-elm-wrap){
height: 60px !important;
}
:deep(.t-table__content){
scrollbar-width: auto !important;
}
}
</style>

View file

@ -1,6 +1,6 @@
<template>
<div class="container">
<a-modal v-model:visible="state.visible" @ok="handleOk" ok-text="Scanned and uploaded">
<a-modal v-model:visible="state.visible" @ok="handleOk" :ok-text="$t('tool.scaned')">
<template #title>
{{ $t('tool.scanqr') }}
</template>
@ -47,7 +47,7 @@
<a-space>
<a-button @click="getLocation">{{ $t('tool.brlonlat') }}</a-button>
<a-button @click="scanLocation">{{ $t('tool.phonelonlat') }}</a-button>
<a-button @click="getPass">{{ $t('tool.satpasstime') }}</a-button>
<a-button type="primary" @click="getPass">{{ $t('tool.satpasstime') }}</a-button>
</a-space>
</a-form-item>
<a-form-item :label-col-style="{ width: '25%' }" field="pass" :label="$t('tool.selectPassTime')">
@ -84,7 +84,7 @@
</a-select>
</a-form-item>
<a-form-item :label-col-style="{ width: '25%' }" label="">
<a-button @click="writeIt">{{ $t('tool.writeData') }}</a-button>
<a-button type="primary" @click="writeIt">{{ $t('tool.writeData') }}</a-button>
</a-form-item>
<a-divider />
<div id="statusArea"

View file

@ -102,7 +102,8 @@
</template>
<script lang="ts" setup>
import { ref, computed, reactive } from 'vue';
import { ref, computed, reactive, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { Input, Select } from 'tdesign-vue-next';
import useLoading from '@/hooks/loading';
import { eeprom_read, uint8ArrayToHexReverseString, uint8ArrayToString, hexReverseStringToUint8Array, stringToUint8Array, eeprom_write, eeprom_reboot, eeprom_init } from '@/utils/serial.js';
@ -161,6 +162,93 @@
cstate.renderData = params.newData
}
const route = useRoute();
onMounted(async ()=>{
if(route.query.url){
const packet = await fetch(route.query.url)
const reader = packet?.body?.getReader();
if(reader){
const chunks = [];
while(true) {
const {done, value} = await reader.read();
if (done) {
break;
}
chunks.push(...value)
}
const binary = new Uint8Array(chunks)
var workbook = xlsxRead(binary);
const renderData : any = Array.from({length: 200}).map(e=>{return {scanlist: []}})
for(let i = 2; i < 202; i++){
if(workbook.Sheets.Sheet1['B' + i]?.w){
renderData[i - 2]['name'] = workbook.Sheets.Sheet1['B' + i]?.w
}
if(workbook.Sheets.Sheet1['C' + i]?.w){
renderData[i - 2]['bandwidth'] = Object.keys(state.bandwidthOption).find(key=>state.bandwidthOption[key]==workbook.Sheets.Sheet1['C' + i]?.w)
}
if(workbook.Sheets.Sheet1['D' + i]?.w){
renderData[i - 2]['rx'] = workbook.Sheets.Sheet1['D' + i]?.w
}
if(workbook.Sheets.Sheet1['E' + i]?.w){
renderData[i - 2]['tx'] = workbook.Sheets.Sheet1['E' + i]?.w
}
if(workbook.Sheets.Sheet1['F' + i]?.w){
renderData[i - 2]['power'] = Object.keys(state.powerOption).find(key=>state.powerOption[key]==workbook.Sheets.Sheet1['F' + i]?.w)
}
if(workbook.Sheets.Sheet1['G' + i]?.w){
renderData[i - 2]['rxTone'] = Object.keys(state.toneOption).find(key=>state.toneOption[key]==workbook.Sheets.Sheet1['G' + i]?.w)
}
if(workbook.Sheets.Sheet1['H' + i]?.w){
renderData[i - 2]['rxCTCSS'] = parseFloat(workbook.Sheets.Sheet1['H' + i]?.w)
}
if(workbook.Sheets.Sheet1['I' + i]?.w){
renderData[i - 2]['rxDCS'] = parseFloat(workbook.Sheets.Sheet1['I' + i]?.w)
}
if(workbook.Sheets.Sheet1['J' + i]?.w){
renderData[i - 2]['txTone'] = Object.keys(state.toneOption).find(key=>state.toneOption[key]==workbook.Sheets.Sheet1['J' + i]?.w)
}
if(workbook.Sheets.Sheet1['K' + i]?.w){
renderData[i - 2]['txCTCSS'] = parseFloat(workbook.Sheets.Sheet1['K' + i]?.w)
}
if(workbook.Sheets.Sheet1['L' + i]?.w){
renderData[i - 2]['txDCS'] = parseFloat(workbook.Sheets.Sheet1['L' + i]?.w)
}
if(workbook.Sheets.Sheet1['M' + i]?.w){
renderData[i - 2]['step'] = parseFloat(workbook.Sheets.Sheet1['M' + i]?.w)
}
if(workbook.Sheets.Sheet1['N' + i]?.w){
renderData[i - 2]['reverse'] = workbook.Sheets.Sheet1['N' + i]?.w == '开' ? true : false
}
if(workbook.Sheets.Sheet1['O' + i]?.w){
renderData[i - 2]['scramb'] = parseFloat(workbook.Sheets.Sheet1['O' + i]?.w)
}
if(workbook.Sheets.Sheet1['P' + i]?.w){
renderData[i - 2]['busy'] = workbook.Sheets.Sheet1['P' + i]?.w == '开' ? true : false
}
if(workbook.Sheets.Sheet1['Q' + i]?.w){
renderData[i - 2]['pttid'] = workbook.Sheets.Sheet1['Q' + i]?.w
}
if(workbook.Sheets.Sheet1['R' + i]?.w){
renderData[i - 2]['mode'] = Object.keys(state.modeOption).find(key=>state.modeOption[key]==workbook.Sheets.Sheet1['R' + i]?.w)
}
if(workbook.Sheets.Sheet1['S' + i]?.w){
renderData[i - 2]['dtmf'] = workbook.Sheets.Sheet1['S' + i]?.w == '开' ? true : false
}
if(workbook.Sheets.Sheet1['T' + i]?.w){
if(workbook.Sheets.Sheet1['T' + i]?.w.split(',').indexOf('I') >= 0){
renderData[i - 2]['scanlist'].push('I')
}
if(workbook.Sheets.Sheet1['T' + i]?.w.split(',').indexOf('II') >= 0){
renderData[i - 2]['scanlist'].push('II')
}
}
}
cstate.renderData = renderData
}
}
})
const columns = computed(() => [
{
colKey: 'drag', //
@ -626,7 +714,11 @@
const _tx = _channel.tx && _channel.rx ? Math.abs(parseInt((_channel.tx * 100000).toFixed(0)) - parseInt((_channel.rx * 100000).toFixed(0))) : NaN
_channelhex += !Number.isNaN(_tx) ? _tx.toString(16).padStart(8, '0') : '00000000'
_channelhex += parseInt((_channel.rx * 100000).toFixed(0)).toString(16).padStart(8, '0')
if(_channelhex.indexOf('-1') != -1){
_channelhex = _channelhex.replace(/^(.{10})(.{6})(.*)$/, '$1000000$3');
}
console.log(_channelhex)
rawEEPROM.set(hexReverseStringToUint8Array(_channelhex), i)
@ -774,6 +866,7 @@
}
xlsxWrite(workbook, 'K5Channel.xlsx');
}
const restoreExcelChannel = () => {
const input = document.createElement('input');
input.type = 'file';

View file

@ -21,16 +21,6 @@
</a-button>
</a-space>
</a-col>
<!-- <a-col :span="12" style="text-align: right;">
<a-space>
<a-button type="primary" @click="saveChannel">
{{ $t('cps.save') }}
</a-button>
<a-button @click="restoreChannel">
{{ $t('cps.load') }}
</a-button>
</a-space>
</a-col> -->
</a-row>
<a-spin :loading="loading" style="width: 100%;">
<a-form-item :label-col-style="{width: '25%'}" field="logo_line1" :label="$t('cps.line1')">
@ -39,7 +29,10 @@
<a-form-item :label-col-style="{width: '25%'}" field="logo_line2" :label="$t('cps.line2')">
<a-input v-model="state.logo_line2" />
</a-form-item>
<a-form-item :label-col-style="{width: '25%'}" field="logo_line2" :label="$t('cps.mdclocplay')">
<a-form-item :label-col-style="{width: '25%'}" field="dtmfid" :label="$t('cps.dtmfid')">
<a-input v-model="state.dtmfid" />
</a-form-item>
<a-form-item :label-col-style="{width: '25%'}" field="mdclocplay" :label="$t('cps.mdclocplay')">
<a-switch v-model="state.mdc_audio_local" type="round"/>
</a-form-item>
</a-spin>
@ -61,7 +54,8 @@ const { loading, setLoading } = useLoading(false);
const state = reactive({
logo_line1: '',
logo_line2: '',
mdc_audio_local: true
mdc_audio_local: true,
dtmfid: ''
})
const readChannel = async() => {
@ -85,6 +79,9 @@ const readChannel = async() => {
state.logo_line2 = uint8ArrayToString(logo.subarray(0x10, 0x20), appStore.configuration?.charset)
}
const dtmfid = await eeprom_read(appStore.connectPort, 0xEE0, 0x03, appStore.configuration?.uart)
state.dtmfid = uint8ArrayToString(dtmfid)
if(parseInt(await eeprom_read(appStore.connectPort, 0x01FFD, 0x01, appStore.configuration?.uart)) == 0){
state.mdc_audio_local = false
}else{
@ -116,21 +113,21 @@ const writeChannel = async() => {
logo.set(stringToUint8Array(state.logo_line2, appStore.configuration?.charset).subarray(0, 0x10), 0x10);
await eeprom_write(appStore.connectPort, 0xEB0, logo, 0x20, appStore.configuration?.uart);
}
if(state.dtmfid == ''){
await eeprom_write(appStore.connectPort, 0xEE0, new Uint8Array([0xff, 0xff, 0xff]), 0x03, appStore.configuration?.uart);
}else{
await eeprom_write(appStore.connectPort, 0xEE0, new Uint8Array([
stringToUint8Array(state.dtmfid.padStart(3, '0').split('')[0]),
stringToUint8Array(state.dtmfid.padStart(3, '0').split('')[1]),
stringToUint8Array(state.dtmfid.padStart(3, '0').split('')[2])
]), 0x03, appStore.configuration?.uart);
}
if(appStore.configuration?.localmdc){
await eeprom_write(appStore.connectPort, 0x01FFD, new Uint8Array([state.mdc_audio_local ? 1 : 0]), 0x01, appStore.configuration?.uart);
}
await eeprom_reboot(appStore.connectPort);
setLoading(false)
}
const saveChannel = async() => {
}
const restoreChannel = async() => {
}
</script>
<script lang="ts">

View file

@ -7530,6 +7530,11 @@ vue-i18n@^9.2.2:
"@intlify/shared" "9.11.1"
"@vue/devtools-api" "^6.5.0"
vue-matomo@^4.2.0:
version "4.2.0"
resolved "https://repo.vicicode.com/repository/npm-proxy/vue-matomo/-/vue-matomo-4.2.0.tgz#d65e369e4ead1d95ef790bef3627512cac3d25e9"
integrity sha512-m5hCw7LH3wPDcERaF4sp/ojR9sEx7Rl8TpOyH/4jjQxMF2DuY/q5pO+i9o5Dx+BXLSa9+IQ0qhAbWYRyESQXmA==
vue-router@^4.0.14:
version "4.3.0"
resolved "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz"