OperatorView.vue 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. <template>
  2. <PagingTable url="/operator-config" :where="where" ref="table" :order="{ country: 'ASC' }">
  3. <template #filter>
  4. <ElButton :icon="Refresh" @click="table.refresh()"></ElButton>
  5. <ElInput
  6. v-model="country"
  7. placeholder="国家"
  8. clearable
  9. @input="updateWhereAndRefresh"
  10. style="width: 200px"
  11. />
  12. <ElSelect v-model="area" placeholder="区域" clearable @change="updateWhereAndRefresh">
  13. <ElOption v-for="area in areas" :key="area.value" :label="area.value" :value="area.value" />
  14. </ElSelect>
  15. <ElSelect v-model="enabled" placeholder="开关" clearable @change="updateWhereAndRefresh">
  16. <ElOption lable="开" value="开"></ElOption>
  17. <ElOption lable="关" value="关"></ElOption>
  18. </ElSelect>
  19. <ElButton
  20. :icon="Plus"
  21. @click="
  22. onEdit({
  23. matchers: [],
  24. configOverrides: {}
  25. })
  26. "
  27. >
  28. 添加
  29. </ElButton>
  30. </template>
  31. <ElTableColumn prop="country" label="国家" />
  32. <ElTableColumn prop="areaCode" label="区号" />
  33. <ElTableColumn prop="remark" label="备注" />
  34. <ElTableColumn prop="area" label="区域" />
  35. <ElTableColumn prop="weighting" label="权重" align="center" width="100" />
  36. <ElTableColumn label="开关" align="center" width="100">
  37. <template #default="{ row }">
  38. <ElSwitch v-model="row.enabled" @change="toggleSwitch($event, row)" />
  39. </template>
  40. </ElTableColumn>
  41. <ElTableColumn label="操作" align="center" width="200">
  42. <template #default="{ row }">
  43. <ElButton type="primary" size="small" @click="onEdit(row)">编辑</ElButton>
  44. <ElButton type="danger" size="small" @click="del(row)">删除</ElButton>
  45. </template>
  46. </ElTableColumn>
  47. </PagingTable>
  48. <EditDialog
  49. v-model="showEditDialog"
  50. :model="model"
  51. :rules="rules"
  52. :on-submit="submit"
  53. @success="table.refresh()"
  54. width="900px"
  55. inline
  56. class="operator-edit-dialog pl-[32px]"
  57. :show-message="false"
  58. label-position="top"
  59. >
  60. <ElFormItem prop="enabled" label="开关">
  61. <ElSwitch v-model="model.enabled" />
  62. </ElFormItem>
  63. <ElFormItem prop="country" label="国家">
  64. <ElInput v-model="model.country" placeholder="请输入国家" />
  65. </ElFormItem>
  66. <ElFormItem prop="areaCode" label="区号">
  67. <ElInput v-model="model.areaCode" placeholder="请输入区号" />
  68. </ElFormItem>
  69. <ElFormItem prop="area" label="区域">
  70. <ElSelect v-model="model.area" placeholder="请选择区域" clearable>
  71. <ElOption v-for="area in areas" :key="area.value" :label="area.value" :value="area.value" />
  72. </ElSelect>
  73. </ElFormItem>
  74. <ElFormItem prop="weighting" label="权重">
  75. <ElInput v-model="model.weighting" placeholder="请输入权重(默认1)" />
  76. </ElFormItem>
  77. <ElFormItem prop="remark" label="备注">
  78. <ElInput v-model="model.remark" placeholder="请输入备注" />
  79. </ElFormItem>
  80. <ElFormItem prop="e2ee" label="端到端加密">
  81. <ElRadioGroup v-model="model.e2ee">
  82. <ElRadioButton label="无" :value="0" />
  83. <ElRadioButton label="优先" :value="1" />
  84. <ElRadioButton label="必须" :value="2" />
  85. </ElRadioGroup>
  86. </ElFormItem>
  87. <ElFormItem prop="matchers" label="匹配规则" class="!w-full">
  88. <div
  89. v-for="(matcher, i) in model.matchers"
  90. :key="i"
  91. class="matcher-item w-full mb-4 bg-neutral-100 dark:bg-neutral-900 p-3 rounded-lg"
  92. >
  93. <ElForm
  94. :model="matcher"
  95. label-position="right"
  96. label-width="80px"
  97. ref="matcherForm"
  98. :rules="matcherRule"
  99. :show-message="false"
  100. >
  101. <ElFormItem prop="match" label="MCCMNC">
  102. <div class="flex">
  103. <EnumSelect v-model="matcher.matchType" :enum="MatcherType" />
  104. <ElInput v-model="matcher.match" placeholder="请输入匹配值" class="ml-3 flex-1" />
  105. </div>
  106. </ElFormItem>
  107. <div class="flex items-start">
  108. <ElFormItem prop="mapTo" label="映射为">
  109. <div class="flex">
  110. <ElInput v-model="matcher.mapTo.mcc" placeholder="MCC" class="!w-[100px]" />
  111. <ElInput v-model="matcher.mapTo.mnc" placeholder="MNC" class="!w-[100px] ml-2" />
  112. </div>
  113. </ElFormItem>
  114. <ElFormItem prop="remark" label="备注" class="ml-3 flex-1">
  115. <ElInput v-model="matcher.remark" placeholder="请输入备注" class="" />
  116. </ElFormItem>
  117. <ElFormItem prop="enabled" label="开关" class="ml-3">
  118. <ElSwitch v-model="matcher.enabled" />
  119. </ElFormItem>
  120. <ElFormItem label="删除" class="ml-3">
  121. <div class="h-[32px] flex items-center">
  122. <ElButton @click="delMatcher(i)" size="small" :icon="Trash" circle></ElButton>
  123. </div>
  124. </ElFormItem>
  125. </div>
  126. </ElForm>
  127. </div>
  128. <ElButton type="primary" @click="addMatcher" size="small">添加</ElButton>
  129. </ElFormItem>
  130. <ElDivider class="!mr-[32px]">自动分配设备</ElDivider>
  131. <ElFormItem prop="assignDevices" label="最小任务数量" label-position="top">
  132. <ElInputNumber
  133. v-model="model.minTaskNum"
  134. placeholder="请输入最小任务数量"
  135. controls-position="right"
  136. :min="0"
  137. class="!w-full"
  138. />
  139. </ElFormItem>
  140. <ElFormItem prop="minTaskNum" label="分配设备数量" label-position="top">
  141. <ElInputNumber
  142. v-model="model.assignDevices"
  143. placeholder="请输入分配设备数量"
  144. controls-position="right"
  145. :min="0"
  146. class="!w-full"
  147. />
  148. </ElFormItem>
  149. <ElFormItem prop="configOverrides" label="配置覆盖" class="!w-full">
  150. <div class="matcher-item w-full mb-4 bg-neutral-100 dark:bg-neutral-900 p-3 rounded-lg">
  151. <ElForm :model="model.configOverrides" label-position="top" inline>
  152. <ElFormItem prop="singleQty" label="单发数量">
  153. <ElInputNumber
  154. controls-position="right"
  155. v-model="model.configOverrides.singleQty"
  156. placeholder="请输入单发数量"
  157. :min="1"
  158. class="!w-[200px]"
  159. />
  160. </ElFormItem>
  161. <ElFormItem prop="groupQty" label="群发数量">
  162. <ElInputNumber
  163. controls-position="right"
  164. v-model="model.configOverrides.groupQty"
  165. placeholder="请输入群发数量"
  166. :min="1"
  167. class="!w-[200px] ml-3"
  168. />
  169. </ElFormItem>
  170. </ElForm>
  171. </div>
  172. </ElFormItem>
  173. </EditDialog>
  174. </template>
  175. <script setup>
  176. import { ref } from 'vue'
  177. import PagingTable from '@/components/PagingTable.vue'
  178. import { useTimeFormatter } from '@/utils/formatter'
  179. import { Plus, Refresh, Trash } from '@vicons/tabler'
  180. import EditDialog from '@/components/EditDialog.vue'
  181. import { setupEditDialog } from '@/utils/editDialog'
  182. import EnumSelect from '@/components/EnumSelect.vue'
  183. import { UserRole } from '@/enums'
  184. import { http } from '@/plugins/http'
  185. import { ElMessage, ElMessageBox } from 'element-plus'
  186. import { useClipboard } from '@vueuse/core'
  187. import { MatcherType } from '@/enums'
  188. const where = ref({})
  189. const timeFormatter = useTimeFormatter()
  190. const table = ref(null)
  191. const model = ref({})
  192. const matcherForm = ref([])
  193. const rules = {
  194. country: [{ required: true, message: '请输入国家码', trigger: 'blur' }],
  195. areaCode: [{ required: true, message: '请输入区号', trigger: 'blur' }],
  196. weighting: [{ required: true, message: '请输入权重', trigger: 'blur' }],
  197. area: [{ required: true, message: '请选择区域', trigger: 'blur' }],
  198. matchers: [
  199. {
  200. validator: async (rule, value, callback) => {
  201. if (value.length === 0) {
  202. callback(new Error('请添加匹配规则'))
  203. } else {
  204. console.log(matcherForm.value)
  205. try {
  206. await Promise.all(matcherForm.value.map((form) => form.validate()))
  207. callback()
  208. } catch (e) {
  209. callback(new Error('请检查匹配规则'))
  210. }
  211. }
  212. },
  213. trigger: 'blur'
  214. }
  215. ]
  216. }
  217. const matcherRule = {
  218. match: [{ required: true, message: '请输入匹配值', trigger: 'blur' }],
  219. mapTo: [
  220. { required: true, message: '请输入映射值', trigger: 'blur' },
  221. {
  222. validator: (rule, value, callback) => {
  223. if (!value.mcc) {
  224. callback(new Error('请输入MCC'))
  225. } else if (!value.mnc) {
  226. callback(new Error('请输入MNC'))
  227. } else {
  228. callback()
  229. }
  230. },
  231. trigger: 'blur'
  232. }
  233. ]
  234. }
  235. const platformList = [
  236. {
  237. value: 'mwze167'
  238. },
  239. {
  240. value: 'durian'
  241. }
  242. ]
  243. const country = ref(null)
  244. const area = ref(null)
  245. const enabled = ref(null)
  246. const areas = [
  247. {
  248. value: '欧洲'
  249. },
  250. {
  251. value: '北美洲'
  252. },
  253. {
  254. value: '中美洲'
  255. },
  256. {
  257. value: '南美洲'
  258. },
  259. {
  260. value: '非洲'
  261. },
  262. {
  263. value: '中东'
  264. },
  265. {
  266. value: '亚洲'
  267. },
  268. {
  269. value: '东南亚'
  270. },
  271. {
  272. value: '大洋洲'
  273. },
  274. {
  275. value: '阿拉伯半岛'
  276. },
  277. {
  278. value: '加勒比海'
  279. },
  280. {
  281. value: '太平洋'
  282. }
  283. ]
  284. function updateWhereAndRefresh() {
  285. where.value = {}
  286. if (area.value) {
  287. where.value.area = area.value
  288. } else {
  289. where.value.area = null
  290. }
  291. if (country.value) {
  292. where.value.country = country.value
  293. } else {
  294. where.value.country = null
  295. }
  296. if (enabled.value === '开') {
  297. where.value.enabled = true
  298. } else if (enabled.value === '关') {
  299. where.value.enabled = false
  300. } else {
  301. where.value.enabled = null
  302. }
  303. }
  304. const { showEditDialog, onEdit } = setupEditDialog(model)
  305. async function submit() {
  306. await http.put('/operator-config', model.value)
  307. ElMessage.success('保存成功')
  308. }
  309. function del(row) {
  310. ElMessageBox.confirm('确定删除吗?', '提示', {
  311. type: 'warning'
  312. }).then(() => {
  313. http.delete(`/operator-config/${row.country}`).then(() => {
  314. ElMessage.success('删除成功')
  315. table.value.refresh()
  316. })
  317. })
  318. }
  319. async function toggleSwitch(value, row) {
  320. row.enabled = value
  321. await http.put('/operator-config', row)
  322. }
  323. function addMatcher() {
  324. model.value.matchers = model.value.matchers || []
  325. model.value.matchers.push({
  326. match: '',
  327. matchType: 'equal',
  328. mapTo: {
  329. mcc: '',
  330. mnc: ''
  331. },
  332. remark: '',
  333. weighting: 1,
  334. enabled: true
  335. })
  336. }
  337. function delMatcher(index) {
  338. model.value.matchers.splice(index, 1)
  339. }
  340. </script>
  341. <style lang="less">
  342. .matcher-item {
  343. .el-select {
  344. width: 100px;
  345. }
  346. .el-form-item {
  347. margin-right: 0;
  348. }
  349. }
  350. .operator-edit-dialog {
  351. > .el-form-item {
  352. width: calc(33.3% - 32px);
  353. }
  354. }
  355. .el-form-item--label-top {
  356. .el-form-item__label {
  357. font-size: 12px;
  358. opacity: 0.8;
  359. margin-bottom: 4px;
  360. }
  361. }
  362. </style>