Files
wordpress-theme-aksrt/assets/admin/src/pages/users/partials/ticket/TicketPrereplyPanel.vue
T
kate522andClaude Opus 5 35bb96e82f refactor: rename the theme brand and every identifier from AKSRT to Jio
Full-project rename. `git grep -i aksrt` is now empty apart from the
DBMigrator 3.3.0 changelog note that documents the rename itself.

Code identifiers
- PHP namespace AKSRT\ -> Jio\ (composer PSR-4 + 378 declarations)
- global constants AKSRT_* -> JIO_* (VERSION/DIR/URI, PAY_TYPE_*, CAPTCHA_*)
- procedural functions, hooks, options, meta keys, table names,
  cookies, query vars, ajax actions and nonces: aksrt_ -> jio_
- text domain aksrt -> jio; Theme Name -> Jio
- JS globals: aksrtData/aksrtAdminData/... -> jioData/jioAdminData/...,
  window.AKSRTCaptcha & friends -> window.JioCaptcha, Aksrt* -> Jio*
- Vue components Aks*.vue -> Jio*.vue (15 files renamed + imports)
- CSS classes/data-attrs/custom properties: aksrt- -> jio-
- REST namespace aksrt/v1 -> jio/v1 (incl. payment gateway callbacks)

Storage layer
- DB tables {prefix}aksrt_* -> {prefix}jio_*; SCHEMA_VERSION 3.2.0 -> 3.3.0
  so dbDelta creates the new names on install
- widget id_base aksrt_widget_* -> jio_widget_*
- uninstall.php drop list rebuilt from the actual migrator tables
  (dropped 3 stale entries, added the 17 missing ones)

Removed
- hardcoded https://api.aksrt.com/version.json default update endpoint;
  get_endpoint() now reads the 「更新端点」 setting and do_check() no-ops
  when it is empty

Files renamed: inc/helpers/aksrt-*.php -> jio-*.php (54),
assets/admin/src/components/form/Aks*.vue -> Jio*.vue (15).
Build artifacts regenerated: assets/css/build, assets/js/runtime,
assets/admin/build.

Verified: audit-psr4-paths errors=0, audit-admin-contracts errors=0
(warnings=56, unchanged baseline), 813/813 widget-system tests,
44/44 phpunit, php -l clean across all PHP.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-11-01 20:05:04 +08:00

204 lines
5.7 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<n-card class="jio-section-card">
<div class="jio-section-header">
<h3>预设回复</h3>
<n-space>
<n-button size="small" :disabled="saving || !reorderable" :loading="savingOrder" @click="saveOrder">
保存排序
</n-button>
<n-button size="small" type="primary" :disabled="saving" @click="openCreate">新增预设回复</n-button>
</n-space>
</div>
<n-alert type="info" :show-icon="false" style="margin-bottom: 12px;">
预设回复是管理员回复框的一键插入话术,按下列顺序展示;拖动行调整顺序后需点「保存排序」。
</n-alert>
<n-empty v-if="!rows.length && !loading" description="暂无预设回复" />
<draggable
v-else
v-model="rows"
v-bind="SORTABLE_LIST_ATTRS"
item-key="id"
class="jio-sortable"
>
<template #item="{ element, index }">
<div class="jio-sortable__item">
<div class="jio-sortable__head">
<JioSortHandle />
<span class="jio-sortable__index">{{ index + 1 }}</span>
<div class="jio-sortable__main">
<span class="jio-ticket-prereply__content">{{ element.content }}</span>
</div>
<div class="jio-sortable__actions">
<n-button size="tiny" type="primary" quaternary @click="openEdit(element)">编辑</n-button>
<n-button size="tiny" type="error" quaternary @click="handleDelete(element)">删除</n-button>
</div>
</div>
</div>
</template>
</draggable>
<n-modal
v-model:show="visible"
:title="editing ? '编辑预设回复' : '新增预设回复'"
preset="card"
style="width: 520px; max-width: 92vw;"
:mask-closable="false"
>
<n-form ref="formRef" :model="form" :rules="rules" label-placement="top">
<n-form-item label="回复内容" path="content">
<n-input
v-model:value="form.content"
type="textarea"
:rows="5"
placeholder="填写可一键插入的回复话术"
maxlength="2000"
show-count
/>
</n-form-item>
</n-form>
<template #footer>
<n-space justify="end">
<n-button :disabled="saving" @click="visible = false">取消</n-button>
<n-button type="primary" :loading="saving" @click="handleSubmit">保存</n-button>
</n-space>
</template>
</n-modal>
</n-card>
</template>
<script setup>
import { computed, onMounted, ref } from 'vue'
import { NButton, NSpace, useDialog, useMessage } from 'naive-ui'
import draggable from 'vuedraggable'
import api from '@/api'
import JioSortHandle from '@/components/form/JioSortHandle.vue'
import { SORTABLE_LIST_ATTRS } from '@/components/form/sortable-options.js'
const message = useMessage()
const dialog = useDialog()
const loading = ref(false)
const saving = ref(false)
const savingOrder = ref(false)
const visible = ref(false)
const formRef = ref(null)
const editing = ref(null)
const rows = ref([])
/** 初始顺序,用于判断排序是否需要保存。 */
const baseline = ref([])
const form = ref({ content: '' })
const rules = {
content: [
{ required: true, message: '请填写预设回复内容', trigger: 'blur' },
{ max: 2000, message: '内容不能超过 2000 字', trigger: 'blur' },
],
}
const reorderable = computed(
() => rows.value.length > 1 && baseline.value.join(',') !== rows.value.map((r) => r.id).join(',')
)
async function load() {
loading.value = true
try {
const res = await api.listTicketPrereplies()
rows.value = res?.data || []
baseline.value = rows.value.map((row) => row.id)
} catch (error) {
message.error(error?.message || '加载预设回复失败')
} finally {
loading.value = false
}
}
function openCreate() {
editing.value = null
form.value = { content: '' }
visible.value = true
}
function openEdit(row) {
editing.value = row
form.value = { content: String(row.content || '') }
visible.value = true
}
async function handleSubmit() {
try {
await formRef.value?.validate()
} catch {
return
}
saving.value = true
try {
const content = form.value.content.trim()
if (editing.value) {
await api.updateTicketPrereply(editing.value.id, { content })
message.success('预设回复已保存')
} else {
await api.createTicketPrereply({ content })
message.success('预设回复已创建')
}
visible.value = false
await load()
} catch (error) {
message.error(error?.message || '保存预设回复失败')
} finally {
saving.value = false
}
}
function handleDelete(row) {
dialog.warning({
title: '删除预设回复',
content: '确认删除该预设回复吗?',
positiveText: '删除',
negativeText: '取消',
onPositiveClick: async () => {
try {
await api.deleteTicketPrereply(row.id)
message.success('预设回复已删除')
await load()
} catch (error) {
message.error(error?.message || '删除预设回复失败')
}
},
})
}
/** 排序接口要求全量 ID,数量不符后端会拒绝。 */
async function saveOrder() {
const ids = rows.value.map((row) => row.id)
savingOrder.value = true
try {
await api.reorderTicketPrereplies(ids)
baseline.value = [...ids]
message.success('排序已保存')
} catch (error) {
message.error(error?.message || '保存排序失败')
await load()
} finally {
savingOrder.value = false
}
}
onMounted(load)
defineExpose({ reload: load })
</script>
<style scoped>
.jio-ticket-prereply__content {
color: var(--jio-color-text, #1F2923);
font-size: 13px;
line-height: 1.6;
white-space: pre-wrap;
word-break: break-word;
}
</style>