task-263(admin.html观感对齐): 店铺管理页对齐(密码眼睛实时读取/修改时间列/整句必填校验/编辑密码必填语义/删除文案)
This commit is contained in:
@@ -33,8 +33,35 @@ const form = reactive(emptyShopManageForm())
|
|||||||
const credentialVisible = ref(false)
|
const credentialVisible = ref(false)
|
||||||
const credential = ref<ShopCredential | null>(null)
|
const credential = ref<ShopCredential | null>(null)
|
||||||
|
|
||||||
|
/** 密码列“眼睛”实时读取:仅展示已读明文,其余保持掩码;读取中占位。 */
|
||||||
|
const revealedPasswordCache = ref<Record<number, string>>({})
|
||||||
|
const revealedRowId = ref<number | null>(null)
|
||||||
|
|
||||||
const groupOptions = computed(() => groups.value)
|
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() {
|
async function loadGroups() {
|
||||||
try {
|
try {
|
||||||
groups.value = await fetchShopManageGroups()
|
groups.value = await fetchShopManageGroups()
|
||||||
@@ -94,6 +121,16 @@ function openEdit(row: ShopSummary) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function submit() {
|
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
|
saving.value = true
|
||||||
try {
|
try {
|
||||||
const createdById = session.user?.id ?? 0
|
const createdById = session.user?.id ?? 0
|
||||||
@@ -124,7 +161,7 @@ async function showCredential(row: ShopSummary) {
|
|||||||
|
|
||||||
async function remove(row: ShopSummary) {
|
async function remove(row: ShopSummary) {
|
||||||
try {
|
try {
|
||||||
await ElMessageBox.confirm(`确认删除店铺「${row.shopName}」?`, '删除店铺', { type: 'warning' })
|
await ElMessageBox.confirm(`确定删除店铺“${row.shopName}”吗?`, '删除确认', { type: 'warning' })
|
||||||
} catch {
|
} catch {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -188,8 +225,19 @@ onMounted(() => {
|
|||||||
<el-table-column prop="mallName" label="商城" min-width="120" />
|
<el-table-column prop="mallName" label="商城" min-width="120" />
|
||||||
<el-table-column prop="groupName" 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 prop="account" label="账号" min-width="140" />
|
||||||
<el-table-column label="密码" width="140">
|
<el-table-column label="密码" width="170">
|
||||||
<template #default="{ row }">{{ (row as ShopSummary).passwordMasked || '—' }}</template>
|
<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>
|
||||||
<el-table-column prop="znUsername" label="自动化账号" min-width="140">
|
<el-table-column prop="znUsername" label="自动化账号" min-width="140">
|
||||||
<template #default="{ row }">{{ (row as ShopSummary).znUsername || '—' }}</template>
|
<template #default="{ row }">{{ (row as ShopSummary).znUsername || '—' }}</template>
|
||||||
@@ -197,6 +245,9 @@ onMounted(() => {
|
|||||||
<el-table-column prop="createdAt" label="创建时间" min-width="170">
|
<el-table-column prop="createdAt" label="创建时间" min-width="170">
|
||||||
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
|
<template #default="{ row }">{{ formatDateTime(row.createdAt) }}</template>
|
||||||
</el-table-column>
|
</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">
|
<el-table-column label="操作" width="220" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button text type="primary" size="small" @click="openEdit(row as ShopSummary)">编辑</el-button>
|
<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-input v-model="form.account" placeholder="登录账号" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="密码" required>
|
<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>
|
||||||
<el-form-item label="自动化账号">
|
<el-form-item label="自动化账号">
|
||||||
<el-input v-model="form.znUsername" placeholder="可选,紫鸟自动化账号" />
|
<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 span { color: var(--el-text-color-secondary); font-size: 12.5px; }
|
||||||
.table-footer b { color: var(--el-text-color-primary); }
|
.table-footer b { color: var(--el-text-color-primary); }
|
||||||
.dim { color: var(--el-text-color-secondary); font-size: 12px; }
|
.dim { color: var(--el-text-color-secondary); font-size: 12px; }
|
||||||
|
.pwd-cell { font-family: Consolas, monospace; font-size: 12px; margin-right: 6px; }
|
||||||
</style>
|
</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