新增耳机阻抗
This commit is contained in:
@@ -233,6 +233,7 @@ const local: App.I18n.Schema = {
|
||||
headphone: 'Headphones',
|
||||
headphone_brand: 'Brands',
|
||||
headphone_model: 'Models',
|
||||
headphone_impedance: 'Impedance',
|
||||
upgrade: 'Upgrade',
|
||||
upgrade_ota: 'OTA',
|
||||
'upgrade_ota-target-device': 'Target Devices',
|
||||
|
||||
@@ -229,6 +229,7 @@ const local: App.I18n.Schema = {
|
||||
headphone: '耳机管理',
|
||||
headphone_brand: '品牌管理',
|
||||
headphone_model: '型号管理',
|
||||
headphone_impedance: '耳机阻抗',
|
||||
upgrade: '升级管理',
|
||||
upgrade_ota: 'OTA 管理',
|
||||
'upgrade_ota-target-device': '定向升级',
|
||||
|
||||
@@ -21,6 +21,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
|
||||
"iframe-page": () => import("@/views/_builtin/iframe-page/[url].vue"),
|
||||
login: () => import("@/views/_builtin/login/index.vue"),
|
||||
headphone_brand: () => import("@/views/headphone/brand/index.vue"),
|
||||
headphone_impedance: () => import("@/views/headphone/impedance/index.vue"),
|
||||
headphone_model: () => import("@/views/headphone/model/index.vue"),
|
||||
home: () => import("@/views/home/index.vue"),
|
||||
meilisearch: () => import("@/views/meilisearch/index.vue"),
|
||||
|
||||
@@ -62,6 +62,18 @@ export const generatedRoutes: GeneratedRoute[] = [
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'headphone_impedance',
|
||||
path: '/headphone/impedance',
|
||||
component: 'view.headphone_impedance',
|
||||
meta: {
|
||||
title: 'headphone_impedance',
|
||||
i18nKey: 'route.headphone_impedance',
|
||||
icon: 'mdi:omega',
|
||||
order: 3,
|
||||
keepAlive: true
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'headphone_model',
|
||||
path: '/headphone/model',
|
||||
|
||||
@@ -168,6 +168,7 @@ const routeMap: RouteMap = {
|
||||
"500": "/500",
|
||||
"headphone": "/headphone",
|
||||
"headphone_brand": "/headphone/brand",
|
||||
"headphone_impedance": "/headphone/impedance",
|
||||
"headphone_model": "/headphone/model",
|
||||
"home": "/home",
|
||||
"iframe-page": "/iframe-page/:url",
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { request } from '../request';
|
||||
import type { PageParams } from './brand';
|
||||
|
||||
export function fetchGetHeadphoneImpedanceList(
|
||||
params?: PageParams & {
|
||||
mac_addr?: string;
|
||||
device_model?: string;
|
||||
headphone_brand?: string;
|
||||
headphone_model?: string;
|
||||
}
|
||||
) {
|
||||
return request<Api.Common.PageResult<Api.HeadphoneImpedance.Item>>({
|
||||
url: '/headphone-impedance/',
|
||||
method: 'get',
|
||||
params
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchGetHeadphoneImpedance(id: number) {
|
||||
return request<Api.HeadphoneImpedance.Item>({
|
||||
url: `/headphone-impedance/${id}`,
|
||||
method: 'get'
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchCreateHeadphoneImpedance(data: Record<string, unknown>) {
|
||||
return request<Api.HeadphoneImpedance.Item>({
|
||||
url: '/headphone-impedance/',
|
||||
method: 'post',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchUpdateHeadphoneImpedance(id: number, data: Record<string, unknown>) {
|
||||
return request<Api.HeadphoneImpedance.Item>({
|
||||
url: `/headphone-impedance/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchDeleteHeadphoneImpedance(id: number) {
|
||||
return request({
|
||||
url: `/headphone-impedance/${id}`,
|
||||
method: 'delete'
|
||||
});
|
||||
}
|
||||
@@ -6,6 +6,7 @@ export * from './user';
|
||||
export * from './ota';
|
||||
export * from './blacklist';
|
||||
export * from './ota-target-device';
|
||||
export * from './headphone-impedance';
|
||||
export * from './share-code-log';
|
||||
export * from './model';
|
||||
export * from './meilisearch';
|
||||
|
||||
@@ -116,6 +116,22 @@ declare namespace Api {
|
||||
}
|
||||
}
|
||||
|
||||
namespace HeadphoneImpedance {
|
||||
interface Item {
|
||||
id: number;
|
||||
mac_addr: string;
|
||||
device_model: string;
|
||||
impedance_ohm: number;
|
||||
headphone_brand: string;
|
||||
headphone_model: string;
|
||||
headphone_brand_norm?: string;
|
||||
headphone_model_norm?: string;
|
||||
ip_addr?: string;
|
||||
create_at?: string | null;
|
||||
update_at?: string | null;
|
||||
}
|
||||
}
|
||||
|
||||
namespace ShareCodeLog {
|
||||
interface Item {
|
||||
id: number;
|
||||
|
||||
@@ -22,6 +22,7 @@ declare module "@elegant-router/types" {
|
||||
"500": "/500";
|
||||
"headphone": "/headphone";
|
||||
"headphone_brand": "/headphone/brand";
|
||||
"headphone_impedance": "/headphone/impedance";
|
||||
"headphone_model": "/headphone/model";
|
||||
"home": "/home";
|
||||
"iframe-page": "/iframe-page/:url";
|
||||
@@ -130,6 +131,7 @@ declare module "@elegant-router/types" {
|
||||
| "iframe-page"
|
||||
| "login"
|
||||
| "headphone_brand"
|
||||
| "headphone_impedance"
|
||||
| "headphone_model"
|
||||
| "home"
|
||||
| "meilisearch"
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
<script setup lang="ts">
|
||||
import { h, onMounted, reactive, ref } from 'vue';
|
||||
import {
|
||||
NButton,
|
||||
NPopconfirm,
|
||||
NSpace,
|
||||
type DataTableColumns,
|
||||
type FormInst,
|
||||
type FormRules
|
||||
} from 'naive-ui';
|
||||
import {
|
||||
fetchCreateHeadphoneImpedance,
|
||||
fetchDeleteHeadphoneImpedance,
|
||||
fetchGetHeadphoneImpedanceList,
|
||||
fetchUpdateHeadphoneImpedance
|
||||
} from '@/service/api';
|
||||
import SvgIcon from '@/components/custom/svg-icon.vue';
|
||||
import { MAC_PATTERN, formatDateTime } from '../../upgrade/shared';
|
||||
|
||||
/**
|
||||
* 耳机阻抗管理
|
||||
*
|
||||
* 管理设备端上报的 user_headphone_impedance 记录:
|
||||
* 连接耳机后测得的阻抗 + 识别出的耳机品牌/型号,支持筛选、修正与删除。
|
||||
*/
|
||||
defineOptions({ name: 'HeadphoneImpedance' });
|
||||
|
||||
// 表数据里出现过的设备型号(X8 / X9 / B9)
|
||||
const modelOptions = [
|
||||
{ label: 'Luxsin-X8', value: 'Luxsin-X8' },
|
||||
{ label: 'Luxsin-X9', value: 'Luxsin-X9' },
|
||||
{ label: 'Luxsin-B9', value: 'Luxsin-B9' }
|
||||
];
|
||||
|
||||
const loading = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
const tableData = ref<Api.HeadphoneImpedance.Item[]>([]);
|
||||
|
||||
const searchForm = reactive({
|
||||
mac_addr: '',
|
||||
device_model: null as string | null,
|
||||
headphone_brand: '',
|
||||
headphone_model: ''
|
||||
});
|
||||
|
||||
const pagination = reactive({
|
||||
page: 1,
|
||||
pageSize: 10,
|
||||
itemCount: 0,
|
||||
pageSizes: [10, 20, 50, 100]
|
||||
});
|
||||
|
||||
function emptyForm() {
|
||||
return {
|
||||
id: null as number | null,
|
||||
mac_addr: '',
|
||||
device_model: 'Luxsin-X8',
|
||||
impedance_ohm: 0,
|
||||
headphone_brand: '',
|
||||
headphone_model: '',
|
||||
ip_addr: ''
|
||||
};
|
||||
}
|
||||
|
||||
const formData = reactive(emptyForm());
|
||||
const formRef = ref<FormInst | null>(null);
|
||||
const dialogVisible = ref(false);
|
||||
const dialogTitle = ref('新增记录');
|
||||
|
||||
const formRules: FormRules = {
|
||||
mac_addr: [
|
||||
{ required: true, message: '请输入 MAC 地址', trigger: ['blur', 'input'] },
|
||||
{
|
||||
validator: (_rule, value: string) => !value || MAC_PATTERN.test(value),
|
||||
message: 'MAC 格式不正确,如 40:D9:5A:D8:06:9C',
|
||||
trigger: ['blur', 'input']
|
||||
}
|
||||
],
|
||||
device_model: [{ required: true, message: '请选择设备型号', trigger: ['change', 'blur'] }],
|
||||
impedance_ohm: [
|
||||
{ type: 'number', required: true, message: '请输入阻抗值', trigger: ['change', 'blur'] },
|
||||
{
|
||||
validator: (_rule, value: number) => value >= 0 && value <= 10000,
|
||||
message: '阻抗范围 0 ~ 10000 Ω',
|
||||
trigger: ['change', 'blur']
|
||||
}
|
||||
],
|
||||
headphone_brand: [{ required: true, message: '请输入耳机品牌', trigger: ['blur', 'input'] }],
|
||||
headphone_model: [{ required: true, message: '请输入耳机型号', trigger: ['blur', 'input'] }]
|
||||
};
|
||||
|
||||
const columns: DataTableColumns<Api.HeadphoneImpedance.Item> = [
|
||||
{ title: 'ID', key: 'id', width: 70 },
|
||||
{ title: 'MAC 地址', key: 'mac_addr', width: 170 },
|
||||
{ title: '设备型号', key: 'device_model', width: 110 },
|
||||
{
|
||||
title: '阻抗',
|
||||
key: 'impedance_ohm',
|
||||
width: 90,
|
||||
render: row => `${row.impedance_ohm} Ω`
|
||||
},
|
||||
{ title: '耳机品牌', key: 'headphone_brand', minWidth: 120, ellipsis: { tooltip: true } },
|
||||
{ title: '耳机型号', key: 'headphone_model', minWidth: 140, ellipsis: { tooltip: true } },
|
||||
{ title: 'IP', key: 'ip_addr', width: 140, ellipsis: { tooltip: true } },
|
||||
{
|
||||
title: '上报时间',
|
||||
key: 'create_at',
|
||||
width: 170,
|
||||
render: row => formatDateTime(row.create_at)
|
||||
},
|
||||
{
|
||||
title: '更新时间',
|
||||
key: 'update_at',
|
||||
width: 170,
|
||||
render: row => formatDateTime(row.update_at)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 130,
|
||||
render(row) {
|
||||
return h(NSpace, { size: 'small', wrap: false }, () => [
|
||||
h(
|
||||
NButton,
|
||||
{ size: 'small', type: 'primary', ghost: true, onClick: () => openEdit(row) },
|
||||
{ default: () => '编辑' }
|
||||
),
|
||||
h(
|
||||
NPopconfirm,
|
||||
{ onPositiveClick: () => handleDelete(row) },
|
||||
{
|
||||
trigger: () =>
|
||||
h(
|
||||
NButton,
|
||||
{ size: 'small', type: 'error', ghost: true },
|
||||
{ default: () => '删除' }
|
||||
),
|
||||
default: () => `确认删除 ID=${row.id} 的记录?`
|
||||
}
|
||||
)
|
||||
]);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
async function loadData() {
|
||||
loading.value = true;
|
||||
const params: Record<string, unknown> = {
|
||||
skip: (pagination.page - 1) * pagination.pageSize,
|
||||
limit: pagination.pageSize,
|
||||
mac_addr: searchForm.mac_addr.trim() || undefined,
|
||||
device_model: searchForm.device_model || undefined,
|
||||
headphone_brand: searchForm.headphone_brand.trim() || undefined,
|
||||
headphone_model: searchForm.headphone_model.trim() || undefined
|
||||
};
|
||||
const { data, error } = await fetchGetHeadphoneImpedanceList(params as any);
|
||||
if (!error && data) {
|
||||
tableData.value = data.items || [];
|
||||
pagination.itemCount = data.total || 0;
|
||||
} else if (!error) {
|
||||
tableData.value = [];
|
||||
pagination.itemCount = 0;
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function handleSearch() {
|
||||
pagination.page = 1;
|
||||
loadData();
|
||||
}
|
||||
|
||||
function handleReset() {
|
||||
searchForm.mac_addr = '';
|
||||
searchForm.device_model = null;
|
||||
searchForm.headphone_brand = '';
|
||||
searchForm.headphone_model = '';
|
||||
pagination.page = 1;
|
||||
loadData();
|
||||
}
|
||||
|
||||
function handlePageChange(page: number) {
|
||||
pagination.page = page;
|
||||
loadData();
|
||||
}
|
||||
|
||||
function handlePageSizeChange(pageSize: number) {
|
||||
pagination.pageSize = pageSize;
|
||||
pagination.page = 1;
|
||||
loadData();
|
||||
}
|
||||
|
||||
function openCreate() {
|
||||
Object.assign(formData, emptyForm());
|
||||
dialogTitle.value = '新增记录';
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
function openEdit(row: Api.HeadphoneImpedance.Item) {
|
||||
Object.assign(formData, {
|
||||
id: row.id,
|
||||
mac_addr: row.mac_addr,
|
||||
device_model: row.device_model,
|
||||
impedance_ohm: row.impedance_ohm,
|
||||
headphone_brand: row.headphone_brand,
|
||||
headphone_model: row.headphone_model,
|
||||
ip_addr: row.ip_addr || ''
|
||||
});
|
||||
dialogTitle.value = '编辑记录';
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
|
||||
function handleSubmit() {
|
||||
formRef.value?.validate(async errors => {
|
||||
if (errors) return;
|
||||
submitLoading.value = true;
|
||||
const payload = {
|
||||
mac_addr: formData.mac_addr.trim(),
|
||||
device_model: formData.device_model,
|
||||
impedance_ohm: formData.impedance_ohm,
|
||||
headphone_brand: formData.headphone_brand.trim(),
|
||||
headphone_model: formData.headphone_model.trim(),
|
||||
ip_addr: formData.ip_addr.trim()
|
||||
};
|
||||
const { error } = formData.id
|
||||
? await fetchUpdateHeadphoneImpedance(formData.id, payload)
|
||||
: await fetchCreateHeadphoneImpedance(payload);
|
||||
submitLoading.value = false;
|
||||
if (!error) {
|
||||
window.$message?.success(formData.id ? '保存成功' : '新增成功');
|
||||
dialogVisible.value = false;
|
||||
loadData();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function handleDelete(row: Api.HeadphoneImpedance.Item) {
|
||||
const { error } = await fetchDeleteHeadphoneImpedance(row.id);
|
||||
if (!error) {
|
||||
window.$message?.success('删除成功');
|
||||
// 当前页删空后回退一页
|
||||
if (tableData.value.length === 1 && pagination.page > 1) {
|
||||
pagination.page -= 1;
|
||||
}
|
||||
loadData();
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(loadData);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<NSpace vertical :size="16">
|
||||
<NCard :bordered="false" class="card-wrapper" size="small">
|
||||
<div class="grid grid-cols-1 gap-12px md:grid-cols-4">
|
||||
<NInput v-model:value="searchForm.mac_addr" placeholder="MAC 地址" clearable @keyup.enter="handleSearch" />
|
||||
<NSelect
|
||||
v-model:value="searchForm.device_model"
|
||||
:options="modelOptions"
|
||||
placeholder="设备型号"
|
||||
clearable
|
||||
@update:value="handleSearch"
|
||||
/>
|
||||
<NInput v-model:value="searchForm.headphone_brand" placeholder="耳机品牌" clearable @keyup.enter="handleSearch" />
|
||||
<NInput v-model:value="searchForm.headphone_model" placeholder="耳机型号" clearable @keyup.enter="handleSearch" />
|
||||
</div>
|
||||
<div class="mt-12px flex items-center justify-between">
|
||||
<NSpace>
|
||||
<NButton type="primary" @click="handleSearch">
|
||||
<template #icon>
|
||||
<SvgIcon icon="ic:round-search" />
|
||||
</template>
|
||||
搜索
|
||||
</NButton>
|
||||
<NButton @click="handleReset">
|
||||
<template #icon>
|
||||
<SvgIcon icon="mdi:refresh" />
|
||||
</template>
|
||||
重置
|
||||
</NButton>
|
||||
</NSpace>
|
||||
<NButton type="primary" @click="openCreate">
|
||||
<template #icon>
|
||||
<SvgIcon icon="mdi:plus" />
|
||||
</template>
|
||||
新增记录
|
||||
</NButton>
|
||||
</div>
|
||||
</NCard>
|
||||
|
||||
<NCard :bordered="false" class="card-wrapper" size="small" title="阻抗记录">
|
||||
<NDataTable
|
||||
remote
|
||||
:columns="columns"
|
||||
:data="tableData"
|
||||
:loading="loading"
|
||||
:pagination="{
|
||||
...pagination,
|
||||
showSizePicker: true,
|
||||
onChange: handlePageChange,
|
||||
onUpdatePageSize: handlePageSizeChange
|
||||
}"
|
||||
:row-key="(row: Api.HeadphoneImpedance.Item) => row.id"
|
||||
:scroll-x="1400"
|
||||
/>
|
||||
</NCard>
|
||||
|
||||
<NModal
|
||||
v-model:show="dialogVisible"
|
||||
preset="card"
|
||||
:title="dialogTitle"
|
||||
class="w-500px"
|
||||
:mask-closable="false"
|
||||
>
|
||||
<NForm
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
label-placement="left"
|
||||
label-width="90"
|
||||
>
|
||||
<NFormItem label="MAC 地址" path="mac_addr">
|
||||
<NInput v-model:value="formData.mac_addr" placeholder="如 40:D9:5A:D8:06:9C" />
|
||||
</NFormItem>
|
||||
<NFormItem label="设备型号" path="device_model">
|
||||
<NSelect v-model:value="formData.device_model" :options="modelOptions" />
|
||||
</NFormItem>
|
||||
<NFormItem label="阻抗 (Ω)" path="impedance_ohm">
|
||||
<NInputNumber
|
||||
v-model:value="formData.impedance_ohm"
|
||||
:min="0"
|
||||
:max="10000"
|
||||
:precision="0"
|
||||
class="w-full"
|
||||
placeholder="0 ~ 10000"
|
||||
/>
|
||||
</NFormItem>
|
||||
<NFormItem label="耳机品牌" path="headphone_brand">
|
||||
<NInput v-model:value="formData.headphone_brand" placeholder="如 Sennheiser" />
|
||||
</NFormItem>
|
||||
<NFormItem label="耳机型号" path="headphone_model">
|
||||
<NInput v-model:value="formData.headphone_model" placeholder="如 HD800" />
|
||||
</NFormItem>
|
||||
<NFormItem label="IP 地址" path="ip_addr">
|
||||
<NInput v-model:value="formData.ip_addr" placeholder="可选" />
|
||||
</NFormItem>
|
||||
</NForm>
|
||||
<template #footer>
|
||||
<NSpace justify="end">
|
||||
<NButton @click="dialogVisible = false">取消</NButton>
|
||||
<NButton type="primary" :loading="submitLoading" @click="handleSubmit">保存</NButton>
|
||||
</NSpace>
|
||||
</template>
|
||||
</NModal>
|
||||
</NSpace>
|
||||
</template>
|
||||
Reference in New Issue
Block a user