SystemPromptManagement.vue 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. <template>
  2. <div class="system-prompt-page">
  3. <el-card>
  4. <div slot="header" class="clearfix">
  5. <span>系统提示词管理</span>
  6. <div style="float:right; display:flex; gap:10px;">
  7. <el-button size="mini" type="info" @click="fetchList">刷新</el-button>
  8. <el-button size="mini" type="primary" @click="showEdit(null)">新增提示词</el-button>
  9. </div>
  10. </div>
  11. <el-form :model="filters" inline size="small" style="margin-bottom:16px;">
  12. <el-form-item label="关键词">
  13. <el-input v-model="filters.keyword" placeholder="搜索标题或键名" clearable @input="fetchList" />
  14. </el-form-item>
  15. </el-form>
  16. <div class="adm-table-wrap">
  17. <el-table :data="list" border stripe v-loading="loading" empty-text="暂无数据" style="width:100%" height="100%">
  18. <el-table-column prop="promptKey" label="提示词键" width="160" />
  19. <el-table-column prop="title" label="标题" width="140" />
  20. <el-table-column prop="content" label="内容" min-width="300" show-overflow-tooltip />
  21. <el-table-column prop="version" label="版本" width="60" />
  22. <el-table-column prop="status" label="状态" width="70">
  23. <template slot-scope="{ row }">
  24. <el-tag :type="row.status === 1 ? "success" : "info"" size="small">
  25. {{ row.status === 1 ? "启用" : "禁用" }}
  26. </el-tag>
  27. </template>
  28. </el-table-column>
  29. <el-table-column prop="updatedAt" label="更新时间" width="160">
  30. <template slot-scope="{ row }">
  31. {{ row.updatedAt ? formatTime(row.updatedAt) : "-" }}
  32. </template>
  33. </el-table-column>
  34. <el-table-column label="操作" width="240" fixed="right">
  35. <template slot-scope="{ row }">
  36. <el-button type="text" size="small" @click="showEdit(row)">编辑</el-button>
  37. <el-button type="text" size="small" @click="showPreview(row)">预览</el-button>
  38. <el-button type="text" size="small" @click="showVersions(row)">版本</el-button>
  39. <el-button type="text" size="small" @click="resetToDefault(row)">重置</el-button>
  40. <el-button type="text" size="small" style="color:#f56c6c" @click="deleteRow(row)">删除</el-button>
  41. </template>
  42. </el-table-column>
  43. </el-table>
  44. </div>
  45. <el-pagination
  46. style="margin-top: 16px; text-align: right"
  47. :current-page="page"
  48. :page-size="size"
  49. :total="total"
  50. layout="total, prev, pager, next"
  51. @current-change="handlePageChange"
  52. />
  53. </el-card>
  54. <!-- 编辑弹窗 -->
  55. <el-dialog :visible.sync="dialog.visible" :title="dialog.isEdit ? "编辑提示词" : "新增提示词"" width="700px" :close-on-click-modal="false">
  56. <el-form :model="dialog.form" label-width="100px" size="small">
  57. <el-form-item label="提示词键" :required="!dialog.isEdit">
  58. <el-input v-model="dialog.form.promptKey" placeholder="如 health_coach(英文下划线)" :disabled="dialog.isEdit" />
  59. </el-form-item>
  60. <el-form-item label="标题" required>
  61. <el-input v-model="dialog.form.title" placeholder="如 儿童健康教练" />
  62. </el-form-item>
  63. <el-form-item label="状态">
  64. <el-radio-group v-model="dialog.form.status">
  65. <el-radio :label="1">启用</el-radio>
  66. <el-radio :label="0">禁用</el-radio>
  67. </el-radio-group>
  68. </el-form-item>
  69. <el-form-item label="内容" required>
  70. <el-input v-model="dialog.form.content" type="textarea" :rows="18" placeholder="输入系统提示词内容..." />
  71. </el-form-item>
  72. <el-form-item label="备注">
  73. <el-input v-model="dialog.form.remark" placeholder="本次变更说明(可选)" />
  74. </el-form-item>
  75. </el-form>
  76. <div slot="footer">
  77. <el-button size="small" @click="dialog.visible = false">取消</el-button>
  78. <el-button size="small" type="primary" :loading="saving" @click="savePrompt">保存</el-button>
  79. </div>
  80. </el-dialog>
  81. <!-- 预览弹窗 -->
  82. <el-dialog title="提示词预览" :visible.sync="previewVisible" width="600px">
  83. <el-card shadow="never" style="white-space: pre-wrap; font-family: monospace; font-size: 13px; line-height: 1.8; background: #fafafa;">
  84. {{ previewContent }}
  85. </el-card>
  86. <div slot="footer">
  87. <el-button size="small" @click="previewVisible = false">关闭</el-button>
  88. </div>
  89. </el-dialog>
  90. <!-- 版本历史弹窗 -->
  91. <el-dialog title="版本历史" :visible.sync="versionDialog.visible" width="900px">
  92. <div style="margin-bottom:12px; color:#666; font-size:13px;">
  93. 当前提示词: <strong>{{ versionDialog.promptKey }}</strong> — {{ versionDialog.title }}
  94. </div>
  95. <el-table :data="versionDialog.versions" border stripe size="small" max-height="400">
  96. <el-table-column prop="version" label="版本号" width="80" align="center">
  97. <template slot-scope="{ row }">
  98. <el-tag :type="row.version === currentVersion ? "primary" : "default"" size="mini">
  99. v{{ row.version }}
  100. </el-tag>
  101. </template>
  102. </el-table-column>
  103. <el-table-column prop="operator" label="操作人" width="100" />
  104. <el-table-column prop="remark" label="说明" min-width="120" show-overflow-tooltip />
  105. <el-table-column prop="createdAt" label="时间" width="160">
  106. <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
  107. </el-table-column>
  108. <el-table-column label="操作" width="100" fixed="right">
  109. <template slot-scope="{ row }">
  110. <el-button
  111. v-if="row.version !== currentVersion"
  112. type="text" size="small"
  113. @click="doRollback(versionDialog.promptKey, row.version, row.title)"
  114. :disabled="row.version === currentVersion">
  115. 回滚到 v{{ row.version }}
  116. </el-button>
  117. <span v-else style="color:#909399; font-size:12px;">当前版本</span>
  118. </template>
  119. </el-table-column>
  120. <div slot="footer">
  121. <el-button size="small" @click="versionDialog.visible = false">关闭</el-button>
  122. </div>
  123. </el-dialog>
  124. </el-table>
  125. </div>
  126. </template>
  127. <script>
  128. import ListStateMixin from '@/mixins/ListStateMixin'
  129. import { getPromptList, savePrompt, resetPrompt, deletePrompt, listVersions, rollbackPrompt } from "@/api/systemPrompt"
  130. export default {
  131. name: 'SystemPromptManagement',
  132. mixins: [ListStateMixin],
  133. data() {
  134. return {
  135. list: [],
  136. loading: false,
  137. filters: { keyword: "" },
  138. page: 1,
  139. size: 20,
  140. total: 0,
  141. dialog: { visible: false, isEdit: false, form: {} },
  142. saving: false,
  143. previewVisible: false,
  144. previewContent: "",
  145. versionDialog: { visible: false, promptKey: "", title: "", versions: [], currentVersion: 0 }
  146. }
  147. },
  148. created() {
  149. this.fetchList()
  150. },
  151. methods: {
  152. async fetchList() {
  153. this.loading = true
  154. try {
  155. const res = await getPromptList({ keyword: this.filters.keyword, page: this.page, size: this.size })
  156. if (res && res.data) {
  157. this.list = res.data.records || []
  158. this.total = res.data.total || 0
  159. }
  160. } finally {
  161. this.loading = false
  162. }
  163. },
  164. showEdit(row) {
  165. this.dialog.isEdit = !!row
  166. this.dialog.form = row ? Object.assign({}, row) : { promptKey: "", title: "", content: "", status: 1, remark: "" }
  167. this.dialog.visible = true
  168. },
  169. async savePrompt() {
  170. const form = this.dialog.form
  171. if (!form.promptKey || !form.title || !form.content) {
  172. this.$message.warning("请填写完整")
  173. return
  174. }
  175. this.saving = true
  176. try {
  177. await savePrompt(form)
  178. this.$message.success("保存成功")
  179. this.dialog.visible = false
  180. this.fetchList()
  181. } finally {
  182. this.saving = false
  183. }
  184. },
  185. async resetToDefault(row) {
  186. this.$confirm("确定要重置为出厂默认内容吗?", "提示", { type: "warning" }).then(async () => {
  187. await resetPrompt(row.promptKey)
  188. this.$message.success("已重置")
  189. this.fetchList()
  190. }).catch(() => {})
  191. },
  192. deleteRow(row) {
  193. this.$confirm("确定要删除该提示词吗?", "提示", { type: "warning" }).then(async () => {
  194. await deletePrompt(row.id)
  195. this.$message.success("删除成功")
  196. this.fetchList()
  197. }).catch(() => {})
  198. },
  199. showPreview(row) {
  200. this.previewContent = row.content
  201. this.previewVisible = true
  202. },
  203. async showVersions(row) {
  204. this.versionDialog.promptKey = row.promptKey
  205. this.versionDialog.title = row.title
  206. this.versionDialog.currentVersion = row.version
  207. this.versionDialog.versions = []
  208. this.versionDialog.visible = true
  209. try {
  210. const res = await listVersions(row.promptKey)
  211. if (res && res.data) {
  212. this.versionDialog.versions = res.data
  213. }
  214. } catch (e) {
  215. this.$message.error("加载版本历史失败")
  216. }
  217. },
  218. async doRollback(promptKey, targetVersion, targetTitle) {
  219. this.$confirm(`确定要回滚到 v${targetVersion}(${targetTitle})吗?当前版本将被备份后恢复。`, "确认回滚", { type: "warning" }).then(async () => {
  220. try {
  221. await rollbackPrompt(promptKey, targetVersion)
  222. this.$message.success("回滚成功")
  223. this.versionDialog.visible = false
  224. this.fetchList()
  225. } catch (e) {
  226. this.$message.error("回滚失败")
  227. }
  228. }).catch(() => {})
  229. },
  230. handlePageChange(page) {
  231. this.page = page
  232. this.fetchList()
  233. }
  234. }
  235. }
  236. </script>
  237. <style scoped>
  238. .system-prompt-page {
  239. padding: 0;
  240. }
  241. </style>