index.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. <template>
  2. <div class="code-page">
  3. <div class="code-hero">
  4. <div>
  5. <span>IDENTITY GENERATOR</span>
  6. <h1>事项编码生成</h1>
  7. <p>选择预警类型与等级,系统按统一编码规则生成唯一的预警事项编码。</p>
  8. </div>
  9. <div class="code-orbit">
  10. <div class="orbit-line"></div>
  11. <el-icon><Connection /></el-icon>
  12. </div>
  13. </div>
  14. <div class="generator-grid">
  15. <el-card class="steps-card" shadow="never">
  16. <div class="step-heading">
  17. <span>编码配置</span>
  18. <small>STEP {{ currentStep }}/2</small>
  19. </div>
  20. <div class="step-track">
  21. <div class="track-item active"><span>01</span><div><strong>选择类型</strong><small>确定预警所属领域</small></div></div>
  22. <div class="track-line"></div>
  23. <div class="track-item" :class="{ active: form.warningLevel }"><span>02</span><div><strong>选择等级</strong><small>确定预警响应级别</small></div></div>
  24. <div class="track-line"></div>
  25. <div class="track-item" :class="{ active: generatedCode }"><span>03</span><div><strong>生成编码</strong><small>得到唯一事项标识</small></div></div>
  26. </div>
  27. <div class="field-block">
  28. <label>预警类型</label>
  29. <div class="type-grid">
  30. <button v-for="item in warningTypes" :key="item.value" :class="{ selected: form.warningType === item.value }" @click="selectType(item.value)">
  31. <span class="type-symbol"><el-icon><Warning /></el-icon></span>
  32. <span>{{ item.label.replace('预警', '') }}</span>
  33. <el-icon v-if="form.warningType === item.value" class="selected-icon"><Check /></el-icon>
  34. </button>
  35. </div>
  36. </div>
  37. <div class="field-block">
  38. <label>预警级别</label>
  39. <div class="level-grid">
  40. <button
  41. v-for="item in warningLevels"
  42. :key="item.value"
  43. :class="{ selected: form.warningLevel === item.value }"
  44. :style="{ '--level-color': item.color }"
  45. @click="selectLevel(item.value)"
  46. >
  47. <span class="level-dot"></span>
  48. <div><strong>{{ item.shortLabel }}预警</strong><small>{{ item.label.match(/(.*)/)?.[0] || '' }}</small></div>
  49. <el-icon v-if="form.warningLevel === item.value"><Check /></el-icon>
  50. </button>
  51. </div>
  52. </div>
  53. <div class="generate-actions">
  54. <el-button @click="reset">重新选择</el-button>
  55. <el-button type="primary" :disabled="!canGenerate" :loading="loading" :icon="MagicStick" @click="generate">生成事项编码</el-button>
  56. </div>
  57. </el-card>
  58. <el-card class="result-card" shadow="never">
  59. <div class="result-heading"><span>生成结果</span><el-tag v-if="generatedCode" type="success">已生成</el-tag></div>
  60. <div v-if="generatedCode" class="code-result">
  61. <div class="result-icon"><el-icon><Key /></el-icon></div>
  62. <span class="result-label">预警事项编码</span>
  63. <strong>{{ generatedCode }}</strong>
  64. <button class="copy-button" @click="copyCode"><el-icon><CopyDocument /></el-icon> 复制编码</button>
  65. <div class="code-parts">
  66. <div><b>WI</b><span>事项标识</span></div>
  67. <div><b>{{ typePrefix }}</b><span>类型缩写</span></div>
  68. <div><b>{{ form.warningLevel }}</b><span>预警等级</span></div>
  69. <div><b>{{ datePart }}</b><span>生成日期</span></div>
  70. <div><b>{{ sequencePart }}</b><span>流水号</span></div>
  71. </div>
  72. </div>
  73. <div v-else class="empty-result">
  74. <div class="empty-illustration"><el-icon><MagicStick /></el-icon></div>
  75. <strong>等待生成编码</strong>
  76. <p>完成左侧类型和等级选择后<br />点击“生成事项编码”获取唯一标识</p>
  77. </div>
  78. <div class="rule-box">
  79. <div class="rule-title"><el-icon><InfoFilled /></el-icon> 编码规则</div>
  80. <p><code>WI + 类型缩写 + 等级 + YYYYMMDD + 4位流水号</code></p>
  81. <span>同一类型、等级和日期下流水号自动递增,确保编码唯一。</span>
  82. </div>
  83. </el-card>
  84. </div>
  85. </div>
  86. </template>
  87. <script setup name="WarningCodeGenerate">
  88. import { computed, onMounted, reactive, ref } from 'vue'
  89. import { ElMessage } from 'element-plus'
  90. import { Check, Connection, CopyDocument, InfoFilled, Key, MagicStick, Warning } from '@element-plus/icons-vue'
  91. import { getDicts } from '@/api/system/dict/data'
  92. import { generateWarningItemCode } from '@/api/warning/list'
  93. import { fallbackWarningTypes, normalizeWarningTypeDict, warningLevels } from '../shared'
  94. const loading = ref(false)
  95. const generatedCode = ref('')
  96. const warningTypes = ref(fallbackWarningTypes)
  97. const form = reactive({ warningType: '', warningLevel: '' })
  98. const canGenerate = computed(() => !!form.warningType && !!form.warningLevel)
  99. const currentStep = computed(() => generatedCode.value ? 3 : form.warningLevel ? 2 : 1)
  100. const typePrefix = computed(() => (form.warningType || '').slice(0, 3).toUpperCase())
  101. const datePart = computed(() => generatedCode.value ? generatedCode.value.slice(-12, -4) : 'YYYYMMDD')
  102. const sequencePart = computed(() => generatedCode.value ? generatedCode.value.slice(-4) : '0001')
  103. async function loadTypes() {
  104. try {
  105. const res = await getDicts('warning_type')
  106. warningTypes.value = normalizeWarningTypeDict(res.data)
  107. } catch {
  108. warningTypes.value = fallbackWarningTypes
  109. }
  110. }
  111. function selectType(value) {
  112. form.warningType = value
  113. generatedCode.value = ''
  114. }
  115. function selectLevel(value) {
  116. form.warningLevel = value
  117. generatedCode.value = ''
  118. }
  119. function reset() {
  120. form.warningType = ''
  121. form.warningLevel = ''
  122. generatedCode.value = ''
  123. }
  124. async function generate() {
  125. if (!canGenerate.value) return
  126. loading.value = true
  127. try {
  128. const res = await generateWarningItemCode(form.warningType, form.warningLevel)
  129. generatedCode.value = res.data || ''
  130. if (generatedCode.value) ElMessage.success('事项编码生成成功')
  131. } finally {
  132. loading.value = false
  133. }
  134. }
  135. async function copyCode() {
  136. if (!generatedCode.value) return
  137. await navigator.clipboard.writeText(generatedCode.value)
  138. ElMessage.success('编码已复制')
  139. }
  140. onMounted(loadTypes)
  141. </script>
  142. <style scoped lang="scss">
  143. .code-page { min-height: calc(100vh - 84px); padding: 20px; background: #f4f7fb; }
  144. .code-hero {
  145. display: flex;
  146. align-items: center;
  147. justify-content: space-between;
  148. padding: 28px 34px;
  149. color: #fff;
  150. border-radius: 18px;
  151. background: linear-gradient(120deg, #312e81, #4f46a5 52%, #7c3aed);
  152. box-shadow: 0 15px 40px rgba(73, 54, 163, .25);
  153. span { color: #d9d4ff; font-size: 11px; letter-spacing: 3px; }
  154. h1 { margin: 6px 0 8px; font-size: 28px; }
  155. p { margin: 0; color: #ddd9ff; }
  156. }
  157. .code-orbit { position: relative; display: grid; width: 90px; height: 90px; font-size: 28px; border: 1px solid rgba(255, 255, 255, .3); border-radius: 50%; background: rgba(255, 255, 255, .1); place-items: center; }
  158. .orbit-line { position: absolute; width: 112px; height: 38px; border: 1px solid rgba(255, 255, 255, .35); border-radius: 50%; transform: rotate(-32deg); }
  159. .generator-grid { display: grid; grid-template-columns: minmax(0, 1.3fr) minmax(340px, .7fr); gap: 16px; margin-top: 18px; }
  160. .steps-card, .result-card { border: 1px solid #e4e9f1; border-radius: 16px; }
  161. .step-heading, .result-heading { display: flex; align-items: center; justify-content: space-between; color: #1e293b; font-weight: 700; }
  162. .step-heading small { color: #8fa0b3; font: 11px Consolas, monospace; }
  163. .step-track { display: flex; align-items: center; margin: 26px 0 32px; }
  164. .track-item { display: flex; align-items: center; gap: 9px; color: #a0adbc; white-space: nowrap; span { display: grid; width: 30px; height: 30px; border: 1px solid #dce4ed; border-radius: 50%; place-items: center; } div { display: flex; flex-direction: column; gap: 3px; } strong { font-size: 12px; } small { font-size: 10px; } &.active { color: #4f46a5; span { color: #fff; border-color: #4f46a5; background: #4f46a5; } } }
  165. .track-line { flex: 1; height: 1px; margin: 0 12px; background: #e3e8ef; }
  166. .field-block { margin-bottom: 27px; label { display: block; margin-bottom: 12px; color: #334155; font-size: 14px; font-weight: 700; } }
  167. .type-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
  168. .type-grid button {
  169. position: relative;
  170. display: flex;
  171. align-items: center;
  172. gap: 8px;
  173. padding: 13px;
  174. color: #526477;
  175. text-align: left;
  176. border: 1px solid #e5eaf1;
  177. border-radius: 10px;
  178. background: #fff;
  179. cursor: pointer;
  180. &:hover, &.selected { color: #4f46a5; border-color: #9b94eb; background: #f5f3ff; }
  181. }
  182. .type-symbol { display: grid; width: 29px; height: 29px; color: #746ce1; border-radius: 8px; background: #ebe9ff; place-items: center; }
  183. .selected-icon { position: absolute; top: 8px; right: 8px; color: #4f46a5; }
  184. .level-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; }
  185. .level-grid button {
  186. display: flex;
  187. align-items: center;
  188. gap: 9px;
  189. padding: 13px 10px;
  190. color: #64748b;
  191. text-align: left;
  192. border: 1px solid #e6eaf0;
  193. border-radius: 10px;
  194. background: #fff;
  195. cursor: pointer;
  196. .level-dot { flex: 0 0 10px; width: 10px; height: 10px; border-radius: 50%; background: var(--level-color); }
  197. div { display: flex; flex-direction: column; gap: 3px; }
  198. strong { color: #334155; font-size: 12px; }
  199. small { color: #94a3b8; font-size: 10px; }
  200. .el-icon { margin-left: auto; color: var(--level-color); }
  201. &:hover, &.selected { border-color: var(--level-color); background: color-mix(in srgb, var(--level-color) 7%, #fff); }
  202. }
  203. .generate-actions { display: flex; justify-content: flex-end; gap: 10px; padding-top: 10px; border-top: 1px solid #edf0f4; }
  204. .result-card { min-height: 500px; }
  205. .result-heading { padding-bottom: 16px; border-bottom: 1px solid #edf0f4; small { color: #94a3b8; } }
  206. .code-result { display: flex; align-items: center; flex-direction: column; padding: 34px 8px 24px; text-align: center; }
  207. .result-icon { display: grid; width: 58px; height: 58px; margin-bottom: 14px; color: #4f46a5; font-size: 26px; border-radius: 18px; background: #efedff; place-items: center; }
  208. .result-label { color: #94a3b8; font-size: 12px; }
  209. .code-result > strong { margin: 9px 0 15px; color: #27205d; font: 25px Consolas, monospace; letter-spacing: 1px; }
  210. .copy-button { padding: 7px 12px; color: #4f46a5; border: 1px solid #c9c5fa; border-radius: 7px; background: #f7f6ff; cursor: pointer; }
  211. .code-parts { display: flex; width: 100%; justify-content: center; gap: 8px; margin-top: 28px; }
  212. .code-parts div { display: flex; min-width: 46px; flex-direction: column; gap: 4px; padding: 8px 5px; border: 1px solid #eceef4; border-radius: 7px; background: #fafbfe; }
  213. .code-parts b { color: #4f46a5; font: 12px Consolas, monospace; }
  214. .code-parts span { color: #94a3b8; font-size: 9px; }
  215. .empty-result { display: flex; align-items: center; flex-direction: column; padding: 70px 0 60px; text-align: center; }
  216. .empty-illustration { display: grid; width: 72px; height: 72px; margin-bottom: 14px; color: #a39df1; font-size: 34px; border-radius: 22px; background: #f2f1ff; place-items: center; }
  217. .empty-result strong { color: #48566b; }
  218. .empty-result p { color: #9aa7b6; font-size: 12px; line-height: 1.8; }
  219. .rule-box { padding: 14px; border-radius: 10px; background: #f7f7ff; }
  220. .rule-title { color: #5c55bc; font-size: 12px; font-weight: 700; }
  221. .rule-box p { margin: 8px 0; color: #5e6581; font-size: 11px; }
  222. .rule-box code { color: #4f46a5; font: 11px Consolas, monospace; }
  223. .rule-box > span { color: #9aa0b3; font-size: 10px; }
  224. @media (max-width: 1100px) { .generator-grid { grid-template-columns: 1fr; } }
  225. @media (max-width: 700px) { .type-grid, .level-grid { grid-template-columns: repeat(2, 1fr); } .track-item div { display: none; } }
  226. </style>