大改三角色逻辑:超级管理员、企业管理员、开票员

This commit is contained in:
BBIT-Kai
2026-05-27 09:10:54 +08:00
parent 59fa2beb03
commit 9fd80980e7
67 changed files with 1141 additions and 2230 deletions
@@ -74,10 +74,23 @@
</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="请输入短信验证码" />
<n-modal
v-model:show="showSms"
preset="card"
title="短信登录"
:style="{ width: '360px', maxWidth: '88vw' }"
content-style="padding: 14px 18px 18px"
>
<n-space vertical size="small" class="auth-panel">
<div class="auth-account">
<span>税局账号</span>
<strong>{{ selected?.account || '-' }}</strong>
</div>
<n-alert v-if="smsTip" type="info" :show-icon="false">{{ smsTip }}</n-alert>
<n-input-group>
<n-input v-model:value="smsCode" placeholder="请输入短信验证码" />
<n-button :loading="smsCodeLoading" @click="sendSmsCode">发送验证码</n-button>
</n-input-group>
<div class="modal-actions">
<n-button @click="showSms = false">取消</n-button>
<n-button type="primary" :loading="smsLoading" @click="submitSms">确认登录</n-button>
@@ -85,12 +98,36 @@
</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
v-model:show="showQrcode"
preset="card"
title="风险认证"
:style="{ width: '400px', maxWidth: '88vw' }"
content-style="padding: 14px 18px 18px"
>
<n-space vertical size="small" class="auth-panel">
<div class="auth-account">
<span>税局账号</span>
<strong>{{ selected?.account || '-' }}</strong>
</div>
<n-radio-group v-model:value="qrcodeType" size="small" @update:value="loadQrcode">
<n-radio-button value="1">电子税务局 APP</n-radio-button>
<n-radio-button value="2">国家网络身份认证 APP</n-radio-button>
</n-radio-group>
<div class="qrcode-wrap">
<img v-if="qrcodeImg" :src="qrcodeImg" :alt="qrcodeTypeLabel" />
<n-empty v-else description="暂无二维码" />
</div>
<div class="qrcode-meta">
<span>{{ qrcodeTypeLabel }}</span>
<small v-if="qrcodeGenTime">生成时间:{{ qrcodeGenTime }}</small>
<small v-if="qrcodeTip">{{ qrcodeTip }}</small>
</div>
<div class="modal-actions">
<n-button @click="showQrcode = false">关闭</n-button>
<n-button type="primary" :loading="qrLoading" @click="loadQrcode">刷新二维码</n-button>
</div>
</n-space>
</n-modal>
</div>
</template>
@@ -109,11 +146,15 @@ import {
NGrid,
NIcon,
NInput,
NInputGroup,
NModal,
NPopconfirm,
NRadioButton,
NRadioGroup,
NSelect,
NSpace,
NTag,
NTooltip,
useMessage
} from 'naive-ui'
import { KeyRound, Plus, RefreshCw, ShieldCheck } from 'lucide-vue-next'
@@ -138,15 +179,22 @@ const showCreate = ref(false)
const showSms = ref(false)
const showQrcode = ref(false)
const smsLoading = ref(false)
const smsCodeLoading = ref(false)
const qrLoading = ref(false)
const smsCode = ref('')
const smsTip = ref('')
const qrcodeImg = ref('')
const qrcodeType = ref('1')
const qrcodeGenTime = ref('')
const qrcodeTip = ref('')
const selected = ref<DigitalAccountItem | null>(null)
const createFormRef = ref<FormInst | null>(null)
const canManage = computed(() => authStore.user?.userType !== 'DIGITAL_OPERATOR')
const canUpdateStatus = computed(() => authStore.hasPermission('digital-account:status'))
const qrcodeTypeLabel = computed(() =>
qrcodeType.value === '2' ? '国家网络身份认证 APP' : '电子税务局 APP'
)
const createForm = reactive({
account: '',
@@ -175,7 +223,13 @@ const identityOptions = [
const columns: DataTableColumns<DigitalAccountItem> = [
{ title: '税局账号', key: 'account', minWidth: 140 },
{ title: '平台登录账号', key: 'platformUsername', minWidth: 180, render: (row) => row.platformUsername || '-' },
{
title: '平台登录账号',
key: 'platformUsername',
width: 180,
ellipsis: true,
render: (row) => renderEllipsisText(row.platformUsername)
},
{ title: '姓名', key: 'name', minWidth: 100 },
{ title: '登录身份', key: 'identityType', render: (row) => identityLabel(row.identityType) },
{ title: '账号状态', key: 'authStatus', render: (row) => authStatusLabel(row.authStatus) },
@@ -214,8 +268,9 @@ const columns: DataTableColumns<DigitalAccountItem> = [
{
title: 'API Key',
key: 'apiKey',
minWidth: 180,
render: (row) => row.apiKey || '-'
width: 180,
ellipsis: true,
render: (row) => renderEllipsisText(row.apiKey)
},
{
title: '操作',
@@ -282,6 +337,18 @@ function authStatusLabel(value?: string | null) {
return value ? map[value] || value : '-'
}
function renderEllipsisText(value?: string | null) {
if (!value) return '-'
return h(
NTooltip,
{ trigger: 'hover' },
{
trigger: () => h('span', { class: 'cell-ellipsis' }, value),
default: () => value
}
)
}
async function load() {
loading.value = true
try {
@@ -324,13 +391,21 @@ async function updateAccountStatus(row: DigitalAccountItem) {
async function openSms(row: DigitalAccountItem) {
selected.value = row
smsCode.value = ''
smsLoading.value = true
smsTip.value = ''
showSms.value = true
}
async function sendSmsCode() {
if (!selected.value) return
smsCodeLoading.value = true
try {
const res = await sendLoginSmsCodeApi({ taxpayerNum: row.taxpayerNum, account: row.account })
const res = await sendLoginSmsCodeApi({
taxpayerNum: selected.value.taxpayerNum,
account: selected.value.account
})
smsTip.value = res.phoneNum ? `验证码已发送至 ${res.phoneNum}` : res.resultMsg
} finally {
smsLoading.value = false
smsCodeLoading.value = false
}
}
@@ -353,6 +428,10 @@ async function submitSms() {
async function openQrcode(row: DigitalAccountItem) {
selected.value = row
qrcodeType.value = '1'
qrcodeImg.value = ''
qrcodeGenTime.value = ''
qrcodeTip.value = ''
showQrcode.value = true
await loadQrcode()
}
@@ -361,8 +440,11 @@ async function loadQrcode() {
if (!selected.value) return
qrLoading.value = true
try {
const res = await getAuthQrcodeApi('1', selected.value.id)
const res = await getAuthQrcodeApi(qrcodeType.value, selected.value.id)
qrcodeImg.value = res.qrcodeImg ? `data:image/png;base64,${res.qrcodeImg}` : ''
qrcodeType.value = res.qrcodeType || qrcodeType.value
qrcodeGenTime.value = res.genTime || ''
qrcodeTip.value = res.resultMsg || ''
} finally {
qrLoading.value = false
}
@@ -388,27 +470,81 @@ onMounted(load)
flex-shrink: 0;
}
.cell-ellipsis {
display: inline-block;
width: 148px;
max-width: 148px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: bottom;
}
.modal-card {
width: min(720px, 92vw);
}
.sms-card {
width: min(420px, 92vw);
}
.modal-actions {
justify-content: flex-end;
margin-top: 10px;
padding-top: 12px;
border-top: 1px solid #eef1f5;
}
.auth-panel {
width: 100%;
}
.auth-account {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
padding: 8px 10px;
border: 1px solid #eef1f5;
border-radius: 8px;
background: #fafafa;
}
.auth-account span {
color: #6b7280;
font-size: 12px;
}
.auth-account strong {
color: #111827;
font-size: 13px;
font-weight: 600;
word-break: break-all;
}
.qrcode-wrap {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
justify-content: center;
min-height: 236px;
border: 1px solid #eef1f5;
border-radius: 8px;
background: #fff;
}
.qrcode-wrap img {
width: 220px;
height: 220px;
}
.qrcode-meta {
display: flex;
flex-direction: column;
gap: 2px;
color: #6b7280;
font-size: 12px;
text-align: center;
}
.qrcode-meta span {
color: #111827;
font-weight: 600;
}
</style>