mirror of
https://github.com/xinnan-tech/xiaozhi-esp32-server.git
synced 2026-07-29 18:13:56 +08:00
fix(manager): align frontend and backend field contracts
This commit is contained in:
@@ -27,7 +27,7 @@ export default {
|
||||
getFileList(params, callback) {
|
||||
const queryParams = new URLSearchParams({
|
||||
page: params.page,
|
||||
pageSize: params.pageSize
|
||||
limit: params.pageSize
|
||||
}).toString();
|
||||
|
||||
RequestService.sendRequest()
|
||||
|
||||
@@ -79,6 +79,7 @@ export default {
|
||||
remark: params.remark,
|
||||
referenceAudio: params.referenceAudio,
|
||||
referenceText: params.referenceText,
|
||||
sort: params.sort,
|
||||
ttsModelId: params.ttsModelId,
|
||||
ttsVoice: params.voiceCode,
|
||||
voiceDemo: params.voiceDemo || ''
|
||||
@@ -93,4 +94,4 @@ export default {
|
||||
});
|
||||
}).send();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,23 +141,24 @@
|
||||
<p class="section-desc">{{ $t('addressBookManagement.setPermissionDesc', { count: selectedPermissions.length }) }}</p>
|
||||
</div>
|
||||
<div class="section-actions">
|
||||
<CustomButton size="small" @click="handleCancel">{{ $t('common.cancel') }}</CustomButton>
|
||||
<CustomButton size="small" @click="handleToggleSelectAll">{{ isAllSelected ? $t('addressBookManagement.deselectAll') : $t('addressBookManagement.selectAll') }}</CustomButton>
|
||||
<CustomButton type="confirm" size="small" @click="handleSavePermissions">{{ $t('addressBookManagement.save') }}</CustomButton>
|
||||
<CustomButton size="small" :disabled="permissionsLoading" @click="handleCancel">{{ $t('common.cancel') }}</CustomButton>
|
||||
<CustomButton size="small" :disabled="permissionsLoading" @click="handleToggleSelectAll">{{ isAllSelected ? $t('addressBookManagement.deselectAll') : $t('addressBookManagement.selectAll') }}</CustomButton>
|
||||
<CustomButton type="confirm" size="small" :disabled="permissionsLoading" @click="handleSavePermissions">{{ $t('addressBookManagement.save') }}</CustomButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="permission-grid">
|
||||
<div v-loading="permissionsLoading" class="permission-grid">
|
||||
<div
|
||||
v-for="device in allDevices"
|
||||
:key="device.id"
|
||||
class="permission-item"
|
||||
:class="{ active: selectedPermissions.includes(device.id) }"
|
||||
:class="{ active: selectedPermissions.includes(device.deviceId) }"
|
||||
>
|
||||
<el-checkbox
|
||||
class="permission-checkbox"
|
||||
:value="selectedPermissions.includes(device.id)"
|
||||
@change="(checked) => handlePermissionToggle(device.id, checked)"
|
||||
:disabled="permissionsLoading"
|
||||
:value="selectedPermissions.includes(device.deviceId)"
|
||||
@change="(checked) => handlePermissionToggle(device.deviceId, checked)"
|
||||
></el-checkbox>
|
||||
<div class="permission-avatar">
|
||||
<img :src="getDeviceAvatar(device.id)" alt="avatar" />
|
||||
@@ -229,7 +230,9 @@ export default {
|
||||
editAgentNameValue: '',
|
||||
editingDeviceId: null,
|
||||
editingDeviceName: '',
|
||||
mqttServiceAvailable: false
|
||||
mqttServiceAvailable: false,
|
||||
permissionRequestSequence: 0,
|
||||
permissionsLoading: false
|
||||
};
|
||||
},
|
||||
created() {
|
||||
@@ -363,18 +366,45 @@ export default {
|
||||
this.loadAddressBookPermissions(device.deviceId);
|
||||
},
|
||||
loadAddressBookPermissions(macAddress) {
|
||||
const requestId = ++this.permissionRequestSequence;
|
||||
this.permissionsLoading = true;
|
||||
this.selectedPermissions = [];
|
||||
this.originalPermissions = [];
|
||||
this.editingDeviceId = null;
|
||||
this.editingDeviceName = '';
|
||||
this.allDevices.forEach(device => {
|
||||
device.addressBookAlias = '';
|
||||
});
|
||||
AddressBookApi.getAddressBookList(macAddress, (res) => {
|
||||
if (
|
||||
requestId !== this.permissionRequestSequence ||
|
||||
this.selectedDevice?.deviceId !== macAddress
|
||||
) {
|
||||
return;
|
||||
}
|
||||
this.permissionsLoading = false;
|
||||
if (res.data?.code === 0) {
|
||||
const permissions = res.data.data || [];
|
||||
const permissionsByTargetMac = new Map(
|
||||
permissions.map(permission => [
|
||||
(permission.targetMac || '').toLowerCase(),
|
||||
permission
|
||||
])
|
||||
);
|
||||
// 设置已选择的权限
|
||||
this.selectedPermissions = permissions
|
||||
.filter(p => p.hasPermission)
|
||||
.map(p => p.targetMac);
|
||||
const permittedTargetMacs = new Set(
|
||||
permissions
|
||||
.filter(p => p.hasPermission)
|
||||
.map(p => (p.targetMac || '').toLowerCase())
|
||||
);
|
||||
this.selectedPermissions = this.allDevices
|
||||
.filter(device => permittedTargetMacs.has((device.deviceId || '').toLowerCase()))
|
||||
.map(device => device.deviceId);
|
||||
// 保存初始权限状态(用于对比变更)
|
||||
this.originalPermissions = [...this.selectedPermissions];
|
||||
// 更新设备的通讯录别名
|
||||
this.allDevices.forEach(device => {
|
||||
const addrBook = permissions.find(p => p.targetMac === device.deviceId);
|
||||
const addrBook = permissionsByTargetMac.get((device.deviceId || '').toLowerCase());
|
||||
if (addrBook) {
|
||||
device.addressBookAlias = addrBook.alias || '';
|
||||
} else {
|
||||
@@ -385,6 +415,7 @@ export default {
|
||||
});
|
||||
},
|
||||
handleStartEditPermission(device) {
|
||||
if (this.permissionsLoading) return;
|
||||
this.editingDeviceId = device.id;
|
||||
this.editingDeviceName = device.addressBookAlias || device.name;
|
||||
this.$nextTick(() => {
|
||||
@@ -412,13 +443,14 @@ export default {
|
||||
this.editingDeviceId = null;
|
||||
this.editingDeviceName = '';
|
||||
},
|
||||
handlePermissionToggle(deviceId, checked) {
|
||||
handlePermissionToggle(targetMac, checked) {
|
||||
if (this.permissionsLoading) return;
|
||||
if (checked) {
|
||||
if (!this.selectedPermissions.includes(deviceId)) {
|
||||
this.selectedPermissions.push(deviceId);
|
||||
if (!this.selectedPermissions.includes(targetMac)) {
|
||||
this.selectedPermissions.push(targetMac);
|
||||
}
|
||||
} else {
|
||||
const index = this.selectedPermissions.indexOf(deviceId);
|
||||
const index = this.selectedPermissions.indexOf(targetMac);
|
||||
if (index > -1) {
|
||||
this.selectedPermissions.splice(index, 1);
|
||||
}
|
||||
@@ -428,21 +460,22 @@ export default {
|
||||
if (this.isAllSelected) {
|
||||
this.selectedPermissions = [];
|
||||
} else {
|
||||
this.selectedPermissions = this.allDevices.map(d => d.id);
|
||||
this.selectedPermissions = this.allDevices.map(d => d.deviceId);
|
||||
}
|
||||
},
|
||||
handleCancel() {
|
||||
this.selectedPermissions = [];
|
||||
},
|
||||
handleSavePermissions() {
|
||||
if (this.permissionsLoading) return;
|
||||
const promises = this.allDevices
|
||||
.filter(device => {
|
||||
const isNowSelected = this.selectedPermissions.includes(device.id);
|
||||
const wasOriginallySelected = this.originalPermissions.includes(device.id);
|
||||
const isNowSelected = this.selectedPermissions.includes(device.deviceId);
|
||||
const wasOriginallySelected = this.originalPermissions.includes(device.deviceId);
|
||||
return isNowSelected !== wasOriginallySelected;
|
||||
})
|
||||
.map(device => {
|
||||
const hasPermission = this.selectedPermissions.includes(device.id);
|
||||
const hasPermission = this.selectedPermissions.includes(device.deviceId);
|
||||
return new Promise((resolve) => {
|
||||
AddressBookApi.updatePermission({
|
||||
macAddress: this.selectedDevice.deviceId,
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
const addressBookSource = await readFile(
|
||||
new URL('../src/views/AddressBookManagement.vue', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
const correctWordApiSource = await readFile(
|
||||
new URL('../src/apis/module/correctWord.js', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
test('address-book permission state consistently uses the target device MAC', () => {
|
||||
assert.match(
|
||||
addressBookSource,
|
||||
/:value="selectedPermissions\.includes\(device\.deviceId\)"/,
|
||||
);
|
||||
assert.match(
|
||||
addressBookSource,
|
||||
/@change="\(checked\) => handlePermissionToggle\(device\.deviceId, checked\)"/,
|
||||
);
|
||||
assert.match(
|
||||
addressBookSource,
|
||||
/this\.selectedPermissions = this\.allDevices\.map\(d => d\.deviceId\)/,
|
||||
);
|
||||
assert.match(
|
||||
addressBookSource,
|
||||
/this\.originalPermissions\.includes\(device\.deviceId\)/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
addressBookSource,
|
||||
/selectedPermissions\.includes\(device\.id\)/,
|
||||
);
|
||||
assert.doesNotMatch(
|
||||
addressBookSource,
|
||||
/originalPermissions\.includes\(device\.id\)/,
|
||||
);
|
||||
assert.match(
|
||||
addressBookSource,
|
||||
/requestId !== this\.permissionRequestSequence/,
|
||||
);
|
||||
assert.match(
|
||||
addressBookSource,
|
||||
/this\.selectedDevice\?\.deviceId !== macAddress/,
|
||||
);
|
||||
assert.match(
|
||||
addressBookSource,
|
||||
/this\.permissionsLoading = true;\s*this\.selectedPermissions = \[\];\s*this\.originalPermissions = \[\];/,
|
||||
);
|
||||
assert.match(
|
||||
addressBookSource,
|
||||
/handleSavePermissions\(\) \{\s*if \(this\.permissionsLoading\) return;/,
|
||||
);
|
||||
});
|
||||
|
||||
test('correct-word pagination maps the UI page size to the backend limit query', () => {
|
||||
assert.match(
|
||||
correctWordApiSource,
|
||||
/new URLSearchParams\(\{\s*page: params\.page,\s*limit: params\.pageSize\s*\}\)/,
|
||||
);
|
||||
assert.doesNotMatch(correctWordApiSource, /pageSize: params\.pageSize/);
|
||||
});
|
||||
@@ -0,0 +1,17 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
const timbreApiSource = await readFile(
|
||||
new URL('../src/apis/module/timbre.js', import.meta.url),
|
||||
'utf8',
|
||||
);
|
||||
|
||||
test('timbre update sends the current sort value to the backend', () => {
|
||||
const updateStart = timbreApiSource.indexOf('updateVoice(params, callback)');
|
||||
|
||||
assert.notEqual(updateStart, -1);
|
||||
const updateSource = timbreApiSource.slice(updateStart);
|
||||
assert.match(updateSource, /\.method\('PUT'\)/);
|
||||
assert.match(updateSource, /sort:\s*params\.sort/);
|
||||
});
|
||||
Reference in New Issue
Block a user