RuleView.vue 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. <!-- 组件:RuleView;功能:七类规则配置与独立试算;props:无;emit:无。核心规则不可关闭,试算不修改业务单据。 -->
  2. <script setup lang="ts">
  3. import { onMounted, ref } from "vue";
  4. import { ElMessage } from "element-plus";
  5. import { api, jsonObject, pretty, type Row, type Page } from "../api";
  6. import { useSession } from "../store";
  7. import PageHeading from "../components/PageHeading.vue";
  8. import StatusBadge from "../components/StatusBadge.vue";
  9. import JsonEvidence from "../components/JsonEvidence.vue";
  10. const session = useSession(),
  11. rows = ref<Row[]>([]),
  12. busy = ref(false),
  13. dialog = ref(false),
  14. testDialog = ref(false),
  15. saving = ref(false),
  16. current = ref<Row>({}),
  17. history = ref<Row[]>([]);
  18. const params = ref("{}"),
  19. scope = ref("{}"),
  20. enabled = ref(true),
  21. severity = ref("error"),
  22. invoice = ref("{}"),
  23. context = ref("{}"),
  24. result = ref<Row | null>(null);
  25. async function load() {
  26. busy.value = true;
  27. try {
  28. rows.value = (await api<Page>("/rules?page_size=100")).items;
  29. } catch (error) {
  30. ElMessage.error((error as Error).message);
  31. } finally {
  32. busy.value = false;
  33. }
  34. }
  35. async function edit(row: Row) {
  36. try {
  37. const data = await api(`/rules/${row.code}`);
  38. current.value = data.current;
  39. history.value = data.history;
  40. params.value = pretty(data.current.params);
  41. scope.value = pretty(data.current.scope);
  42. enabled.value = data.current.enabled;
  43. severity.value = data.current.severity;
  44. dialog.value = true;
  45. } catch (error) {
  46. ElMessage.error((error as Error).message);
  47. }
  48. }
  49. async function save() {
  50. saving.value = true;
  51. try {
  52. await api(`/rules/${current.value.code}`, "PATCH", {
  53. expected_version: current.value.version,
  54. params: jsonObject(params.value),
  55. scope: jsonObject(scope.value),
  56. enabled: enabled.value,
  57. severity: severity.value,
  58. });
  59. dialog.value = false;
  60. await load();
  61. ElMessage.success("规则新版本已保存");
  62. } catch (error) {
  63. ElMessage.error((error as Error).message);
  64. } finally {
  65. saving.value = false;
  66. }
  67. }
  68. function openTest(row: Row) {
  69. current.value = row;
  70. invoice.value = "{}";
  71. context.value = "{}";
  72. result.value = null;
  73. testDialog.value = true;
  74. }
  75. async function test() {
  76. saving.value = true;
  77. try {
  78. result.value = await api(`/rules/${current.value.code}/test`, "POST", {
  79. invoice: jsonObject(invoice.value),
  80. context: jsonObject(context.value),
  81. });
  82. } catch (error) {
  83. ElMessage.error((error as Error).message);
  84. } finally {
  85. saving.value = false;
  86. }
  87. }
  88. onMounted(load);
  89. </script>
  90. <template>
  91. <PageHeading
  92. title="审计规则"
  93. description="参数、严重程度与适用范围均版本化;核心检查不能被关闭或绕过。"
  94. ><el-button @click="load">刷新</el-button></PageHeading
  95. ><el-alert
  96. :closable="false"
  97. type="info"
  98. title="税额检查使用发票声明税率;税号长度仅作演示格式检查,不代表税务平台验真。政策合规需要真实制度片段和模型结论。"
  99. />
  100. <section class="card">
  101. <el-table :data="rows" v-loading="busy"
  102. ><el-table-column
  103. prop="name"
  104. label="规则"
  105. min-width="150"
  106. /><el-table-column
  107. prop="code"
  108. label="标识"
  109. min-width="230"
  110. class-name="mono"
  111. /><el-table-column
  112. prop="version"
  113. label="版本"
  114. width="65"
  115. /><el-table-column label="严重程度" width="100"
  116. ><template #default="{ row }"
  117. ><StatusBadge :value="row.severity" /></template></el-table-column
  118. ><el-table-column label="启用" width="85"
  119. ><template #default="{ row }"
  120. ><el-tag :type="row.enabled ? 'success' : 'info'">{{
  121. row.enabled ? "生效" : "停用"
  122. }}</el-tag></template
  123. ></el-table-column
  124. ><el-table-column label="参数" min-width="155"
  125. ><template #default="{ row }"
  126. ><JsonEvidence
  127. :value="{ params: row.params, scope: row.scope }"
  128. title="配置详情" /></template></el-table-column
  129. ><el-table-column label="操作" min-width="160"
  130. ><template #default="{ row }"
  131. ><el-button link type="primary" @click="edit(row)">{{
  132. session.level >= 4 ? "编辑" : "版本"
  133. }}</el-button
  134. ><el-button link type="primary" @click="openTest(row)"
  135. >试算</el-button
  136. ></template
  137. ></el-table-column
  138. ></el-table
  139. >
  140. </section>
  141. <el-dialog
  142. v-model="dialog"
  143. :title="`${current.name || ''} · v${current.version || ''}`"
  144. width="min(690px,94vw)"
  145. ><el-form label-position="top"
  146. ><el-form-item label="生效"
  147. ><el-switch
  148. v-model="enabled"
  149. :disabled="session.level < 4" /></el-form-item
  150. ><el-form-item label="严重程度"
  151. ><el-select v-model="severity" :disabled="session.level < 4"
  152. ><el-option label="警告" value="warning" /><el-option
  153. label="错误"
  154. value="error" /><el-option
  155. label="严重"
  156. value="critical" /></el-select></el-form-item
  157. ><el-form-item label="参数(仅允许该规则的白名单字段)"
  158. ><el-input
  159. v-model="params"
  160. class="json-editor"
  161. type="textarea"
  162. :rows="7"
  163. :readonly="session.level < 4" /></el-form-item
  164. ><el-form-item label="适用范围(departments / types,空对象表示全部)"
  165. ><el-input
  166. v-model="scope"
  167. class="json-editor"
  168. type="textarea"
  169. :rows="4"
  170. :readonly="session.level < 4" /></el-form-item></el-form
  171. ><JsonEvidence :value="history" title="历史版本" /><template #footer
  172. ><el-button @click="dialog = false">关闭</el-button
  173. ><el-button
  174. v-if="session.level >= 4"
  175. type="primary"
  176. :loading="saving"
  177. @click="save"
  178. >发布新规则版本</el-button
  179. ></template
  180. ></el-dialog
  181. >
  182. <el-dialog
  183. v-model="testDialog"
  184. :title="`规则试算:${current.name || ''}`"
  185. width="min(750px,94vw)"
  186. ><el-alert
  187. :closable="false"
  188. title="上下文显式提供 today、existing_hashes、type 等字段;缺少输入时返回未完成或不通过,试算不写正式审核。"
  189. type="info"
  190. /><el-form label-position="top"
  191. ><el-form-item label="发票字段 JSON"
  192. ><el-input
  193. v-model="invoice"
  194. class="json-editor"
  195. type="textarea"
  196. :rows="8" /></el-form-item
  197. ><el-form-item label="上下文 JSON"
  198. ><el-input
  199. v-model="context"
  200. class="json-editor"
  201. type="textarea"
  202. :rows="4" /></el-form-item></el-form
  203. ><JsonEvidence
  204. v-if="result"
  205. :value="result"
  206. title="试算结果(展开)"
  207. /><template #footer
  208. ><el-button @click="testDialog = false">关闭</el-button
  209. ><el-button type="primary" :loading="saving" @click="test"
  210. >执行试算</el-button
  211. ></template
  212. ></el-dialog
  213. >
  214. </template>