task-263(admin.html观感对齐): 店铺管理页对齐(密码眼睛实时读取/修改时间列/整句必填校验/编辑密码必填语义/删除文案)
This commit is contained in:
@@ -33,8 +33,35 @@ const form = reactive(emptyShopManageForm())
|
||||
const credentialVisible = ref(false)
|
||||
const credential = ref<ShopCredential | null>(null)
|
||||
|
||||
/** 密码列“眼睛”实时读取:仅展示已读明文,其余保持掩码;读取中占位。 */
|
||||
const revealedPasswordCache = ref<Record<number, string>>({})
|
||||
const revealedRowId = ref<number | null>(null)
|
||||
|
||||
const groupOptions = computed(() => groups.value)
|
||||
|
||||
function shownPassword(row: ShopSummary): string {
|
||||
if (revealedRowId.value !== row.id) return row.passwordMasked || '******'
|
||||
const plain = revealedPasswordCache.value[row.id]
|
||||
return plain != null && plain !== '' ? plain : '读取中...'
|
||||
}
|
||||
|
||||
async function togglePassword(row: ShopSummary) {
|
||||
if (revealedRowId.value === row.id) {
|
||||
revealedRowId.value = null
|
||||
return
|
||||
}
|
||||
revealedRowId.value = row.id
|
||||
if (revealedPasswordCache.value[row.id] == null) {
|
||||
try {
|
||||
const cred = await fetchShopCredential(row.id, row.shopName)
|
||||
revealedPasswordCache.value[row.id] = cred.password
|
||||
} catch (error) {
|
||||
revealedRowId.value = null
|
||||
ElMessage.error(error instanceof Error ? error.message : '读取密码失败')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function loadGroups() {
|
||||
try {
|
||||
groups.value = await fetchShopManageGroups()
|
||||
@@ -94,6 +121,16 @@ function openEdit(row: ShopSummary) {
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
if (
|
||||
form.groupId == null ||
|
||||
!(form.shopName || '').trim() ||
|
||||
!(form.mallName || '').trim() ||
|
||||
!(form.account || '').trim() ||
|
||||
!(form.password || '').trim()
|
||||
) {
|
||||
ElMessage.warning('请完整填写分组、店铺名、店铺商城名、账号、密码')
|
||||
return
|
||||
}
|
||||
saving.value = true
|
||||
try {
|
||||
const createdById = session.user?.id ?? 0
|
||||
@@ -124,7 +161,7 @@ async function showCredential(row: ShopSummary) {
|
||||
|
||||
async function remove(row: ShopSummary) {
|
||||
try {
|
||||
await ElMessageBox.confirm(`确认删除店铺「${row.shopName}」?`, '删除店铺', { type: 'warning' })
|
||||
await ElMessageBox.confirm(`确定删除店铺“${row.shopName}”吗?`, '删除确认', { type: 'warning' })
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
@@ -188,8 +225,19 @@ onMounted(() => {
|
||||
<el-table-column prop="mallName" label="商城" min-width="120" />
|
||||
<el-table-column prop="groupName" label="分组" min-width="120" />
|
||||
<el-table-column prop="account" label="账号" min-width="140" />
|
||||
<el-table-column label="密码" width="140">
|
||||
<template #default="{ row }">{{ (row as ShopSummary).passwordMasked || '—' }}</template>
|
||||
<el-table-column label="密码" width="170">
|
||||
<template #default="{ row }">
|
||||
<span class="pwd-cell">{{ shownPassword(row as ShopSummary) }}</span>
|
||||
<el-button
|
||||
link
|
||||
type="primary"
|
||||
size="small"
|
||||
:aria-label="revealedRowId === (row as ShopSummary).id ? '隐藏密码' : '显示密码'"
|
||||
@click="togglePassword(row as ShopSummary)"
|
||||
>
|
||||
{{ revealedRowId === (row as ShopSummary).id ? '隐藏' : '显示' }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="znUsername" label="自动化账号" min-width="140">
|
||||
<template #default="{ row }">{{ (row as ShopSummary).znUsername || '—' }}</template>
|
||||
@@ -197,6 +245,9 @@ onMounted(() => {
|
||||
<el-table-column prop="createdAt" label="创建时间" min-width="170">
|
||||
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="updatedAt" label="修改时间" min-width="170">
|
||||
<template #default="{ row }">{{ formatDateTime(row.updatedAt) }}</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="220" fixed="right">
|
||||
<template #default="{ row }">
|
||||
<el-button text type="primary" size="small" @click="openEdit(row as ShopSummary)">编辑</el-button>
|
||||
@@ -228,7 +279,7 @@ onMounted(() => {
|
||||
<el-input v-model="form.account" placeholder="登录账号" />
|
||||
</el-form-item>
|
||||
<el-form-item label="密码" required>
|
||||
<el-input v-model="form.password" type="password" show-password :placeholder="editingId == null ? '登录密码' : '留空则不修改?(后端必填,编辑需重填)'" />
|
||||
<el-input v-model="form.password" type="password" show-password :placeholder="editingId == null ? '登录密码' : '后端必填,编辑请重新填写登录密码'" />
|
||||
</el-form-item>
|
||||
<el-form-item label="自动化账号">
|
||||
<el-input v-model="form.znUsername" placeholder="可选,紫鸟自动化账号" />
|
||||
@@ -267,4 +318,5 @@ onMounted(() => {
|
||||
.table-footer span { color: var(--el-text-color-secondary); font-size: 12.5px; }
|
||||
.table-footer b { color: var(--el-text-color-primary); }
|
||||
.dim { color: var(--el-text-color-secondary); font-size: 12px; }
|
||||
.pwd-cell { font-family: Consolas, monospace; font-size: 12px; margin-right: 6px; }
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import test from 'node:test'
|
||||
import assert from 'node:assert/strict'
|
||||
import { readSource } from './helpers.ts'
|
||||
|
||||
// module 13 task 263:店铺管理页对齐 admin panel-shop-manage(分组/店铺名筛选、密码掩码+眼睛
|
||||
// 实时读取、修改时间列、必填校验整句、编辑密码必填语义、删除确认、凭证明文弹窗复制)。
|
||||
|
||||
test('test_task_263_shop_manage_normal_primary_path', () => {
|
||||
const page = readSource('src/pages/shop/ShopManagePage.vue')
|
||||
assert.match(page, /分组/, '需有分组列/分组筛选')
|
||||
assert.match(page, /店铺名称/, '需有店铺名称筛选')
|
||||
assert.match(page, /shopName/, '店铺名筛选绑定 shopName')
|
||||
})
|
||||
|
||||
test('test_task_263_shop_manage_normal_variant_input', () => {
|
||||
// 密码列:掩码默认,提供眼睛实时读取与隐藏。
|
||||
const page = readSource('src/pages/shop/ShopManagePage.vue')
|
||||
assert.match(page, /togglePassword/, '需有眼睛切换密码读取')
|
||||
assert.match(page, /shownPassword/, '密码展示经 shownPassword')
|
||||
assert.match(page, /读取中\.\.\./, '读取中有占位')
|
||||
assert.match(page, /\*\*\*\*\*\*/, '掩码默认 ******')
|
||||
})
|
||||
|
||||
test('test_task_263_shop_manage_normal_repeated_operation_is_idempotent', () => {
|
||||
const page = readSource('src/pages/shop/ShopManagePage.vue')
|
||||
assert.ok((page.match(/formatDateTime/g) || []).length >= 1, '时间统一格式化')
|
||||
})
|
||||
|
||||
test('test_task_263_shop_manage_boundary_empty_input', () => {
|
||||
// 必填整句校验(含编辑需重填密码的语义)。
|
||||
const page = readSource('src/pages/shop/ShopManagePage.vue')
|
||||
assert.match(page, /请完整填写分组、店铺名、店铺商城名、账号、密码/, '必填校验需整句提示')
|
||||
assert.match(page, /后端必填,编辑请重新填写登录密码/, '编辑密码占位不再自相矛盾')
|
||||
})
|
||||
|
||||
test('test_task_263_shop_manage_boundary_single_item', () => {
|
||||
const page = readSource('src/pages/shop/ShopManagePage.vue')
|
||||
assert.match(page, /updatedAt/, '需含修改时间列')
|
||||
assert.match(page, /创建时间/, '保留创建时间列')
|
||||
})
|
||||
|
||||
test('test_task_263_shop_manage_boundary_limit_or_missing_field', () => {
|
||||
// 分组下拉来源 fetchShopManageGroups。
|
||||
const page = readSource('src/pages/shop/ShopManagePage.vue')
|
||||
assert.match(page, /fetchShopManageGroups/, '分组下拉走分组接口')
|
||||
assert.match(page, /credential/, '凭证明文/复制能力在位')
|
||||
})
|
||||
|
||||
test('test_task_263_shop_manage_invalid_input_rejected', () => {
|
||||
const page = readSource('src/pages/shop/ShopManagePage.vue')
|
||||
assert.match(page, /确定删除店铺“/, '删除确认需含“确定删除店铺”')
|
||||
})
|
||||
|
||||
test('test_task_263_shop_manage_dependency_failure_returns_actionable_message', () => {
|
||||
// 密码列含明文时不脱离 fetch credential 适配层(不内联请求)。
|
||||
const page = readSource('src/pages/shop/ShopManagePage.vue')
|
||||
assert.match(page, /fetchShopCredential/, '明文经 fetchShopCredential 获取')
|
||||
assert.equal(/http\.(get|post|put|delete)/.test(page), false, '页面不内联 HTTP 请求')
|
||||
})
|
||||
Reference in New Issue
Block a user