多数电账号管理
This commit is contained in:
@@ -0,0 +1,363 @@
|
||||
<template>
|
||||
<div class="page-shell">
|
||||
<section class="page-card table-fill">
|
||||
<div class="page-toolbar">
|
||||
<h2 class="page-toolbar-title">数电账号管理</h2>
|
||||
<div class="page-toolbar-actions">
|
||||
<n-button :loading="loading" @click="refreshAccounts">
|
||||
<template #icon><n-icon :component="RefreshCw" /></template>
|
||||
刷新
|
||||
</n-button>
|
||||
<n-button v-if="canManage" type="primary" @click="showCreate = true">
|
||||
<template #icon><n-icon :component="Plus" /></template>
|
||||
新增
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card-body card-body-fill table-fill">
|
||||
<n-data-table
|
||||
flex-height
|
||||
size="small"
|
||||
:columns="columns"
|
||||
:data="accounts"
|
||||
:loading="loading"
|
||||
:pagination="{ pageSize: 10 }"
|
||||
:row-key="(row: DigitalAccountItem) => row.id"
|
||||
:scroll-x="1460"
|
||||
/>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<n-modal v-model:show="showCreate" preset="card" title="新增数电账号" class="modal-card">
|
||||
<n-form ref="createFormRef" :model="createForm" :rules="createRules" label-placement="top">
|
||||
<n-grid responsive="screen" :cols="2" :x-gap="12">
|
||||
<n-gi>
|
||||
<n-form-item label="税局账号" path="account">
|
||||
<n-input v-model:value="createForm.account" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="税局密码" path="taxPassword">
|
||||
<n-input v-model:value="createForm.taxPassword" type="password" show-password-on="click" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="姓名" path="name">
|
||||
<n-input v-model:value="createForm.name" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="手机号" path="phoneNum">
|
||||
<n-input v-model:value="createForm.phoneNum" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="身份类型" path="identityType">
|
||||
<n-select v-model:value="createForm.identityType" :options="identityOptions" />
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<n-form-item label="平台密码" path="platformPassword">
|
||||
<n-input
|
||||
v-model:value="createForm.platformPassword"
|
||||
type="password"
|
||||
show-password-on="click"
|
||||
/>
|
||||
</n-form-item>
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
<div class="modal-actions">
|
||||
<n-button @click="showCreate = false">取消</n-button>
|
||||
<n-button type="primary" :loading="saving" @click="createAccount">保存</n-button>
|
||||
</div>
|
||||
</n-form>
|
||||
</n-modal>
|
||||
|
||||
<n-modal v-model:show="showSms" preset="card" title="短信登录" class="sms-card">
|
||||
<n-space vertical>
|
||||
<n-alert v-if="smsTip" type="info">{{ smsTip }}</n-alert>
|
||||
<n-input v-model:value="smsCode" placeholder="请输入短信验证码" />
|
||||
<div class="modal-actions">
|
||||
<n-button @click="showSms = false">取消</n-button>
|
||||
<n-button type="primary" :loading="smsLoading" @click="submitSms">确认登录</n-button>
|
||||
</div>
|
||||
</n-space>
|
||||
</n-modal>
|
||||
|
||||
<n-modal v-model:show="showQrcode" preset="card" title="风险认证" class="sms-card">
|
||||
<div class="qrcode-wrap">
|
||||
<img v-if="qrcodeImg" :src="qrcodeImg" alt="认证二维码" />
|
||||
<n-empty v-else description="暂无二维码" />
|
||||
<n-button size="small" :loading="qrLoading" @click="loadQrcode">刷新二维码</n-button>
|
||||
</div>
|
||||
</n-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue'
|
||||
import type { DataTableColumns, FormInst, FormRules } from 'naive-ui'
|
||||
import {
|
||||
NAlert,
|
||||
NButton,
|
||||
NDataTable,
|
||||
NEmpty,
|
||||
NForm,
|
||||
NFormItem,
|
||||
NGi,
|
||||
NGrid,
|
||||
NIcon,
|
||||
NInput,
|
||||
NModal,
|
||||
NSelect,
|
||||
NSpace,
|
||||
NTag,
|
||||
useMessage
|
||||
} from 'naive-ui'
|
||||
import { KeyRound, Plus, RefreshCw, ShieldCheck } from 'lucide-vue-next'
|
||||
import {
|
||||
createDigitalAccountApi,
|
||||
getAuthQrcodeApi,
|
||||
listDigitalAccountsApi,
|
||||
refreshDigitalAccountsApi,
|
||||
sendLoginSmsCodeApi,
|
||||
smsLoginApi
|
||||
} from '@/api/piaotong'
|
||||
import type { DigitalAccountItem } from '@/api/piaotong'
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const message = useMessage()
|
||||
const authStore = useAuthStore()
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const accounts = ref<DigitalAccountItem[]>([])
|
||||
const showCreate = ref(false)
|
||||
const showSms = ref(false)
|
||||
const showQrcode = ref(false)
|
||||
const smsLoading = ref(false)
|
||||
const qrLoading = ref(false)
|
||||
const smsCode = ref('')
|
||||
const smsTip = ref('')
|
||||
const qrcodeImg = ref('')
|
||||
const selected = ref<DigitalAccountItem | null>(null)
|
||||
const createFormRef = ref<FormInst | null>(null)
|
||||
|
||||
const canManage = computed(() => authStore.user?.userType !== 'DIGITAL_OPERATOR')
|
||||
|
||||
const createForm = reactive({
|
||||
account: '',
|
||||
taxPassword: '',
|
||||
identityType: '09',
|
||||
phoneNum: '',
|
||||
name: '',
|
||||
platformPassword: ''
|
||||
})
|
||||
|
||||
const createRules: FormRules = {
|
||||
account: [{ required: true, message: '请输入税局账号', trigger: ['blur', 'input'] }],
|
||||
taxPassword: [{ required: true, message: '请输入税局密码', trigger: ['blur', 'input'] }],
|
||||
name: [{ required: true, message: '请输入姓名', trigger: ['blur', 'input'] }],
|
||||
phoneNum: [{ required: true, message: '请输入手机号', trigger: ['blur', 'input'] }],
|
||||
platformPassword: [{ required: true, message: '请输入平台密码', trigger: ['blur', 'input'] }]
|
||||
}
|
||||
|
||||
const identityOptions = [
|
||||
{ label: '法定代表人', value: '01' },
|
||||
{ label: '财务负责人', value: '02' },
|
||||
{ label: '办税员', value: '03' },
|
||||
{ label: '管理员', value: '05' },
|
||||
{ label: '开票员', value: '09' }
|
||||
]
|
||||
|
||||
const columns: DataTableColumns<DigitalAccountItem> = [
|
||||
{ title: '税局账号', key: 'account', minWidth: 140 },
|
||||
{ title: '平台登录账号', key: 'platformUsername', minWidth: 180, render: (row) => row.platformUsername || '-' },
|
||||
{ title: '姓名', key: 'name', minWidth: 100 },
|
||||
{ title: '登录身份', key: 'identityType', render: (row) => identityLabel(row.identityType) },
|
||||
{ title: '账号状态', key: 'authStatus', render: (row) => authStatusLabel(row.authStatus) },
|
||||
{
|
||||
title: '登录认证',
|
||||
key: 'loginAuthStatus',
|
||||
render: (row) =>
|
||||
h(
|
||||
NTag,
|
||||
{ type: row.loginAuthStatus === '1' ? 'success' : 'warning' },
|
||||
{ default: () => (row.loginAuthStatus === '1' ? '已登录' : '未登录') }
|
||||
)
|
||||
},
|
||||
{
|
||||
title: '风险认证',
|
||||
key: 'riskAuthStatus',
|
||||
render: (row) =>
|
||||
h(
|
||||
NTag,
|
||||
{ type: row.riskAuthStatus === '1' ? 'success' : 'warning' },
|
||||
{ default: () => (row.riskAuthStatus === '1' ? '已认证' : '未认证') }
|
||||
)
|
||||
},
|
||||
{ title: '最近认证', key: 'lastAuthSuccTime', minWidth: 160 },
|
||||
{
|
||||
title: 'API Key',
|
||||
key: 'apiKey',
|
||||
minWidth: 180,
|
||||
render: (row) => row.apiKey || '-'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
fixed: 'right',
|
||||
width: 220,
|
||||
render: (row) =>
|
||||
h('div', { class: 'row-actions' }, [
|
||||
h(
|
||||
NButton,
|
||||
{ size: 'small', secondary: true, onClick: () => openSms(row) },
|
||||
{ icon: () => h(NIcon, { component: KeyRound }), default: () => '短信登录' }
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{ size: 'small', tertiary: true, onClick: () => openQrcode(row) },
|
||||
{ icon: () => h(NIcon, { component: ShieldCheck }), default: () => '认证' }
|
||||
)
|
||||
])
|
||||
}
|
||||
]
|
||||
|
||||
function identityLabel(value?: string | null) {
|
||||
return identityOptions.find((item) => item.value === value)?.label || value || '-'
|
||||
}
|
||||
|
||||
function authStatusLabel(value?: string | null) {
|
||||
const map: Record<string, string> = {
|
||||
'0': '无需认证',
|
||||
'1': '风险认证',
|
||||
'2': '登录认证',
|
||||
'3': '风险+登录认证'
|
||||
}
|
||||
return value ? map[value] || value : '-'
|
||||
}
|
||||
|
||||
async function load() {
|
||||
loading.value = true
|
||||
try {
|
||||
accounts.value = await listDigitalAccountsApi()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function refreshAccounts() {
|
||||
loading.value = true
|
||||
try {
|
||||
accounts.value = await refreshDigitalAccountsApi()
|
||||
message.success('数电账号已刷新')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function createAccount() {
|
||||
await createFormRef.value?.validate()
|
||||
saving.value = true
|
||||
try {
|
||||
await createDigitalAccountApi({ ...createForm })
|
||||
message.success('数电账号已创建')
|
||||
showCreate.value = false
|
||||
await load()
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openSms(row: DigitalAccountItem) {
|
||||
selected.value = row
|
||||
smsCode.value = ''
|
||||
smsLoading.value = true
|
||||
showSms.value = true
|
||||
try {
|
||||
const res = await sendLoginSmsCodeApi({ taxpayerNum: row.taxpayerNum, account: row.account })
|
||||
smsTip.value = res.phoneNum ? `验证码已发送至 ${res.phoneNum}` : res.resultMsg
|
||||
} finally {
|
||||
smsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function submitSms() {
|
||||
if (!selected.value || !smsCode.value.trim()) return
|
||||
smsLoading.value = true
|
||||
try {
|
||||
await smsLoginApi({
|
||||
taxpayerNum: selected.value.taxpayerNum,
|
||||
account: selected.value.account,
|
||||
smsCode: smsCode.value.trim()
|
||||
})
|
||||
message.success('短信登录成功')
|
||||
showSms.value = false
|
||||
await load()
|
||||
} finally {
|
||||
smsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openQrcode(row: DigitalAccountItem) {
|
||||
selected.value = row
|
||||
showQrcode.value = true
|
||||
await loadQrcode()
|
||||
}
|
||||
|
||||
async function loadQrcode() {
|
||||
if (!selected.value) return
|
||||
qrLoading.value = true
|
||||
try {
|
||||
const res = await getAuthQrcodeApi('1', selected.value.id)
|
||||
qrcodeImg.value = res.qrcodeImg ? `data:image/png;base64,${res.qrcodeImg}` : ''
|
||||
} finally {
|
||||
qrLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.modal-actions,
|
||||
.row-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.row-actions {
|
||||
flex-wrap: nowrap;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.row-actions :deep(.n-button) {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.modal-card {
|
||||
width: min(720px, 92vw);
|
||||
}
|
||||
|
||||
.sms-card {
|
||||
width: min(420px, 92vw);
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.qrcode-wrap {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.qrcode-wrap img {
|
||||
width: 220px;
|
||||
height: 220px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user