This commit is contained in:
@@ -38,6 +38,11 @@ const components = Object.fromEntries(
|
||||
})
|
||||
)
|
||||
|
||||
test('carrier form grid top-aligns items so helper text cannot offset adjacent controls', () => {
|
||||
const style = components.index.descriptor.styles.map((block) => block.content).join('\n')
|
||||
assert.match(style, /\.carrier-form-grid\s*\{[^}]*align-items:\s*start\s*;/)
|
||||
})
|
||||
|
||||
const carrier = (enabled) => ({
|
||||
id: 7,
|
||||
carrier_code: 'carrier-test',
|
||||
@@ -51,6 +56,7 @@ const carrier = (enabled) => ({
|
||||
|
||||
function harness(userType, responseCarrier = carrier(1)) {
|
||||
const writes = []
|
||||
const confirmations = []
|
||||
const user = vue.reactive({ info: { user_type: userType }, permissions: [], buttons: [] })
|
||||
const permission = evaluate(read('src/composables/usePermission.ts'), {
|
||||
vue,
|
||||
@@ -70,7 +76,12 @@ function harness(userType, responseCarrier = carrier(1)) {
|
||||
'@/api/modules': { CarrierService: service },
|
||||
'element-plus': {
|
||||
ElMessage: { success() {}, warning() {} },
|
||||
ElMessageBox: {},
|
||||
ElMessageBox: {
|
||||
confirm: async (...args) => {
|
||||
confirmations.push(args)
|
||||
return 'confirm'
|
||||
}
|
||||
},
|
||||
ElTag: {},
|
||||
ElSwitch: {},
|
||||
ElButton: {}
|
||||
@@ -91,7 +102,14 @@ function harness(userType, responseCarrier = carrier(1)) {
|
||||
}
|
||||
const setup = (name) =>
|
||||
evaluate(components[name].script, imports, runtimeVue).default.setup({}, { expose() {} })
|
||||
return { list: setup('index'), detail: setup('detail'), writes }
|
||||
return {
|
||||
list: setup('index'),
|
||||
detail: setup('detail'),
|
||||
writes,
|
||||
confirmations,
|
||||
messageBox: imports['element-plus'].ElMessageBox,
|
||||
service
|
||||
}
|
||||
}
|
||||
|
||||
const validForm = {
|
||||
@@ -170,6 +188,113 @@ test('editing then creating resets control state; default creation sends numeric
|
||||
assert.equal(list.form.realname_control_enabled, 0)
|
||||
})
|
||||
|
||||
for (const userType of [1, 2]) {
|
||||
test(`user_type=${userType}: enabling control requires confirmation before PUT and sends true`, async () => {
|
||||
const { list, writes, confirmations, messageBox } = harness(userType)
|
||||
list.showDialog('edit', carrier(0))
|
||||
list.form.realname_control_enabled = 1
|
||||
let confirm
|
||||
messageBox.confirm = (...args) => {
|
||||
confirmations.push(args)
|
||||
return new Promise((resolve) => {
|
||||
confirm = resolve
|
||||
})
|
||||
}
|
||||
const pending = list.handleSubmit(validForm)
|
||||
assert.equal(confirmations.length, 1)
|
||||
assert.match(confirmations[0][0], /未实名的开机卡会在下一次评估时被停机/)
|
||||
assert.deepEqual(writes, [])
|
||||
assert.equal(list.submitLoading.value, true)
|
||||
await list.handleSubmit(validForm)
|
||||
assert.equal(confirmations.length, 1)
|
||||
confirm('confirm')
|
||||
await pending
|
||||
assert.equal(writes.length, 1)
|
||||
assert.equal(writes[0].method, 'update')
|
||||
assert.equal(writes[0].data.realname_control_enabled, 1)
|
||||
assert.equal(writes[0].data.realname_control_confirm, true)
|
||||
assert.equal(list.submitLoading.value, false)
|
||||
})
|
||||
}
|
||||
|
||||
for (const reason of ['cancel', 'close']) {
|
||||
test(`${reason} of realname confirmation does not submit; retry requires a fresh confirmation`, async () => {
|
||||
const { list, writes, messageBox } = harness(1)
|
||||
list.showDialog('edit', carrier(0))
|
||||
list.form.realname_control_enabled = 1
|
||||
let prompts = 0
|
||||
messageBox.confirm = async () => {
|
||||
prompts += 1
|
||||
throw reason
|
||||
}
|
||||
await list.handleSubmit(validForm)
|
||||
assert.deepEqual(writes, [])
|
||||
assert.equal(list.dialogVisible.value, true)
|
||||
assert.equal(list.submitLoading.value, false)
|
||||
assert.equal(list.form.realname_control_enabled, 1)
|
||||
messageBox.confirm = async () => {
|
||||
prompts += 1
|
||||
return 'confirm'
|
||||
}
|
||||
await list.handleSubmit(validForm)
|
||||
assert.equal(prompts, 2)
|
||||
assert.equal(writes[0].data.realname_control_confirm, true)
|
||||
})
|
||||
}
|
||||
|
||||
test('unchanged, disabled, new, and unauthorized carriers never send confirmation automatically', async () => {
|
||||
for (const [userType, mode, original, selected] of [
|
||||
[1, 'edit', 1, 1],
|
||||
[1, 'edit', 0, 0],
|
||||
[2, 'edit', 1, 0],
|
||||
[1, 'add', 0, 1],
|
||||
[3, 'edit', 0, 1],
|
||||
[4, 'edit', 0, 1]
|
||||
]) {
|
||||
const { list, writes, confirmations } = harness(userType)
|
||||
list.showDialog(mode, carrier(original))
|
||||
list.form.realname_control_enabled = selected
|
||||
await list.handleSubmit(validForm)
|
||||
assert.equal(confirmations.length, 0)
|
||||
assert.equal(writes.length, 1)
|
||||
assert.equal(Object.hasOwn(writes[0].data, 'realname_control_confirm'), false)
|
||||
}
|
||||
})
|
||||
|
||||
test('opening another carrier resets the original control state after a cancelled enabling', async () => {
|
||||
const { list, writes, messageBox } = harness(2)
|
||||
list.showDialog('edit', carrier(0))
|
||||
list.form.realname_control_enabled = 1
|
||||
messageBox.confirm = async () => {
|
||||
throw 'cancel'
|
||||
}
|
||||
await list.handleSubmit(validForm)
|
||||
list.showDialog('edit', { ...carrier(1), id: 8 })
|
||||
await list.handleSubmit(validForm)
|
||||
assert.equal(writes[0].id, 8)
|
||||
assert.equal(Object.hasOwn(writes[0].data, 'realname_control_confirm'), false)
|
||||
})
|
||||
|
||||
test('a failed PUT requires a fresh confirmation on retry', async (t) => {
|
||||
const { list, writes, confirmations, service } = harness(2)
|
||||
t.mock.method(console, 'error', () => {})
|
||||
list.showDialog('edit', carrier(0))
|
||||
list.form.realname_control_enabled = 1
|
||||
const updateCarrier = service.updateCarrier
|
||||
service.updateCarrier = async () => {
|
||||
throw new Error('PUT failed')
|
||||
}
|
||||
await list.handleSubmit(validForm)
|
||||
assert.equal(confirmations.length, 1)
|
||||
assert.deepEqual(writes, [])
|
||||
assert.equal(list.dialogVisible.value, true)
|
||||
assert.equal(list.submitLoading.value, false)
|
||||
service.updateCarrier = updateCarrier
|
||||
await list.handleSubmit(validForm)
|
||||
assert.equal(confirmations.length, 2)
|
||||
assert.equal(writes[0].data.realname_control_confirm, true)
|
||||
})
|
||||
|
||||
function findElement(node, predicate) {
|
||||
if (node.type === 1 && predicate(node)) return node
|
||||
for (const child of node.children || []) {
|
||||
@@ -235,11 +360,18 @@ test('carrier endpoints preserve the field in list/detail responses and create/u
|
||||
assert.equal((await CarrierService.getCarriers()).data.items[0].realname_control_enabled, 1)
|
||||
assert.equal((await CarrierService.getCarrierDetail(7)).data.realname_control_enabled, 0)
|
||||
await CarrierService.createCarrier({ ...carrier(1) })
|
||||
await CarrierService.updateCarrier(7, { realname_control_enabled: 0 })
|
||||
await CarrierService.updateCarrier(7, {
|
||||
realname_control_enabled: 1,
|
||||
realname_control_confirm: true
|
||||
})
|
||||
assert.deepEqual(calls, [
|
||||
{ method: 'GET', url: '/api/admin/carriers' },
|
||||
{ method: 'GET', url: '/api/admin/carriers/7' },
|
||||
{ method: 'POST', url: '/api/admin/carriers', data: carrier(1) },
|
||||
{ method: 'PUT', url: '/api/admin/carriers/7', data: { realname_control_enabled: 0 } }
|
||||
{
|
||||
method: 'PUT',
|
||||
url: '/api/admin/carriers/7',
|
||||
data: { realname_control_enabled: 1, realname_control_confirm: true }
|
||||
}
|
||||
])
|
||||
})
|
||||
|
||||
@@ -63,6 +63,7 @@ export interface UpdateCarrierParams {
|
||||
description?: string | null
|
||||
data_reset_day?: number | null // 上游流量重置日(1-28)
|
||||
realname_control_enabled?: CarrierRealnameControlEnabled // 通道实名管控 (0:不管控, 1:管控)
|
||||
realname_control_confirm?: boolean // 从不管控改为管控时,确认未实名开机卡将在下次评估时被停机
|
||||
realname_link_type?: string | null // 实名链接类型
|
||||
realname_link_template?: string | null // 实名链接模板
|
||||
traffic_threshold_enabled?: CarrierTrafficThresholdEnabled | null // 通道流量阈值开关
|
||||
|
||||
@@ -206,7 +206,13 @@
|
||||
import { CarrierService } from '@/api/modules'
|
||||
import { ElMessage, ElMessageBox, ElTag, ElSwitch, ElButton } from 'element-plus'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import type { Carrier, CarrierType, CreateCarrierParams } from '@/types/api'
|
||||
import type {
|
||||
Carrier,
|
||||
CarrierType,
|
||||
CarrierRealnameControlEnabled,
|
||||
CreateCarrierParams,
|
||||
UpdateCarrierParams
|
||||
} from '@/types/api'
|
||||
import type { SearchFormItem } from '@/types'
|
||||
import { useCheckedColumns } from '@/composables/useCheckedColumns'
|
||||
import { useAuth } from '@/composables/useAuth'
|
||||
@@ -592,6 +598,7 @@
|
||||
}
|
||||
|
||||
const dialogType = ref('add')
|
||||
const originalRealnameControlEnabled = ref<CarrierRealnameControlEnabled>(0)
|
||||
|
||||
// 显示新增/编辑对话框
|
||||
const showDialog = (type: string, row?: any) => {
|
||||
@@ -606,6 +613,7 @@
|
||||
form.description = row.description
|
||||
form.data_reset_day = row.data_reset_day || 1
|
||||
form.realname_control_enabled = row.realname_control_enabled ?? 0
|
||||
originalRealnameControlEnabled.value = form.realname_control_enabled
|
||||
form.traffic_threshold_mode = getThresholdMode(row.traffic_threshold_enabled)
|
||||
form.traffic_threshold_unit = row.traffic_threshold_unit ?? null
|
||||
form.traffic_threshold_value = row.traffic_threshold_value ?? null
|
||||
@@ -619,6 +627,7 @@
|
||||
form.description = ''
|
||||
form.data_reset_day = 1
|
||||
form.realname_control_enabled = 0
|
||||
originalRealnameControlEnabled.value = 0
|
||||
form.traffic_threshold_mode = 'unconfigured'
|
||||
form.traffic_threshold_unit = null
|
||||
form.traffic_threshold_value = null
|
||||
@@ -713,7 +722,7 @@
|
||||
|
||||
// 提交表单
|
||||
const handleSubmit = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
if (!formEl || submitLoading.value) return
|
||||
|
||||
if (
|
||||
canConfigureTrafficThreshold.value &&
|
||||
@@ -727,7 +736,7 @@
|
||||
}
|
||||
|
||||
await formEl.validate(async (valid) => {
|
||||
if (valid) {
|
||||
if (valid && !submitLoading.value) {
|
||||
submitLoading.value = true
|
||||
try {
|
||||
const data: CreateCarrierParams = {
|
||||
@@ -747,7 +756,7 @@
|
||||
await CarrierService.createCarrier(data)
|
||||
ElMessage.success('新增成功')
|
||||
} else {
|
||||
const updateData = {
|
||||
const updateData: UpdateCarrierParams = {
|
||||
carrier_name: form.carrier_name,
|
||||
description: form.description || undefined,
|
||||
data_reset_day: form.data_reset_day || null,
|
||||
@@ -757,6 +766,27 @@
|
||||
...getTrafficThresholdPayload(),
|
||||
...getRealnameControlPayload()
|
||||
}
|
||||
if (
|
||||
canConfigureRealnameControl.value &&
|
||||
originalRealnameControlEnabled.value === 0 &&
|
||||
updateData.realname_control_enabled === 1
|
||||
) {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
'开启通道实名管控后,该通道未实名的开机卡会在下一次评估时被停机。确认开启吗?',
|
||||
'确认开启通道实名管控',
|
||||
{
|
||||
type: 'warning',
|
||||
confirmButtonText: '确认开启',
|
||||
cancelButtonText: '取消',
|
||||
closeOnClickModal: false
|
||||
}
|
||||
)
|
||||
} catch {
|
||||
return
|
||||
}
|
||||
updateData.realname_control_confirm = true
|
||||
}
|
||||
await CarrierService.updateCarrier(form.id, updateData)
|
||||
ElMessage.success('修改成功')
|
||||
}
|
||||
@@ -798,6 +828,7 @@
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
column-gap: 24px;
|
||||
align-items: start;
|
||||
|
||||
:deep(.el-form-item) {
|
||||
margin-right: 0;
|
||||
@@ -808,7 +839,7 @@
|
||||
grid-column: 1 / -1;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
@media (width <= 640px) {
|
||||
.carrier-form-grid {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user