feat(tickets): allow custom status key in status create/edit
- Validate status_key (^[a-z][a-z0-9_]{1,23}$, unique) on create/update
- Renaming a key moves aksrt_ticket.status in the same transaction
- Default statuses remain immutable
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8d871c0828
commit
135fb4eaa4
4 files changed
+134
-7
No files matched your search
@@ -39,6 +39,22 @@
|
||||
<n-form-item label="状态名称" path="name">
|
||||
<n-input v-model:value="form.name" placeholder="如:处理中" maxlength="60" show-count />
|
||||
</n-form-item>
|
||||
<n-form-item label="标识" path="status_key">
|
||||
<div class="aksrt-status-key">
|
||||
<n-input
|
||||
v-model:value="form.status_key"
|
||||
class="aksrt-status-key__input"
|
||||
:placeholder="editing ? '' : '留空则按名称自动生成'"
|
||||
maxlength="24"
|
||||
show-count
|
||||
:input-props="{ autocomplete: 'off', spellcheck: false }"
|
||||
@blur="form.status_key = normalizeKey(form.status_key)"
|
||||
/>
|
||||
<span class="aksrt-ticket-status__hint">
|
||||
小写字母开头,仅含小写字母、数字与下划线(2–24 位)。修改后,使用该状态的工单会同步改为新标识。
|
||||
</span>
|
||||
</div>
|
||||
</n-form-item>
|
||||
<n-form-item label="状态颜色" path="color">
|
||||
<div class="aksrt-status-color">
|
||||
<div class="aksrt-status-color__swatches" role="radiogroup" aria-label="状态颜色">
|
||||
@@ -132,7 +148,14 @@ const rows = ref([])
|
||||
const DEFAULT_COLOR = '#6b7280'
|
||||
const HEX_RE = /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i
|
||||
|
||||
const form = ref({ name: '', color: DEFAULT_COLOR, is_closed: false })
|
||||
/** 与 TicketStatusService::KEY_PATTERN 保持一致。 */
|
||||
const KEY_RE = /^[a-z][a-z0-9_]{1,23}$/
|
||||
|
||||
const form = ref({ name: '', status_key: '', color: DEFAULT_COLOR, is_closed: false })
|
||||
|
||||
function normalizeKey(value) {
|
||||
return String(value || '').trim().toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* 归一化为 TicketStatusService::sanitize_color() 接受的小写 #rgb / #rrggbb;
|
||||
@@ -149,6 +172,26 @@ const rules = {
|
||||
{ required: true, message: '请填写状态名称', trigger: 'blur' },
|
||||
{ max: 60, message: '状态名称不能超过 60 字', trigger: 'blur' },
|
||||
],
|
||||
status_key: [
|
||||
{
|
||||
// 新增时可留空(后端按名称生成);编辑时必须填写。
|
||||
validator: (_rule, value) => {
|
||||
const key = normalizeKey(value)
|
||||
if (key === '') return !editing.value
|
||||
return KEY_RE.test(key)
|
||||
},
|
||||
message: '标识需以小写字母开头,仅含小写字母、数字与下划线,长度 2–24 位',
|
||||
trigger: ['blur', 'input'],
|
||||
},
|
||||
{
|
||||
validator: (_rule, value) => {
|
||||
const key = normalizeKey(value)
|
||||
return key === '' || !rows.value.some((row) => row.status_key === key && row.id !== editing.value?.id)
|
||||
},
|
||||
message: '该标识已被其他状态使用',
|
||||
trigger: ['blur', 'input'],
|
||||
},
|
||||
],
|
||||
color: [
|
||||
{
|
||||
validator: (_rule, value) => toHex(value) !== '',
|
||||
@@ -245,7 +288,7 @@ async function load() {
|
||||
|
||||
function openCreate() {
|
||||
editing.value = null
|
||||
form.value = { name: '', color: DEFAULT_COLOR, is_closed: false }
|
||||
form.value = { name: '', status_key: '', color: DEFAULT_COLOR, is_closed: false }
|
||||
visible.value = true
|
||||
}
|
||||
|
||||
@@ -253,6 +296,7 @@ function openEdit(row) {
|
||||
editing.value = row
|
||||
form.value = {
|
||||
name: String(row.name || ''),
|
||||
status_key: String(row.status_key || ''),
|
||||
color: toHex(row.color) || DEFAULT_COLOR,
|
||||
is_closed: !!row.is_closed,
|
||||
}
|
||||
@@ -269,6 +313,7 @@ async function handleSubmit() {
|
||||
try {
|
||||
const payload = {
|
||||
name: form.value.name.trim(),
|
||||
status_key: normalizeKey(form.value.status_key),
|
||||
color: currentHex.value,
|
||||
is_closed: form.value.is_closed ? 1 : 0,
|
||||
}
|
||||
@@ -466,6 +511,18 @@ defineExpose({ reload: load })
|
||||
box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
/* —— 弹窗:标识 —— */
|
||||
.aksrt-status-key {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.aksrt-status-key__input {
|
||||
font-family: var(--n-font-family-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
|
||||
}
|
||||
|
||||
/* —— 弹窗:完结开关 —— */
|
||||
.aksrt-status-closed {
|
||||
display: flex;
|
||||
|
||||
Reference in new issue
Block a user