mirror of
https://github.com/silenty4ng/k5web
synced 2025-01-07 20:33:28 +00:00
update
This commit is contained in:
parent
8bd42373e7
commit
b5f20f19c5
20 changed files with 1662 additions and 12 deletions
|
@ -10,7 +10,7 @@
|
|||
:style="{ margin: 0, fontSize: '18px' }"
|
||||
:heading="5"
|
||||
>
|
||||
K5Web 网页小工具
|
||||
K5Web 工具箱
|
||||
</a-typography-title>
|
||||
<icon-menu-fold
|
||||
v-if="!topMenu && appStore.device === 'mobile'"
|
||||
|
@ -139,6 +139,9 @@
|
|||
|
||||
const connectIt = async () => {
|
||||
if(appStore.connectState == false){
|
||||
try{
|
||||
if(appStore.connectPort)await disconnect(appStore.connectPort);
|
||||
}catch{}
|
||||
const _connect = await connect();
|
||||
|
||||
if(!_connect){
|
||||
|
|
|
@ -12,16 +12,6 @@ const LIST: AppRouteRecordRaw = {
|
|||
order: 2,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'chi',
|
||||
name: 'Chi',
|
||||
component: () => import('@/views/list/chi/index.vue'),
|
||||
meta: {
|
||||
locale: '字库写入',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'backup',
|
||||
name: 'Backup',
|
||||
|
@ -32,6 +22,36 @@ const LIST: AppRouteRecordRaw = {
|
|||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'flash',
|
||||
name: 'Flash',
|
||||
component: () => import('@/views/list/flash/index.vue'),
|
||||
meta: {
|
||||
locale: '固件升级',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'image',
|
||||
name: 'Image',
|
||||
component: () => import('@/views/list/image/index.vue'),
|
||||
meta: {
|
||||
locale: '开机图片',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'chi',
|
||||
name: 'Chi',
|
||||
component: () => import('@/views/list/chi/index.vue'),
|
||||
meta: {
|
||||
locale: '字库写入',
|
||||
requiresAuth: true,
|
||||
roles: ['*'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
|
@ -1252,6 +1252,7 @@ export {
|
|||
check_eeprom,
|
||||
eeprom_write,
|
||||
flash_flashFirmware,
|
||||
flash_generateCommand,
|
||||
unpackVersion,
|
||||
unpack
|
||||
}
|
|
@ -259,7 +259,9 @@ const iFlashIt = async () => {
|
|||
await sendPacket(_connect, _data);
|
||||
await readPacket(_connect, 0x18)
|
||||
await flash_flashFirmware(_connect, unpack(binary))
|
||||
appStore.updateSettings({ connectPort: _connect });
|
||||
if(appStore.connectPort){
|
||||
appStore.updateSettings({ connectPort: _connect });
|
||||
}
|
||||
state.flashIt = true
|
||||
state.loading = false
|
||||
}
|
||||
|
|
204
src/views/list/flash/components/card-wrap.vue
Normal file
204
src/views/list/flash/components/card-wrap.vue
Normal file
|
@ -0,0 +1,204 @@
|
|||
<template>
|
||||
<div class="card-wrap">
|
||||
<a-card v-if="loading" :bordered="false" hoverable>
|
||||
<slot name="skeleton"></slot>
|
||||
</a-card>
|
||||
<a-card v-else :bordered="false" hoverable>
|
||||
<a-space align="start">
|
||||
<a-avatar
|
||||
v-if="icon"
|
||||
:size="24"
|
||||
style="margin-right: 8px; background-color: #626aea"
|
||||
>
|
||||
<icon-filter />
|
||||
</a-avatar>
|
||||
<a-card-meta>
|
||||
<template #title>
|
||||
<a-typography-text style="margin-right: 10px">
|
||||
{{ title }}
|
||||
</a-typography-text>
|
||||
<template v-if="showTag">
|
||||
<a-tag
|
||||
v-if="open && isExpires === false"
|
||||
size="small"
|
||||
color="green"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-check-circle-fill />
|
||||
</template>
|
||||
<span>{{ tagText }}</span>
|
||||
</a-tag>
|
||||
<a-tag v-else-if="isExpires" size="small" color="red">
|
||||
<template #icon>
|
||||
<icon-check-circle-fill />
|
||||
</template>
|
||||
<span>{{ expiresTagText }}</span>
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
<template #description>
|
||||
{{ description }}
|
||||
<slot></slot>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
</a-space>
|
||||
<template #actions>
|
||||
<a-switch v-if="actionType === 'switch'" v-model="open" />
|
||||
<a-space v-else-if="actionType === 'button'">
|
||||
<template v-if="isExpires">
|
||||
<a-button type="outline" @click="renew">
|
||||
{{ expiresText }}
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-button v-if="open" @click="handleToggle">
|
||||
{{ closeTxt }}
|
||||
</a-button>
|
||||
<a-button v-else-if="!open" type="outline" @click="handleToggle">
|
||||
{{ openTxt }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
<div v-else>
|
||||
<a-space>
|
||||
<a-button @click="toggle(false)">
|
||||
{{ closeTxt }}
|
||||
</a-button>
|
||||
<a-button type="primary" @click="toggle(true)">
|
||||
{{ openTxt }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
|
||||
const props = defineProps({
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
actionType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
defaultValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
openTxt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
closeTxt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
expiresText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showTag: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
tagText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
expires: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
expiresTagText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const [open, toggle] = useToggle(props.defaultValue);
|
||||
const handleToggle = () => {
|
||||
toggle();
|
||||
};
|
||||
const isExpires = ref(props.expires);
|
||||
const renew = () => {
|
||||
isExpires.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.card-wrap {
|
||||
height: 100%;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
// box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
:deep(.arco-card) {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
.arco-card-body {
|
||||
height: 100%;
|
||||
.arco-space {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.arco-space-item {
|
||||
height: 100%;
|
||||
&:last-child {
|
||||
flex: 1;
|
||||
}
|
||||
.arco-card-meta {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
.arco-card-meta-content {
|
||||
flex: 1;
|
||||
.arco-card-meta-description {
|
||||
margin-top: 8px;
|
||||
color: rgb(var(--gray-6));
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.arco-card-meta-footer {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.arco-card-meta-title) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
// To prevent the shaking
|
||||
line-height: 28px;
|
||||
}
|
||||
:deep(.arco-skeleton-line) {
|
||||
&:last-child {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
115
src/views/list/flash/components/quality-inspection.vue
Normal file
115
src/views/list/flash/components/quality-inspection.vue
Normal file
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<div class="list-wrap">
|
||||
<a-typography-title class="block-title" :heading="6">
|
||||
{{ $t('cardList.tab.title.content') }}
|
||||
</a-typography-title>
|
||||
<a-row class="list-row" :gutter="24">
|
||||
<a-col
|
||||
:xs="12"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
:xxl="6"
|
||||
class="list-col"
|
||||
>
|
||||
<div class="card-wrap empty-wrap">
|
||||
<a-card :bordered="false" hoverable>
|
||||
<a-result :status="null" :title="$t('cardList.content.action')">
|
||||
<template #icon>
|
||||
<icon-plus style="font-size: 20px" />
|
||||
</template>
|
||||
</a-result>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-for="item in renderData"
|
||||
:key="item.id"
|
||||
class="list-col"
|
||||
:xs="12"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
:xxl="6"
|
||||
>
|
||||
<CardWrap
|
||||
:loading="loading"
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:default-value="item.enable"
|
||||
:action-type="item.actionType"
|
||||
:icon="item.icon"
|
||||
:open-txt="$t('cardList.content.inspection')"
|
||||
:close-txt="$t('cardList.content.delete')"
|
||||
:show-tag="false"
|
||||
>
|
||||
<a-descriptions
|
||||
style="margin-top: 16px"
|
||||
:data="item.data"
|
||||
layout="inline-horizontal"
|
||||
:column="2"
|
||||
/>
|
||||
<template #skeleton>
|
||||
<a-skeleton :animation="true">
|
||||
<a-skeleton-line
|
||||
:widths="['50%', '50%', '100%', '40%']"
|
||||
:rows="4"
|
||||
/>
|
||||
<a-skeleton-line :widths="['40%']" :rows="1" />
|
||||
</a-skeleton>
|
||||
</template>
|
||||
</CardWrap>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryInspectionList, ServiceRecord } from '@/api/list';
|
||||
import useRequest from '@/hooks/request';
|
||||
import CardWrap from './card-wrap.vue';
|
||||
|
||||
const defaultValue: ServiceRecord[] = new Array(3).fill({});
|
||||
const { loading, response: renderData } = useRequest<ServiceRecord[]>(
|
||||
queryInspectionList,
|
||||
defaultValue
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.card-wrap {
|
||||
height: 100%;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
:deep(.arco-card-meta-description) {
|
||||
color: rgb(var(--gray-6));
|
||||
.arco-descriptions-item-label-inline {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
color: rgb(var(--gray-6));
|
||||
}
|
||||
.arco-descriptions-item-value-inline {
|
||||
color: rgb(var(--gray-8));
|
||||
}
|
||||
}
|
||||
}
|
||||
.empty-wrap {
|
||||
height: 200px;
|
||||
border-radius: 4px;
|
||||
:deep(.arco-card) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
.arco-result-title {
|
||||
color: rgb(var(--gray-6));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
51
src/views/list/flash/components/rules-preset.vue
Normal file
51
src/views/list/flash/components/rules-preset.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<div class="list-wrap">
|
||||
<a-typography-title class="block-title" :heading="6">
|
||||
{{ $t('cardList.tab.title.preset') }}
|
||||
</a-typography-title>
|
||||
<a-row class="list-row" :gutter="24">
|
||||
<a-col
|
||||
v-for="item in renderData"
|
||||
:key="item.id"
|
||||
:xs="12"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
:xxl="6"
|
||||
class="list-col"
|
||||
style="min-height: 140px"
|
||||
>
|
||||
<CardWrap
|
||||
:loading="loading"
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:default-value="item.enable"
|
||||
:action-type="item.actionType"
|
||||
:tag-text="$t('cardList.preset.tag')"
|
||||
>
|
||||
<template #skeleton>
|
||||
<a-skeleton :animation="true">
|
||||
<a-skeleton-line :widths="['100%', '40%']" :rows="2" />
|
||||
<a-skeleton-line :widths="['40%']" :rows="1" />
|
||||
</a-skeleton>
|
||||
</template>
|
||||
</CardWrap>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryRulesPresetList, ServiceRecord } from '@/api/list';
|
||||
import useRequest from '@/hooks/request';
|
||||
import CardWrap from './card-wrap.vue';
|
||||
|
||||
const defaultValue: ServiceRecord[] = new Array(6).fill({});
|
||||
const { loading, response: renderData } = useRequest<ServiceRecord[]>(
|
||||
queryRulesPresetList,
|
||||
defaultValue
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
57
src/views/list/flash/components/the-service.vue
Normal file
57
src/views/list/flash/components/the-service.vue
Normal file
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<div class="list-wrap">
|
||||
<a-typography-title class="block-title" :heading="6">
|
||||
{{ $t('cardList.tab.title.service') }}
|
||||
</a-typography-title>
|
||||
<a-row class="list-row" :gutter="24">
|
||||
<a-col
|
||||
v-for="item in renderData"
|
||||
:key="item.id"
|
||||
:xs="12"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
:xxl="6"
|
||||
class="list-col"
|
||||
style="min-height: 162px"
|
||||
>
|
||||
<CardWrap
|
||||
:loading="loading"
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:default-value="item.enable"
|
||||
:action-type="item.actionType"
|
||||
:expires="item.expires"
|
||||
:open-txt="$t('cardList.service.open')"
|
||||
:close-txt="$t('cardList.service.cancel')"
|
||||
:expires-text="$t('cardList.service.renew')"
|
||||
:tag-text="$t('cardList.service.tag')"
|
||||
:expires-tag-text="$t('cardList.service.expiresTag')"
|
||||
:icon="item.icon"
|
||||
>
|
||||
<template #skeleton>
|
||||
<a-skeleton :animation="true">
|
||||
<a-skeleton-line :widths="['100%', '40%', '100%']" :rows="3" />
|
||||
<a-skeleton-line :widths="['40%']" :rows="1" />
|
||||
</a-skeleton>
|
||||
</template>
|
||||
</CardWrap>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryTheServiceList, ServiceRecord } from '@/api/list';
|
||||
import useRequest from '@/hooks/request';
|
||||
import CardWrap from './card-wrap.vue';
|
||||
|
||||
const defaultValue: ServiceRecord[] = new Array(4).fill({});
|
||||
const { loading, response: renderData } = useRequest<ServiceRecord[]>(
|
||||
queryTheServiceList,
|
||||
defaultValue
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
152
src/views/list/flash/index.vue
Normal file
152
src/views/list/flash/index.vue
Normal file
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['小工具', '固件升级']" />
|
||||
<a-row :gutter="20" align="stretch">
|
||||
<a-col :span="24">
|
||||
<a-card class="general-card" title="固件升级">
|
||||
<a-space>
|
||||
<a-button @click="selectFile">{{ state.binaryFile ? state.binaryName : '选择固件' }}</a-button>
|
||||
<a-button type="primary" :disabled="!state.binaryFile" @click="flashIt">更新</a-button>
|
||||
</a-space>
|
||||
<a-divider />
|
||||
<div id="statusArea" style="height: 20em; background-color: azure; color: silver; overflow: auto; padding: 20px"
|
||||
v-html="state.status"></div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, nextTick } from 'vue';
|
||||
import { useAppStore } from '@/store';
|
||||
import { eeprom_write, eeprom_reboot, eeprom_init, disconnect, connect, readPacket, sendPacket, unpackVersion, unpack, flash_generateCommand } from '@/utils/serial.js';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const state : {
|
||||
status: any,
|
||||
binaryName: any,
|
||||
binaryFile: any
|
||||
} = reactive({
|
||||
status: "点击更新按钮更新固件到设备<br/><br/>",
|
||||
binaryFile: undefined,
|
||||
binaryName: ''
|
||||
})
|
||||
|
||||
const selectFile = () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.onchange = async () => {
|
||||
const blob = new Blob([input.files[0]], { type: 'application/octet-stream' });
|
||||
const rawEEPROM = new Uint8Array(await blob.arrayBuffer());
|
||||
state.binaryFile = rawEEPROM
|
||||
state.binaryName = input.files[0].name
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
const flashIt = async () => {
|
||||
if(!state.binaryFile){
|
||||
alert('请选择文件');
|
||||
return;
|
||||
}
|
||||
if(appStore.connectPort){
|
||||
await disconnect(appStore.connectPort);
|
||||
}
|
||||
let _connect = await connect();
|
||||
await readPacket(_connect, 0x18, 1000);
|
||||
const rawVersion = unpackVersion(state.binaryFile);
|
||||
const _data = new Uint8Array([0x30, 0x5, rawVersion.length, 0x0, ...rawVersion]);
|
||||
await sendPacket(_connect, _data);
|
||||
await readPacket(_connect, 0x18)
|
||||
const firmware = unpack(state.binaryFile);
|
||||
|
||||
if (firmware.length > 0xefff) throw new Error('Last resort boundary check failed. Whoever touched the code is an idiot.');
|
||||
|
||||
for (let i = 0; i < firmware.length; i += 0x100) {
|
||||
const data = firmware.slice(i, i + 0x100);
|
||||
const command = flash_generateCommand(data, i, firmware.length);
|
||||
|
||||
try {
|
||||
await sendPacket(_connect, command);
|
||||
await readPacket(_connect, 0x1a);
|
||||
} catch (e) {
|
||||
console.log('Flash command rejected. Aborting.');
|
||||
return Promise.reject(e);
|
||||
}
|
||||
|
||||
state.status = state.status + `更新进度 ${((i / firmware.length) * 100).toFixed(1)}%<br/>`
|
||||
nextTick(()=>{
|
||||
const textarea = document?.getElementById('statusArea');
|
||||
if(textarea)textarea.scrollTop = textarea?.scrollHeight;
|
||||
})
|
||||
}
|
||||
state.status = state.status + "更新进度 100.0%<br/>";
|
||||
state.status = state.status + "固件更新成功";
|
||||
nextTick(()=>{
|
||||
const textarea = document?.getElementById('statusArea');
|
||||
if(textarea)textarea.scrollTop = textarea?.scrollHeight;
|
||||
})
|
||||
if(appStore.connectPort){
|
||||
appStore.updateSettings({ connectPort: _connect });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Flash',
|
||||
};
|
||||
</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>
|
19
src/views/list/flash/locale/en-US.ts
Normal file
19
src/views/list/flash/locale/en-US.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
export default {
|
||||
'menu.list.cardList': 'Card List',
|
||||
'cardList.tab.title.all': 'All',
|
||||
'cardList.tab.title.content': 'Quality Inspection',
|
||||
'cardList.tab.title.service': 'The service',
|
||||
'cardList.tab.title.preset': 'Rules Preset',
|
||||
'cardList.searchInput.placeholder': 'Search',
|
||||
'cardList.enable': 'Enable',
|
||||
'cardList.disable': 'Disable',
|
||||
'cardList.content.delete': 'Delete',
|
||||
'cardList.content.inspection': 'Inspection',
|
||||
'cardList.content.action': 'Click Create Qc Content queue',
|
||||
'cardList.service.open': 'Open',
|
||||
'cardList.service.cancel': 'Cancel',
|
||||
'cardList.service.renew': 'Contract of service',
|
||||
'cardList.service.tag': 'Opened',
|
||||
'cardList.service.expiresTag': 'Expired',
|
||||
'cardList.preset.tag': 'Enable',
|
||||
};
|
19
src/views/list/flash/locale/zh-CN.ts
Normal file
19
src/views/list/flash/locale/zh-CN.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
export default {
|
||||
'menu.list.cardList': '卡片列表',
|
||||
'cardList.tab.title.all': '全部',
|
||||
'cardList.tab.title.content': '内容质检',
|
||||
'cardList.tab.title.service': '开通服务',
|
||||
'cardList.tab.title.preset': '规则预置',
|
||||
'cardList.searchInput.placeholder': '搜索',
|
||||
// 'cardList.statistic.enable': '已启用',
|
||||
// 'cardList.statistic.disable': '未启用',
|
||||
'cardList.content.delete': '删除',
|
||||
'cardList.content.inspection': '质检',
|
||||
'cardList.content.action': '点击创建质检内容队列',
|
||||
'cardList.service.open': '开通服务',
|
||||
'cardList.service.cancel': '取消服务',
|
||||
'cardList.service.renew': '续约服务',
|
||||
'cardList.service.tag': '已开通',
|
||||
'cardList.service.expiresTag': '已过期',
|
||||
'cardList.preset.tag': '已启用',
|
||||
};
|
186
src/views/list/flash/mock.ts
Normal file
186
src/views/list/flash/mock.ts
Normal file
|
@ -0,0 +1,186 @@
|
|||
import Mock from 'mockjs';
|
||||
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
|
||||
import { ServiceRecord } from '@/api/list';
|
||||
|
||||
const qualityInspectionList: ServiceRecord[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'quality',
|
||||
title: '视频类-历史导入',
|
||||
description: '2021-10-12 00:00:00',
|
||||
data: [
|
||||
{
|
||||
label: '待质检数',
|
||||
value: '120',
|
||||
},
|
||||
{
|
||||
label: '积压时长',
|
||||
value: '60s',
|
||||
},
|
||||
{
|
||||
label: '待抽检数',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'quality',
|
||||
title: '图文类-图片版权',
|
||||
description: '2021-12-11 18:30:00',
|
||||
data: [
|
||||
{
|
||||
label: '待质检数',
|
||||
value: '120',
|
||||
},
|
||||
{
|
||||
label: '积压时长',
|
||||
value: '60s',
|
||||
},
|
||||
{
|
||||
label: '待抽检数',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'quality',
|
||||
title: '图文类-高清图片',
|
||||
description: '2021-10-15 08:10:00',
|
||||
data: [
|
||||
{
|
||||
label: '待质检数',
|
||||
value: '120',
|
||||
},
|
||||
{
|
||||
label: '积压时长',
|
||||
value: '60s',
|
||||
},
|
||||
{
|
||||
label: '待抽检数',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const theServiceList: ServiceRecord[] = [
|
||||
{
|
||||
id: 1,
|
||||
icon: 'code',
|
||||
title: '漏斗分析',
|
||||
description:
|
||||
'用户行为分析之漏斗分析模型是企业实现精细化运营、进行用户行为分析的重要数据分析模型。',
|
||||
enable: true,
|
||||
actionType: 'button',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
icon: 'edit',
|
||||
title: '用户分布',
|
||||
description:
|
||||
'快速诊断用户人群,地域细分情况,了解数据分布的集中度,以及主要的数据分布的区间段是什么。',
|
||||
enable: true,
|
||||
actionType: 'button',
|
||||
expires: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
icon: 'user',
|
||||
title: '资源分发',
|
||||
description:
|
||||
'移动端动态化资源分发解决方案。提供稳定大流量服务支持、灵活定制的分发圈选规则,通过离线化预加载。',
|
||||
enable: false,
|
||||
actionType: 'button',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
icon: 'user',
|
||||
title: '用户画像分析',
|
||||
description:
|
||||
'用户画像就是将典型用户信息标签化,根据用户特征、业务场景和用户行为等信息,构建一个标签化的用户模型。',
|
||||
enable: true,
|
||||
actionType: 'button',
|
||||
},
|
||||
];
|
||||
const rulesPresetList: ServiceRecord[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: '内容屏蔽规则',
|
||||
description:
|
||||
'用户在执行特定的内容分发任务时,可使用内容屏蔽规则根据特定标签,过滤内容集合。',
|
||||
enable: true,
|
||||
actionType: 'switch',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '内容置顶规则',
|
||||
description:
|
||||
'该规则支持用户在执行特定内容分发任务时,对固定的几条内容置顶。',
|
||||
enable: true,
|
||||
actionType: 'switch',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '内容加权规则',
|
||||
description: '选定内容加权规则后可自定义从不同内容集合获取内容的概率。',
|
||||
enable: false,
|
||||
actionType: 'switch',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '内容分发规则',
|
||||
description: '内容分发时,对某些内容需要固定在C端展示的位置。',
|
||||
enable: true,
|
||||
actionType: 'switch',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '违禁内容识别',
|
||||
description: '精准识别赌博、刀枪、毒品、造假、贩假等违规物品和违规行为。',
|
||||
enable: false,
|
||||
actionType: 'switch',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '多语言文字符号识别',
|
||||
description:
|
||||
'精准识别英语、维语、藏语、蒙古语、朝鲜语等多种语言以及emoji表情形态的语义识别。',
|
||||
enable: false,
|
||||
actionType: 'switch',
|
||||
},
|
||||
];
|
||||
|
||||
setupMock({
|
||||
setup() {
|
||||
// Quality Inspection
|
||||
Mock.mock(new RegExp('/api/list/quality-inspection'), () => {
|
||||
return successResponseWrap(
|
||||
qualityInspectionList.map((_, index) => ({
|
||||
...qualityInspectionList[index % qualityInspectionList.length],
|
||||
id: Mock.Random.guid(),
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
// the service
|
||||
Mock.mock(new RegExp('/api/list/the-service'), () => {
|
||||
return successResponseWrap(
|
||||
theServiceList.map((_, index) => ({
|
||||
...theServiceList[index % theServiceList.length],
|
||||
id: Mock.Random.guid(),
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
// rules preset
|
||||
Mock.mock(new RegExp('/api/list/rules-preset'), () => {
|
||||
return successResponseWrap(
|
||||
rulesPresetList.map((_, index) => ({
|
||||
...rulesPresetList[index % rulesPresetList.length],
|
||||
id: Mock.Random.guid(),
|
||||
}))
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
204
src/views/list/image/components/card-wrap.vue
Normal file
204
src/views/list/image/components/card-wrap.vue
Normal file
|
@ -0,0 +1,204 @@
|
|||
<template>
|
||||
<div class="card-wrap">
|
||||
<a-card v-if="loading" :bordered="false" hoverable>
|
||||
<slot name="skeleton"></slot>
|
||||
</a-card>
|
||||
<a-card v-else :bordered="false" hoverable>
|
||||
<a-space align="start">
|
||||
<a-avatar
|
||||
v-if="icon"
|
||||
:size="24"
|
||||
style="margin-right: 8px; background-color: #626aea"
|
||||
>
|
||||
<icon-filter />
|
||||
</a-avatar>
|
||||
<a-card-meta>
|
||||
<template #title>
|
||||
<a-typography-text style="margin-right: 10px">
|
||||
{{ title }}
|
||||
</a-typography-text>
|
||||
<template v-if="showTag">
|
||||
<a-tag
|
||||
v-if="open && isExpires === false"
|
||||
size="small"
|
||||
color="green"
|
||||
>
|
||||
<template #icon>
|
||||
<icon-check-circle-fill />
|
||||
</template>
|
||||
<span>{{ tagText }}</span>
|
||||
</a-tag>
|
||||
<a-tag v-else-if="isExpires" size="small" color="red">
|
||||
<template #icon>
|
||||
<icon-check-circle-fill />
|
||||
</template>
|
||||
<span>{{ expiresTagText }}</span>
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
<template #description>
|
||||
{{ description }}
|
||||
<slot></slot>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
</a-space>
|
||||
<template #actions>
|
||||
<a-switch v-if="actionType === 'switch'" v-model="open" />
|
||||
<a-space v-else-if="actionType === 'button'">
|
||||
<template v-if="isExpires">
|
||||
<a-button type="outline" @click="renew">
|
||||
{{ expiresText }}
|
||||
</a-button>
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-button v-if="open" @click="handleToggle">
|
||||
{{ closeTxt }}
|
||||
</a-button>
|
||||
<a-button v-else-if="!open" type="outline" @click="handleToggle">
|
||||
{{ openTxt }}
|
||||
</a-button>
|
||||
</template>
|
||||
</a-space>
|
||||
<div v-else>
|
||||
<a-space>
|
||||
<a-button @click="toggle(false)">
|
||||
{{ closeTxt }}
|
||||
</a-button>
|
||||
<a-button type="primary" @click="toggle(true)">
|
||||
{{ openTxt }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { useToggle } from '@vueuse/core';
|
||||
|
||||
const props = defineProps({
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
actionType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
defaultValue: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
openTxt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
closeTxt: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
expiresText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
showTag: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
tagText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
expires: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
expiresTagText: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const [open, toggle] = useToggle(props.defaultValue);
|
||||
const handleToggle = () => {
|
||||
toggle();
|
||||
};
|
||||
const isExpires = ref(props.expires);
|
||||
const renew = () => {
|
||||
isExpires.value = false;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.card-wrap {
|
||||
height: 100%;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
border-radius: 4px;
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
// box-shadow: 4px 4px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
:deep(.arco-card) {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
.arco-card-body {
|
||||
height: 100%;
|
||||
.arco-space {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
.arco-space-item {
|
||||
height: 100%;
|
||||
&:last-child {
|
||||
flex: 1;
|
||||
}
|
||||
.arco-card-meta {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
.arco-card-meta-content {
|
||||
flex: 1;
|
||||
.arco-card-meta-description {
|
||||
margin-top: 8px;
|
||||
color: rgb(var(--gray-6));
|
||||
line-height: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.arco-card-meta-footer {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.arco-card-meta-title) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
// To prevent the shaking
|
||||
line-height: 28px;
|
||||
}
|
||||
:deep(.arco-skeleton-line) {
|
||||
&:last-child {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
115
src/views/list/image/components/quality-inspection.vue
Normal file
115
src/views/list/image/components/quality-inspection.vue
Normal file
|
@ -0,0 +1,115 @@
|
|||
<template>
|
||||
<div class="list-wrap">
|
||||
<a-typography-title class="block-title" :heading="6">
|
||||
{{ $t('cardList.tab.title.content') }}
|
||||
</a-typography-title>
|
||||
<a-row class="list-row" :gutter="24">
|
||||
<a-col
|
||||
:xs="12"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
:xxl="6"
|
||||
class="list-col"
|
||||
>
|
||||
<div class="card-wrap empty-wrap">
|
||||
<a-card :bordered="false" hoverable>
|
||||
<a-result :status="null" :title="$t('cardList.content.action')">
|
||||
<template #icon>
|
||||
<icon-plus style="font-size: 20px" />
|
||||
</template>
|
||||
</a-result>
|
||||
</a-card>
|
||||
</div>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-for="item in renderData"
|
||||
:key="item.id"
|
||||
class="list-col"
|
||||
:xs="12"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
:xxl="6"
|
||||
>
|
||||
<CardWrap
|
||||
:loading="loading"
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:default-value="item.enable"
|
||||
:action-type="item.actionType"
|
||||
:icon="item.icon"
|
||||
:open-txt="$t('cardList.content.inspection')"
|
||||
:close-txt="$t('cardList.content.delete')"
|
||||
:show-tag="false"
|
||||
>
|
||||
<a-descriptions
|
||||
style="margin-top: 16px"
|
||||
:data="item.data"
|
||||
layout="inline-horizontal"
|
||||
:column="2"
|
||||
/>
|
||||
<template #skeleton>
|
||||
<a-skeleton :animation="true">
|
||||
<a-skeleton-line
|
||||
:widths="['50%', '50%', '100%', '40%']"
|
||||
:rows="4"
|
||||
/>
|
||||
<a-skeleton-line :widths="['40%']" :rows="1" />
|
||||
</a-skeleton>
|
||||
</template>
|
||||
</CardWrap>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryInspectionList, ServiceRecord } from '@/api/list';
|
||||
import useRequest from '@/hooks/request';
|
||||
import CardWrap from './card-wrap.vue';
|
||||
|
||||
const defaultValue: ServiceRecord[] = new Array(3).fill({});
|
||||
const { loading, response: renderData } = useRequest<ServiceRecord[]>(
|
||||
queryInspectionList,
|
||||
defaultValue
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.card-wrap {
|
||||
height: 100%;
|
||||
transition: all 0.3s;
|
||||
border: 1px solid var(--color-neutral-3);
|
||||
&:hover {
|
||||
transform: translateY(-4px);
|
||||
}
|
||||
:deep(.arco-card-meta-description) {
|
||||
color: rgb(var(--gray-6));
|
||||
.arco-descriptions-item-label-inline {
|
||||
font-weight: normal;
|
||||
font-size: 12px;
|
||||
color: rgb(var(--gray-6));
|
||||
}
|
||||
.arco-descriptions-item-value-inline {
|
||||
color: rgb(var(--gray-8));
|
||||
}
|
||||
}
|
||||
}
|
||||
.empty-wrap {
|
||||
height: 200px;
|
||||
border-radius: 4px;
|
||||
:deep(.arco-card) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 4px;
|
||||
.arco-result-title {
|
||||
color: rgb(var(--gray-6));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
51
src/views/list/image/components/rules-preset.vue
Normal file
51
src/views/list/image/components/rules-preset.vue
Normal file
|
@ -0,0 +1,51 @@
|
|||
<template>
|
||||
<div class="list-wrap">
|
||||
<a-typography-title class="block-title" :heading="6">
|
||||
{{ $t('cardList.tab.title.preset') }}
|
||||
</a-typography-title>
|
||||
<a-row class="list-row" :gutter="24">
|
||||
<a-col
|
||||
v-for="item in renderData"
|
||||
:key="item.id"
|
||||
:xs="12"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
:xxl="6"
|
||||
class="list-col"
|
||||
style="min-height: 140px"
|
||||
>
|
||||
<CardWrap
|
||||
:loading="loading"
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:default-value="item.enable"
|
||||
:action-type="item.actionType"
|
||||
:tag-text="$t('cardList.preset.tag')"
|
||||
>
|
||||
<template #skeleton>
|
||||
<a-skeleton :animation="true">
|
||||
<a-skeleton-line :widths="['100%', '40%']" :rows="2" />
|
||||
<a-skeleton-line :widths="['40%']" :rows="1" />
|
||||
</a-skeleton>
|
||||
</template>
|
||||
</CardWrap>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryRulesPresetList, ServiceRecord } from '@/api/list';
|
||||
import useRequest from '@/hooks/request';
|
||||
import CardWrap from './card-wrap.vue';
|
||||
|
||||
const defaultValue: ServiceRecord[] = new Array(6).fill({});
|
||||
const { loading, response: renderData } = useRequest<ServiceRecord[]>(
|
||||
queryRulesPresetList,
|
||||
defaultValue
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
57
src/views/list/image/components/the-service.vue
Normal file
57
src/views/list/image/components/the-service.vue
Normal file
|
@ -0,0 +1,57 @@
|
|||
<template>
|
||||
<div class="list-wrap">
|
||||
<a-typography-title class="block-title" :heading="6">
|
||||
{{ $t('cardList.tab.title.service') }}
|
||||
</a-typography-title>
|
||||
<a-row class="list-row" :gutter="24">
|
||||
<a-col
|
||||
v-for="item in renderData"
|
||||
:key="item.id"
|
||||
:xs="12"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="6"
|
||||
:xl="6"
|
||||
:xxl="6"
|
||||
class="list-col"
|
||||
style="min-height: 162px"
|
||||
>
|
||||
<CardWrap
|
||||
:loading="loading"
|
||||
:title="item.title"
|
||||
:description="item.description"
|
||||
:default-value="item.enable"
|
||||
:action-type="item.actionType"
|
||||
:expires="item.expires"
|
||||
:open-txt="$t('cardList.service.open')"
|
||||
:close-txt="$t('cardList.service.cancel')"
|
||||
:expires-text="$t('cardList.service.renew')"
|
||||
:tag-text="$t('cardList.service.tag')"
|
||||
:expires-tag-text="$t('cardList.service.expiresTag')"
|
||||
:icon="item.icon"
|
||||
>
|
||||
<template #skeleton>
|
||||
<a-skeleton :animation="true">
|
||||
<a-skeleton-line :widths="['100%', '40%', '100%']" :rows="3" />
|
||||
<a-skeleton-line :widths="['40%']" :rows="1" />
|
||||
</a-skeleton>
|
||||
</template>
|
||||
</CardWrap>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryTheServiceList, ServiceRecord } from '@/api/list';
|
||||
import useRequest from '@/hooks/request';
|
||||
import CardWrap from './card-wrap.vue';
|
||||
|
||||
const defaultValue: ServiceRecord[] = new Array(4).fill({});
|
||||
const { loading, response: renderData } = useRequest<ServiceRecord[]>(
|
||||
queryTheServiceList,
|
||||
defaultValue
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
170
src/views/list/image/index.vue
Normal file
170
src/views/list/image/index.vue
Normal file
|
@ -0,0 +1,170 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['小工具', '开机图片']" />
|
||||
<a-row :gutter="20" align="stretch">
|
||||
<a-col :span="24">
|
||||
<a-spin :loading="state.loading" tip="写入中...">
|
||||
<a-card class="general-card" title="开机图片">
|
||||
<a-tabs :active-key="state.activeKey" @change="(e)=>{state.activeKey = e}">
|
||||
<a-tab-pane :key="1" title="LOSEHU 117"></a-tab-pane>
|
||||
<a-tab-pane :key="2" title="LOSEHU 118+"></a-tab-pane>
|
||||
</a-tabs>
|
||||
<div id="canvasDiv" style="zoom: 250%;"></div>
|
||||
<br>
|
||||
<a-space>
|
||||
<a-button @click="selectFile">选择图片</a-button>
|
||||
<a-button type="primary" :disabled="!state.binaryFile" @click="flashIt">更新</a-button>
|
||||
</a-space>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive, nextTick } from 'vue';
|
||||
import { useAppStore } from '@/store';
|
||||
import { eeprom_write, eeprom_reboot, eeprom_init } from '@/utils/serial.js';
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
||||
const state : {
|
||||
activeKey: any,
|
||||
binaryFile: any,
|
||||
loading: boolean
|
||||
} = reactive({
|
||||
activeKey: 1,
|
||||
binaryFile: undefined,
|
||||
loading: false
|
||||
})
|
||||
|
||||
const selectFile = () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.onchange = async () => {
|
||||
const blob = new Blob([input.files[0]], { type: 'application/octet-stream' });
|
||||
const file = URL.createObjectURL(blob);
|
||||
const canvas = document.createElement("canvas");
|
||||
canvas.classList.add("mt-3", "mr-3", "border", "shadow-sm");
|
||||
canvas.width = 128;
|
||||
canvas.height = 64;
|
||||
const canvas2 = canvas.cloneNode();
|
||||
const canvasDiv = document.getElementById('canvasDiv');
|
||||
canvasDiv.innerHTML = "";
|
||||
canvasDiv?.append(canvas, canvas2);
|
||||
|
||||
const img = new Image()
|
||||
img.src = file;
|
||||
img.onload = () => {
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx?.drawImage(img, 0, 0, 128, 64);
|
||||
const imageData = ctx?.getImageData(0, 0, canvas.width, canvas.height).data;
|
||||
function getPixel(x: any, y: any) {
|
||||
const index = y * 128 + x;
|
||||
const i = index * 4;
|
||||
return imageData[i] + imageData[i + 1] + imageData[i + 2] > 128 * 3 ? 0 : 1;
|
||||
}
|
||||
|
||||
const ctx2 = canvas2.getContext('2d');
|
||||
const imageData2 = ctx2.getImageData(0, 0, canvas2.width, canvas2.height);
|
||||
for (let y = 0; y < 64; y++) {
|
||||
for (let x = 0; x < 128; x++) {
|
||||
const index = y * 128 + x;
|
||||
const i = index * 4;
|
||||
const pixel = !getPixel(x, y);
|
||||
imageData2.data[i] = pixel * 255;
|
||||
imageData2.data[i + 1] = pixel * 255;
|
||||
imageData2.data[i + 2] = pixel * 255;
|
||||
imageData2.data[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
ctx2.putImageData(imageData2, 0, 0);
|
||||
|
||||
const outputArray = new Uint8Array(1024);
|
||||
// getPixel(i) outputs the pixel value for any x y coordinate. 0 = black, 1 = white.
|
||||
// the outputArray is 1024 bytes, where each byte is 8 pixels IN VERTICAL ORDER.
|
||||
|
||||
let i = 0;
|
||||
for (let y = 0; y < 64; y += 8) {
|
||||
for (let x = 0; x < 128; x++) {
|
||||
let byte = 0;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
byte |= getPixel(x, y + i) << i;
|
||||
}
|
||||
outputArray[i++] = byte;
|
||||
}
|
||||
}
|
||||
|
||||
state.binaryFile = outputArray;
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
|
||||
const flashIt = async () => {
|
||||
if(appStore.connectState != true){alert('请先连接手台!'); return;};
|
||||
state.loading = true
|
||||
let position = 0x1E350;
|
||||
if(state.activeKey == 2)position = 0x2080;
|
||||
await eeprom_init(appStore.connectPort);
|
||||
const rawEEPROM = state.binaryFile;
|
||||
for (let i = position; i < rawEEPROM.length + position; i += 0x80) {
|
||||
await eeprom_write(appStore.connectPort, i, rawEEPROM.slice(i - position, i - position + 0x80));
|
||||
}
|
||||
await eeprom_reboot(appStore.connectPort);
|
||||
state.loading = false
|
||||
}
|
||||
|
||||
</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>
|
19
src/views/list/image/locale/en-US.ts
Normal file
19
src/views/list/image/locale/en-US.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
export default {
|
||||
'menu.list.cardList': 'Card List',
|
||||
'cardList.tab.title.all': 'All',
|
||||
'cardList.tab.title.content': 'Quality Inspection',
|
||||
'cardList.tab.title.service': 'The service',
|
||||
'cardList.tab.title.preset': 'Rules Preset',
|
||||
'cardList.searchInput.placeholder': 'Search',
|
||||
'cardList.enable': 'Enable',
|
||||
'cardList.disable': 'Disable',
|
||||
'cardList.content.delete': 'Delete',
|
||||
'cardList.content.inspection': 'Inspection',
|
||||
'cardList.content.action': 'Click Create Qc Content queue',
|
||||
'cardList.service.open': 'Open',
|
||||
'cardList.service.cancel': 'Cancel',
|
||||
'cardList.service.renew': 'Contract of service',
|
||||
'cardList.service.tag': 'Opened',
|
||||
'cardList.service.expiresTag': 'Expired',
|
||||
'cardList.preset.tag': 'Enable',
|
||||
};
|
19
src/views/list/image/locale/zh-CN.ts
Normal file
19
src/views/list/image/locale/zh-CN.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
export default {
|
||||
'menu.list.cardList': '卡片列表',
|
||||
'cardList.tab.title.all': '全部',
|
||||
'cardList.tab.title.content': '内容质检',
|
||||
'cardList.tab.title.service': '开通服务',
|
||||
'cardList.tab.title.preset': '规则预置',
|
||||
'cardList.searchInput.placeholder': '搜索',
|
||||
// 'cardList.statistic.enable': '已启用',
|
||||
// 'cardList.statistic.disable': '未启用',
|
||||
'cardList.content.delete': '删除',
|
||||
'cardList.content.inspection': '质检',
|
||||
'cardList.content.action': '点击创建质检内容队列',
|
||||
'cardList.service.open': '开通服务',
|
||||
'cardList.service.cancel': '取消服务',
|
||||
'cardList.service.renew': '续约服务',
|
||||
'cardList.service.tag': '已开通',
|
||||
'cardList.service.expiresTag': '已过期',
|
||||
'cardList.preset.tag': '已启用',
|
||||
};
|
186
src/views/list/image/mock.ts
Normal file
186
src/views/list/image/mock.ts
Normal file
|
@ -0,0 +1,186 @@
|
|||
import Mock from 'mockjs';
|
||||
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
|
||||
import { ServiceRecord } from '@/api/list';
|
||||
|
||||
const qualityInspectionList: ServiceRecord[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'quality',
|
||||
title: '视频类-历史导入',
|
||||
description: '2021-10-12 00:00:00',
|
||||
data: [
|
||||
{
|
||||
label: '待质检数',
|
||||
value: '120',
|
||||
},
|
||||
{
|
||||
label: '积压时长',
|
||||
value: '60s',
|
||||
},
|
||||
{
|
||||
label: '待抽检数',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'quality',
|
||||
title: '图文类-图片版权',
|
||||
description: '2021-12-11 18:30:00',
|
||||
data: [
|
||||
{
|
||||
label: '待质检数',
|
||||
value: '120',
|
||||
},
|
||||
{
|
||||
label: '积压时长',
|
||||
value: '60s',
|
||||
},
|
||||
{
|
||||
label: '待抽检数',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'quality',
|
||||
title: '图文类-高清图片',
|
||||
description: '2021-10-15 08:10:00',
|
||||
data: [
|
||||
{
|
||||
label: '待质检数',
|
||||
value: '120',
|
||||
},
|
||||
{
|
||||
label: '积压时长',
|
||||
value: '60s',
|
||||
},
|
||||
{
|
||||
label: '待抽检数',
|
||||
value: '0',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const theServiceList: ServiceRecord[] = [
|
||||
{
|
||||
id: 1,
|
||||
icon: 'code',
|
||||
title: '漏斗分析',
|
||||
description:
|
||||
'用户行为分析之漏斗分析模型是企业实现精细化运营、进行用户行为分析的重要数据分析模型。',
|
||||
enable: true,
|
||||
actionType: 'button',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
icon: 'edit',
|
||||
title: '用户分布',
|
||||
description:
|
||||
'快速诊断用户人群,地域细分情况,了解数据分布的集中度,以及主要的数据分布的区间段是什么。',
|
||||
enable: true,
|
||||
actionType: 'button',
|
||||
expires: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
icon: 'user',
|
||||
title: '资源分发',
|
||||
description:
|
||||
'移动端动态化资源分发解决方案。提供稳定大流量服务支持、灵活定制的分发圈选规则,通过离线化预加载。',
|
||||
enable: false,
|
||||
actionType: 'button',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
icon: 'user',
|
||||
title: '用户画像分析',
|
||||
description:
|
||||
'用户画像就是将典型用户信息标签化,根据用户特征、业务场景和用户行为等信息,构建一个标签化的用户模型。',
|
||||
enable: true,
|
||||
actionType: 'button',
|
||||
},
|
||||
];
|
||||
const rulesPresetList: ServiceRecord[] = [
|
||||
{
|
||||
id: 1,
|
||||
title: '内容屏蔽规则',
|
||||
description:
|
||||
'用户在执行特定的内容分发任务时,可使用内容屏蔽规则根据特定标签,过滤内容集合。',
|
||||
enable: true,
|
||||
actionType: 'switch',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: '内容置顶规则',
|
||||
description:
|
||||
'该规则支持用户在执行特定内容分发任务时,对固定的几条内容置顶。',
|
||||
enable: true,
|
||||
actionType: 'switch',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '内容加权规则',
|
||||
description: '选定内容加权规则后可自定义从不同内容集合获取内容的概率。',
|
||||
enable: false,
|
||||
actionType: 'switch',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '内容分发规则',
|
||||
description: '内容分发时,对某些内容需要固定在C端展示的位置。',
|
||||
enable: true,
|
||||
actionType: 'switch',
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '违禁内容识别',
|
||||
description: '精准识别赌博、刀枪、毒品、造假、贩假等违规物品和违规行为。',
|
||||
enable: false,
|
||||
actionType: 'switch',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '多语言文字符号识别',
|
||||
description:
|
||||
'精准识别英语、维语、藏语、蒙古语、朝鲜语等多种语言以及emoji表情形态的语义识别。',
|
||||
enable: false,
|
||||
actionType: 'switch',
|
||||
},
|
||||
];
|
||||
|
||||
setupMock({
|
||||
setup() {
|
||||
// Quality Inspection
|
||||
Mock.mock(new RegExp('/api/list/quality-inspection'), () => {
|
||||
return successResponseWrap(
|
||||
qualityInspectionList.map((_, index) => ({
|
||||
...qualityInspectionList[index % qualityInspectionList.length],
|
||||
id: Mock.Random.guid(),
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
// the service
|
||||
Mock.mock(new RegExp('/api/list/the-service'), () => {
|
||||
return successResponseWrap(
|
||||
theServiceList.map((_, index) => ({
|
||||
...theServiceList[index % theServiceList.length],
|
||||
id: Mock.Random.guid(),
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
// rules preset
|
||||
Mock.mock(new RegExp('/api/list/rules-preset'), () => {
|
||||
return successResponseWrap(
|
||||
rulesPresetList.map((_, index) => ({
|
||||
...rulesPresetList[index % rulesPresetList.length],
|
||||
id: Mock.Random.guid(),
|
||||
}))
|
||||
);
|
||||
});
|
||||
},
|
||||
});
|
Loading…
Reference in a new issue