Explorar o código

feat: 重写渠道和运营商

x1ongzhu hai 1 ano
pai
achega
c68e79ddf5

+ 8 - 0
src/enums/index.js

@@ -46,3 +46,11 @@ export const SysConfigType = {
     object: '对象',
     file: '文件'
 }
+export const MatcherType = {
+    equal: '等于',
+    regex: '正则',
+    prefix: '前缀',
+    suffix: '后缀',
+    in: '包含',
+    notIn: '不包含'
+}

+ 8 - 0
src/router/index.js

@@ -102,6 +102,14 @@ const router = createRouter({
                     meta: {
                         title: '余额明细'
                     }
+                },
+                {
+                    path: '/operator',
+                    name: 'operator',
+                    component: () => import('../views/OperatorView.vue'),
+                    meta: {
+                        title: '运营商配置'
+                    }
                 }
             ]
         }

+ 62 - 124
src/views/ChannelView.vue

@@ -1,60 +1,53 @@
 <template>
-    <PagingTable url="/channel" :where="where" ref="table">
-        <template #filter>
-            <ElButton :icon="Refresh" @click="table.refresh()"></ElButton>
-            <ElButton :icon="Plus" @click="onEdit()">添加</ElButton>
-        </template>
-        <ElTableColumn prop="id" label="#" width="80" />
-        <ElTableColumn prop="mcc" label="MCC" />
-        <ElTableColumn prop="mnc" label="MNC" />
-        <ElTableColumn prop="country" label="国家" />
-        <ElTableColumn prop="operator" label="运营商" />
-        <ElTableColumn prop="scope" label="IMSI范围" />
-        <ElTableColumn prop="platform" label="平台" />
-        <ElTableColumn prop="remark" label="备注" />
-        <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
-        <ElTableColumn label="开关" align="center" width="100">
-            <template #default="{ row }">
-                <ElSwitch v-model="row.switch" @change="updateSwitch(row)" />
-            </template>
-        </ElTableColumn>
-        <ElTableColumn label="操作" align="center" width="200">
-            <template #default="{ row }">
-                <ElButton type="danger" size="small" @click="del(row)">删除</ElButton>
-            </template>
-        </ElTableColumn>
-    </PagingTable>
-    <EditDialog v-model="showEditDialog" :model="model" :rules="rules" :on-submit="submit" @success="table.refresh()">
-        <ElFormItem prop="mcc" label="MCC">
-            <ElInput v-model="model.mcc" placeholder="请输入MCC" />
-        </ElFormItem>
-        <ElFormItem prop="mnc" label="MNC">
-            <ElInput v-model="model.mnc" placeholder="请输入MNC" />
-        </ElFormItem>
-        <ElFormItem prop="country" label="国家">
-            <ElInput v-model="model.country" placeholder="请输入国家" />
-        </ElFormItem>
-        <ElFormItem prop="operator" label="运营商">
-            <ElInput v-model="model.operator" placeholder="请输入运营商" />
-        </ElFormItem>
-        <ElFormItem prop="scope" label="IMSI范围">
-            <ElInput v-model="model.scope" placeholder="IMSI范围" type="textarea" />
-        </ElFormItem>
-        <ElFormItem prop="platform" label="平台">
-            <ElSelect v-model="model.platform" placeholder="请选择平台">
-                <ElOption v-for="item in platformList" :key="item.value" :label="item.value" :value="item.value" />
+    <div>
+        <ElButton type="primary" @click="onEdit" :icon="Plus"> 新增 </ElButton>
+        <div v-for="item in channels" :key="item.id" class="p-4 rounded-lg mt-4 bg-white dark:bg-neutral-800">
+            <div class="flex items-center">
+                <div class="font-bold text-md w-[100px]">{{ item.source }}</div>
+                <ElButton :icon="Edit" @click="onEdit(item)" circle class="ml-4" type="primary" size="small"></ElButton>
+            </div>
+            <div class="flex flex-wrap mt-4">
+                <div v-for="(countryConfig, i) in item.countryConfig" :key="i" class="flex items-center">
+                    <div class="w-20 text-right text-sm text-neutral-800 dark:text-neutral-200">
+                        {{ countryConfig.countryCode }}
+                    </div>
+                    <ElSwitch v-model="countryConfig.enabled" class="ml-2" @change="saveCountryConfig(item)" />
+                </div>
+            </div>
+        </div>
+    </div>
+    <EditDialog v-model="showEditDialog" :model="model" :rules="rules" :on-submit="submit" @success="getData">
+        <ElFormItem prop="source" label="平台">
+            <ElSelect v-model="model.source" placeholder="请选择平台">
+                <ElOption lable="mwze167" value="mwze167"></ElOption>
+                <ElOption lable="durian" value="durian"></ElOption>
             </ElSelect>
         </ElFormItem>
         <ElFormItem prop="remark" label="备注">
             <ElInput v-model="model.remark" placeholder="请输入备注" />
         </ElFormItem>
+        <ElFormItem prop="countryConfig" label="国家配置">
+            <div v-for="(item, i) in model.countryConfig" :key="i" class="w-full mb-2">
+                <ElInput v-model="item.countryCode" placeholder="国家代码" class="!w-[80px]" />
+                <ElSwitch v-model="item.enabled" class="ml-4" />
+                <ElButton
+                    type="danger"
+                    @click="removeCountryConfig(i)"
+                    :icon="Trash"
+                    circle
+                    class="ml-4"
+                    size="small"
+                />
+            </div>
+            <ElButton type="primary" @click="addCountryConfig" :icon="Plus" />
+        </ElFormItem>
     </EditDialog>
 </template>
 <script setup>
-import { ref } from 'vue'
+import { onMounted, ref } from 'vue'
 import PagingTable from '@/components/PagingTable.vue'
 import { useTimeFormatter } from '@/utils/formatter'
-import { Plus, Refresh } from '@vicons/tabler'
+import { Plus, Refresh, Trash, Edit } from '@vicons/tabler'
 import EditDialog from '@/components/EditDialog.vue'
 import { setupEditDialog } from '@/utils/editDialog'
 import EnumSelect from '@/components/EnumSelect.vue'
@@ -63,93 +56,38 @@ import { http } from '@/plugins/http'
 import { ElMessage, ElMessageBox } from 'element-plus'
 import { useClipboard } from '@vueuse/core'
 
-const where = ref({})
-const timeFormatter = useTimeFormatter()
-const table = ref(null)
-const model = ref({})
-const rules = {
-    mcc: [{ required: true, message: '请输入MCC', trigger: 'blur' }],
-    mnc: [{ required: true, message: '请输入MNC', trigger: 'blur' }],
-    country: [{ required: true, message: '请输入国家码', trigger: 'blur' }],
-    platform: [{ required: true, message: '请输入平台名称', trigger: 'blur' }]
+const channels = ref([])
+function getData() {
+    http.post('/channel').then((res) => {
+        channels.value = res.items
+    })
 }
-const platformList = [
-    {
-        value: 'mwze167',
-    },
-    {
-        value: 'durian',
-    },
-]
+onMounted(async () => {
+    getData()
+})
 
+const model = ref({
+    countryConfig: []
+})
 const { showEditDialog, onEdit } = setupEditDialog(model)
-
+const rules = {
+    source: [{ required: true, message: '请输入平台名称', trigger: 'blur' }]
+}
 async function submit() {
-    if (model.value.scope) {
-        model.value.scope = model.value.scope.split('\n').map(Number)
-    } else {
-        model.value.scope = []
-    }
     await http.put('/channel', model.value)
     ElMessage.success('保存成功')
 }
-
-function del(row) {
-    ElMessageBox.confirm('确定删除吗?', '提示', {
-        type: 'warning'
-    }).then(() => {
-        http.delete(`/channel/${row.id}`).then(() => {
-            ElMessage.success('删除成功')
-            table.value.refresh()
-        })
-    })
-}
-
-function updateSwitch(row) {
-    http.get(`/channel/updateSwitch/${row.id}`).then(() => {
-        table.value.refresh()
+function addCountryConfig() {
+    model.value.countryConfig = model.value.countryConfig || []
+    model.value.countryConfig.push({
+        countryCode: '',
+        enabled: true
     })
 }
-
-const selectedRow = ref(null)
-const showDetailDialog = ref(false)
-const phoneTable = ref(null)
-
-const phoneModel = ref({})
-const phoneRules = {
-    number: [{ required: true, message: '请输入号码', trigger: 'blur' }]
-}
-const { showEditDialog: showPhoneEditDialog, onEdit: onPhoneEdit } = setupEditDialog(phoneModel)
-
-async function submitPhone() {
-    phoneModel.value.listId = selectedRow.value.id
-    await http.put('/phone-list/phone', phoneModel.value)
-    ElMessage.success('保存成功')
-}
-
-function delPhone(row) {
-    ElMessageBox.confirm('确定删除吗?', '提示', {
-        type: 'warning'
-    }).then(() => {
-        http.delete(`/phone-list/phone/${row.id}`).then(() => {
-            ElMessage.success('删除成功')
-            phoneTable.value.refresh()
-        })
-    })
+function removeCountryConfig(index) {
+    model.value.countryConfig.splice(index, 1)
 }
-
-function importList() {
-    const input = document.createElement('input')
-    input.type = 'file'
-    input.accept = '.txt'
-    input.onchange = async () => {
-        const file = input.files[0]
-        const formData = new FormData()
-        formData.append('file', file)
-        await http.post(`/phone-list/${selectedRow.value.id}/import`, formData)
-        ElMessage.success('导入成功')
-        phoneTable.value.refresh()
-    }
-    input.click()
+function saveCountryConfig(item) {
+    http.put('/channel', item)
 }
 </script>

+ 4 - 0
src/views/MainView.vue

@@ -99,6 +99,10 @@ if (roles.includes('admin')) {
                     name: '/channel',
                     title: '渠道列表'
                 },
+                {
+                    name: '/operator',
+                    title: '运营商配置'
+                },
                 {
                     name: '/rcsNumber',
                     title: 'RCS号码'

+ 163 - 0
src/views/OperatorView.vue

@@ -0,0 +1,163 @@
+<template>
+    <PagingTable url="/operator-config" :where="where" ref="table">
+        <template #filter>
+            <ElButton :icon="Refresh" @click="table.refresh()"></ElButton>
+            <ElButton
+                :icon="Plus"
+                @click="
+                    onEdit({
+                        matchers: []
+                    })
+                "
+            >
+                添加
+            </ElButton>
+        </template>
+        <ElTableColumn prop="country" label="国家" />
+        <ElTableColumn prop="remark" label="备注" />
+        <ElTableColumn label="开关" align="center" width="100">
+            <template #default="{ row }">
+                <ElSwitch v-model="row.enabled" @change="toggleSwitch($event, row)" />
+            </template>
+        </ElTableColumn>
+        <ElTableColumn label="操作" align="center" width="200">
+            <template #default="{ row }">
+                <ElButton type="primary" size="small" @click="onEdit(row)">编辑</ElButton>
+                <ElButton type="danger" size="small" @click="del(row)">删除</ElButton>
+            </template>
+        </ElTableColumn>
+    </PagingTable>
+    <EditDialog
+        v-model="showEditDialog"
+        :model="model"
+        :rules="rules"
+        :on-submit="submit"
+        @success="table.refresh()"
+        width="900px"
+    >
+        <ElFormItem prop="country" label="国家">
+            <ElInput v-model="model.country" placeholder="请输入国家" />
+        </ElFormItem>
+        <ElFormItem prop="remark" label="备注">
+            <ElInput v-model="model.remark" placeholder="请输入备注" />
+        </ElFormItem>
+        <ElFormItem label="匹配规则">
+            <div
+                v-for="(matcher, i) in model.matchers"
+                :key="i"
+                class="matcher-item w-full mb-4 bg-neutral-100 dark:bg-neutral-900 p-2 rounded-lg"
+            >
+                <ElForm :model="matcher" inline label-position="right" label-width="80px">
+                    <ElFormItem prop="matchType" label="MCCMNC">
+                        <EnumSelect v-model="matcher.matchType" :enum="MatcherType" />
+                    </ElFormItem>
+                    <ElFormItem v-model="matcher.match" class="ml-2">
+                        <ElInput v-model="matcher.match" placeholder="请输入匹配值" class="!w-[560px]" />
+                    </ElFormItem>
+                    <ElFormItem prop="mapTo" label="映射为" class="mt-2">
+                        <div>
+                            <ElInput v-model="matcher.mapTo.mcc" placeholder="MCC" class="!w-[100px]" />
+                            <ElInput v-model="matcher.mapTo.mnc" placeholder="MNC" class="!w-[100px] ml-2" />
+                        </div>
+                    </ElFormItem>
+                    <ElFormItem prop="remark" label="备注" class="mt-2">
+                        <ElInput v-model="matcher.remark" placeholder="请输入备注" class="!w-[215px]" />
+                    </ElFormItem>
+                    <ElFormItem prop="enabled" label="开关" class="mt-2">
+                        <ElSwitch v-model="matcher.enabled" />
+                    </ElFormItem>
+                    <ElButton @click="delMatcher(i)" size="small" class="mt-2 ml-4" :icon="Trash" circle></ElButton>
+                </ElForm>
+            </div>
+            <ElButton type="primary" @click="addMatcher" size="small">添加</ElButton>
+        </ElFormItem>
+
+        <ElFormItem prop="enabled" label="开关">
+            <ElSwitch v-model="model.enabled" />
+        </ElFormItem>
+    </EditDialog>
+</template>
+<script setup>
+import { ref } from 'vue'
+import PagingTable from '@/components/PagingTable.vue'
+import { useTimeFormatter } from '@/utils/formatter'
+import { Plus, Refresh, Trash } from '@vicons/tabler'
+import EditDialog from '@/components/EditDialog.vue'
+import { setupEditDialog } from '@/utils/editDialog'
+import EnumSelect from '@/components/EnumSelect.vue'
+import { UserRole } from '@/enums'
+import { http } from '@/plugins/http'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import { useClipboard } from '@vueuse/core'
+import { MatcherType } from '@/enums'
+
+const where = ref({})
+const timeFormatter = useTimeFormatter()
+const table = ref(null)
+const model = ref({})
+const rules = {
+    mcc: [{ required: true, message: '请输入MCC', trigger: 'blur' }],
+    mnc: [{ required: true, message: '请输入MNC', trigger: 'blur' }],
+    country: [{ required: true, message: '请输入国家码', trigger: 'blur' }],
+    platform: [{ required: true, message: '请输入平台名称', trigger: 'blur' }]
+}
+const platformList = [
+    {
+        value: 'mwze167'
+    },
+    {
+        value: 'durian'
+    }
+]
+
+const { showEditDialog, onEdit } = setupEditDialog(model)
+
+async function submit() {
+    await http.put('/operator-config', model.value)
+    ElMessage.success('保存成功')
+}
+
+function del(row) {
+    ElMessageBox.confirm('确定删除吗?', '提示', {
+        type: 'warning'
+    }).then(() => {
+        http.delete(`/operator-config/${row.country}`).then(() => {
+            ElMessage.success('删除成功')
+            table.value.refresh()
+        })
+    })
+}
+
+async function toggleSwitch(value, row) {
+    row.enabled = value
+    await http.put('/operator-config', row)
+}
+
+function addMatcher() {
+    model.value.matchers = model.value.matchers || []
+    model.value.matchers.push({
+        match: '',
+        matchType: 'equal',
+        mapTo: {
+            mcc: '',
+            mnc: ''
+        },
+        remark: '',
+        enabled: true
+    })
+}
+
+function delMatcher(index) {
+    model.value.matchers.splice(index, 1)
+}
+</script>
+<style lang="less">
+.matcher-item {
+    .el-select {
+        width: 100px;
+    }
+    .el-form-item {
+        margin-right: 0;
+    }
+}
+</style>

+ 26 - 23
src/views/RcsNumberView.vue

@@ -2,8 +2,13 @@
     <PagingTable url="/rcs-number" :where="where" ref="table">
         <template #filter>
             <ElButton :icon="Search" @click="table.refresh()"></ElButton>
-            <ElSelect v-model="channel" placeholder="渠道" clearable value-key="id">
-                <ElOption v-for="channel in channels" :key="channel.id" :label="channel.country" :value="channel" />
+            <ElSelect v-model="country" placeholder="渠道" clearable value-key="country">
+                <ElOption
+                    v-for="country in countries"
+                    :key="country.country"
+                    :label="country.country"
+                    :value="country"
+                />
             </ElSelect>
             <ElButton :icon="Plus" @click="getNumber()">取号</ElButton>
         </template>
@@ -15,7 +20,7 @@
         <ElTableColumn prop="message" label="消息" show-overflow-tooltip />
         <ElTableColumn prop="deviceId" label="设备Id" align="center" />
         <ElTableColumn prop="deviceName" label="设备编号" align="center" width="120" />
-        <ElTableColumn prop="taskId" label="任务Id" align="center" width="80"/>
+        <ElTableColumn prop="taskId" label="任务Id" align="center" width="80" />
         <ElTableColumn label="状态" align="center" width="80">
             <template #default="{ row }">
                 <ElTag v-if="row.status === 'pending'" type="info">等待</ElTag>
@@ -26,7 +31,9 @@
         </ElTableColumn>
         <ElTableColumn prop="createdAt" label="创建时间" :formatter="timeFormatter" width="150" />
         <ElTableColumn label="操作" align="center" width="120">
-            <template #default="{}"></template>
+            <template #default="{ row }">
+                <ElButton link size="small" @click="showRaw(row)">原始响应</ElButton>
+            </template>
         </ElTableColumn>
     </PagingTable>
 </template>
@@ -35,33 +42,20 @@ import { computed, onMounted, ref } from 'vue'
 import PagingTable from '@/components/PagingTable.vue'
 import { useTimeFormatter } from '@/utils/formatter'
 import { Plus, Search } from '@vicons/tabler'
-import EditDialog from '@/components/EditDialog.vue'
-import { setupEditDialog } from '@/utils/editDialog'
-import EnumSelect from '@/components/EnumSelect.vue'
-import { UserRole } from '@/enums'
 import { http } from '@/plugins/http'
 import { ElMessage, ElMessageBox } from 'element-plus'
-import { useClipboard } from '@vueuse/core'
 
-const where = computed(() => ({ country: channel.value?.country }))
+const where = computed(() => ({ country: country.value?.country.toLowerCase() }))
 const timeFormatter = useTimeFormatter()
 const table = ref(null)
-const model = ref({})
-const rules = {
-    username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
-    name: [{ required: true, message: '请输入昵称', trigger: 'blur' }],
-    phone: [{ required: true, message: '请输入手机', trigger: 'blur' }],
-    password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
-    roles: [{ required: true, message: '请选择角色', trigger: 'blur' }]
-}
-const channels = ref([])
-const channel = ref(null)
+const countries = ref([])
+const country = ref(null)
 
 async function getNumber() {
     await ElMessageBox.confirm('确定取号吗?')
     try {
         await http.put('/rcs-number', {
-            channelId: channel.value?.id
+            country: country.value?.country
         })
         ElMessage.success('取号成功')
         table.value.refresh()
@@ -72,8 +66,17 @@ async function getNumber() {
 }
 
 onMounted(() => {
-    http.post('/channel').then((res) => {
-        channels.value = res.items
+    http.post('/operator-config').then((res) => {
+        countries.value = res.items
     })
 })
+function showRaw(row) {
+    const raw = JSON.parse(row.rawResponse)
+    if (raw.data && raw.data[0] && raw.data[0].mcc) {
+        raw.data[0].mcc = JSON.parse(raw.data[0].mcc)
+    }
+    ElMessageBox.alert(`<div class="whitespace-pre">${JSON.stringify(raw, null, 4)}</div>`, '原始响应', {
+        dangerouslyUseHTMLString: true
+    })
+}
 </script>