bug
This commit is contained in:
parent
bd0e0c51a4
commit
0af7df1766
|
|
@ -5,6 +5,7 @@ export interface LoginParams {
|
|||
password: string
|
||||
login_type: number // 1-邮箱登录 2-手机登录
|
||||
code?: string // 验证码(可选)
|
||||
device_no?: string // 设备号(可选)
|
||||
}
|
||||
// 登录响应类型
|
||||
export interface LoginResponse {
|
||||
|
|
|
|||
|
|
@ -344,7 +344,7 @@ const getCustomerList = async (id: string) => {
|
|||
searchForm.value.endTime.length > 0
|
||||
? `${searchForm.value.endTime} 23:59:59`
|
||||
: '2026-03-01 23:59:59',
|
||||
status: searchForm.value.status,
|
||||
status: searchForm.value.status || '',
|
||||
}
|
||||
const data: any = await getCustomerListApi(params)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export const search = [
|
||||
// 账号名称
|
||||
{ prop: 'accountName', label: '账号名称', type: 'input' as const, placeholder: '请输入账号名称', width: '150px' },
|
||||
{ prop: 'accountName', label: '账号名称', type: 'input' as const, placeholder: '请输入账号名称', width: '240px' },
|
||||
// 开始时间
|
||||
{
|
||||
prop: "startTime", label: '注册开始时间', type: 'date' as const, dateType: 'date' as const, placeholder: '请选择注册开始时间', width: '270px', // 时间范围选择器
|
||||
|
|
|
|||
|
|
@ -83,11 +83,11 @@ const getFundDetailList = async () => {
|
|||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
start_time:
|
||||
searchForm.value.startTime.length > 0
|
||||
searchForm.value.startTime?.length > 0
|
||||
? `${searchForm.value.startTime} 0:00:00`
|
||||
: '2026-01-01 0:00:00',
|
||||
end_time:
|
||||
searchForm.value.endTime.length > 0
|
||||
searchForm.value.endTime?.length > 0
|
||||
? `${searchForm.value.endTime} 23:59:59`
|
||||
: '2026-03-15 23:59:59',
|
||||
user_name: searchForm.value.username,
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ export const search = [
|
|||
valueFormat: 'YYYY-MM-DD',
|
||||
placeholder: '请选择开始时间',
|
||||
width: '220px',
|
||||
clearable: false,
|
||||
},
|
||||
//结束时间
|
||||
{
|
||||
|
|
@ -53,6 +54,7 @@ export const search = [
|
|||
valueFormat: 'YYYY-MM-DD',
|
||||
placeholder: '请选择结束时间',
|
||||
width: '220px',
|
||||
clearable: false,
|
||||
},
|
||||
]
|
||||
export const tableColumns = [
|
||||
|
|
|
|||
|
|
@ -0,0 +1,458 @@
|
|||
<template>
|
||||
<div class="market-maker-dashboard">
|
||||
|
||||
<div class="stats-wrapper">
|
||||
<div class="stats-grid">
|
||||
<div
|
||||
v-for="(item, index) in cardData"
|
||||
:key="index"
|
||||
class="stat-box"
|
||||
:class="`stat-box--${index % 7}`"
|
||||
>
|
||||
<div class="stat-box__icon">
|
||||
<el-icon :size="28">
|
||||
<component :is="item.icon" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<div class="stat-box__info">
|
||||
<span class="stat-box__value">
|
||||
<AnimatedNumber :value="Number(item.value) || 0" :decimals="2" />
|
||||
</span>
|
||||
<span class="stat-box__label">{{ item.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="content-area">
|
||||
<div class="info-panel">
|
||||
<div class="panel-header">
|
||||
<el-icon><User /></el-icon>
|
||||
<span>当前用户</span>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<div class="user-info">
|
||||
<div class="user-avatar">
|
||||
<el-icon :size="32"><UserFilled /></el-icon>
|
||||
</div>
|
||||
<div class="user-details">
|
||||
<span class="user-name">{{ userStore.userInfo?.username || '-' }}</span>
|
||||
<el-tag size="small" :type="tagType">
|
||||
{{ roleName }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">用户ID</span>
|
||||
<span class="info-value">{{ userStore.userInfo?.id || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">最后登录</span>
|
||||
<span class="info-value">{{ userStore.userInfo?.last_login || '-' }}</span>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<span class="info-label">账户类型</span>
|
||||
<span class="info-value">{{ roleName }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="info-panel">
|
||||
<div class="panel-header">
|
||||
<el-icon><Operation /></el-icon>
|
||||
<span>快捷操作</span>
|
||||
</div>
|
||||
<div class="panel-content">
|
||||
<div class="action-list">
|
||||
<router-link
|
||||
v-for="action in quickActions"
|
||||
:key="action.name"
|
||||
:to="action.path"
|
||||
class="action-item"
|
||||
>
|
||||
<el-icon :size="18">
|
||||
<component :is="action.icon" />
|
||||
</el-icon>
|
||||
<span>{{ action.name }}</span>
|
||||
<el-icon class="arrow"><Right /></el-icon>
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { onMounted, ref, computed } from 'vue'
|
||||
import {
|
||||
UserFilled,
|
||||
User,
|
||||
Money,
|
||||
Wallet,
|
||||
Coin,
|
||||
Timer,
|
||||
Operation,
|
||||
Right,
|
||||
List,
|
||||
Goods,
|
||||
Document,
|
||||
Setting,
|
||||
TrendCharts,
|
||||
DataAnalysis,
|
||||
Trophy,
|
||||
Lock
|
||||
} from '@element-plus/icons-vue'
|
||||
import AnimatedNumber from '@/views/agent/user/AnimatedNumber.vue'
|
||||
import { getWalletCapital } from '@/api/modules/capital/withdraw'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
|
||||
const props = defineProps<{
|
||||
roleId: number
|
||||
roleName: string
|
||||
pageTitle: string
|
||||
}>()
|
||||
|
||||
defineOptions({
|
||||
name: 'MarketMakerDashboard'
|
||||
})
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
const formattedDate = computed(() => {
|
||||
const now = new Date()
|
||||
return `${now.getFullYear()}/${String(now.getMonth() + 1).padStart(2, '0')}/${String(now.getDate()).padStart(2, '0')}`
|
||||
})
|
||||
|
||||
// 标签类型(不同角色显示不同颜色)
|
||||
const tagType = computed<'danger' | 'success' | 'warning' | 'primary'>(() => {
|
||||
if (props.roleId === 2) return 'warning'
|
||||
if (props.roleId === 3) return 'danger'
|
||||
if (props.roleId === 4) return 'success'
|
||||
return 'primary'
|
||||
})
|
||||
|
||||
const cardData = ref([
|
||||
{ label: '总金额', value: '0', icon: Money },
|
||||
{ label: '盈利', value: '0', icon: Trophy },
|
||||
{ label: '手续费佣金', value: '0', icon: Coin },
|
||||
{ label: '可提现点差佣金', value: '0', icon: Wallet },
|
||||
{ label: '冻结点差佣金', value: '0', icon: Lock },
|
||||
{ label: '服务费', value: '0', icon: DataAnalysis },
|
||||
{ label: '保证金', value: '0', icon: TrendCharts }
|
||||
])
|
||||
|
||||
const quickActions = ref<{ name: string; path: string; icon: any }[]>([])
|
||||
|
||||
const iconMap: Record<string, any> = {
|
||||
user: User,
|
||||
list: List,
|
||||
goods: Goods,
|
||||
document: Document,
|
||||
setting: Setting,
|
||||
money: Money,
|
||||
trendcharts: TrendCharts,
|
||||
wallet: Wallet,
|
||||
coin: Coin,
|
||||
timer: Timer
|
||||
}
|
||||
|
||||
const initQuickActions = () => {
|
||||
const actions: { name: string; path: string; icon: any }[] = []
|
||||
const menus = userStore.menusInfo || []
|
||||
|
||||
let count = 0
|
||||
for (const menu of menus) {
|
||||
if (count >= 4) break
|
||||
|
||||
if (menu.son && menu.son.length > 0) {
|
||||
for (const child of menu.son) {
|
||||
if (count >= 4) break
|
||||
if (child.path === '/dashboard') continue
|
||||
actions.push({
|
||||
name: child.name || menu.name,
|
||||
path: child.path || '/',
|
||||
icon: iconMap[child.icon || ''] || List
|
||||
})
|
||||
count++
|
||||
}
|
||||
} else {
|
||||
if (menu.path === '/dashboard') continue
|
||||
actions.push({
|
||||
name: menu.name,
|
||||
path: menu.path || '/',
|
||||
icon: iconMap[menu.icon || ''] || List
|
||||
})
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
quickActions.value = actions.slice(0, 4)
|
||||
}
|
||||
|
||||
const fetchWalletData = async () => {
|
||||
try {
|
||||
const res: any = await getWalletCapital()
|
||||
if (res) {
|
||||
cardData.value = [
|
||||
{ label: '总金额', value: res.amount ?? 0, icon: Money },
|
||||
{ label: '盈利', value: res.closing_profit ?? 0, icon: Trophy },
|
||||
{ label: '手续费佣金', value: res.commission_fee_handling ?? 0, icon: Coin },
|
||||
{ label: '可提现点差佣金', value: ((res.commission_point_diff || 0) - (res.freeze || 0)) , icon: Wallet },
|
||||
{ label: '冻结点差佣金', value: res.freeze ?? 0, icon: Lock },
|
||||
{ label: '服务费', value: res.service ?? 0, icon: DataAnalysis },
|
||||
{ label: '保证金', value: res.bail ?? 0, icon: TrendCharts }
|
||||
]
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取钱包资金失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
initQuickActions()
|
||||
fetchWalletData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.market-maker-dashboard {
|
||||
padding: 20px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.page-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 24px;
|
||||
padding-bottom: 16px;
|
||||
border-bottom: 1px solid var(--el-border-color-light);
|
||||
|
||||
h1 {
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.date {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.stats-wrapper {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.stats-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 20px;
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding: 20px 24px;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
transition: all 0.25s ease;
|
||||
|
||||
&:hover {
|
||||
border-color: var(--el-color-primary-light-5);
|
||||
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.06);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
&__icon {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 12px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
&__info {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&__value {
|
||||
font-size: 24px;
|
||||
font-weight: 700;
|
||||
color: var(--el-text-color-primary);
|
||||
line-height: 1.2;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
&__label {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-secondary);
|
||||
margin-top: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.stat-box--0 .stat-box__icon { background: linear-gradient(135deg, #ecf5ff, #d9ecff); color: #409eff; }
|
||||
.stat-box--1 .stat-box__icon { background: linear-gradient(135deg, #f0f9eb, #e1f3d8); color: #67c23a; }
|
||||
.stat-box--2 .stat-box__icon { background: linear-gradient(135deg, #fdf6ec, #fbeccb); color: #e6a23c; }
|
||||
.stat-box--3 .stat-box__icon { background: linear-gradient(135deg, #fef0f0, #fad7d7); color: #f56c6c; }
|
||||
.stat-box--4 .stat-box__icon { background: linear-gradient(135deg, #f4f4f5, #e4e4e7); color: #909399; }
|
||||
.stat-box--5 .stat-box__icon { background: linear-gradient(135deg, #fdf5f6, #f9d7e3); color: #c71585; }
|
||||
.stat-box--6 .stat-box__icon { background: linear-gradient(135deg, #f0f9ff, #d6ecff); color: #0089e5; }
|
||||
|
||||
.content-area {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 20px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.info-panel {
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
border: 1px solid var(--el-border-color-lighter);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 16px 20px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-bottom: 1px solid var(--el-border-color-lighter);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
|
||||
.el-icon {
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.panel-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 16px;
|
||||
border-bottom: 1px dashed var(--el-border-color-lighter);
|
||||
|
||||
.user-avatar {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: linear-gradient(135deg, var(--el-color-primary-light-9), var(--el-color-primary-light-7));
|
||||
border-radius: 50%;
|
||||
color: var(--el-color-primary);
|
||||
}
|
||||
|
||||
.user-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
|
||||
.user-name {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px dashed var(--el-border-color-lighter);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
|
||||
.info-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: var(--el-text-color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.action-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
color: var(--el-text-color-regular);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
background: var(--el-color-primary-light-9);
|
||||
color: var(--el-color-primary);
|
||||
|
||||
.arrow {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.el-icon {
|
||||
color: var(--el-color-primary);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.arrow {
|
||||
margin-left: auto;
|
||||
font-size: 12px;
|
||||
opacity: 0.4;
|
||||
transition: opacity 0.2s ease;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -81,6 +81,13 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 承包商首页 -->
|
||||
<MarketMakerDashboard
|
||||
v-else-if="userRoleId === 2 || userRoleId === 3 ||userRoleId === 4"
|
||||
:role-id="userRoleId"
|
||||
role-name="承包商"
|
||||
page-title="承包商首页"
|
||||
/>
|
||||
<Agent v-else />
|
||||
</template>
|
||||
|
||||
|
|
@ -88,6 +95,7 @@
|
|||
import { onMounted, ref, computed } from 'vue'
|
||||
import { UserFilled, User, TrendCharts, Money, Wallet, Coin, Timer, Operation, Right, List, Goods, Document, Setting } from '@element-plus/icons-vue'
|
||||
import Agent from './components/view/index.vue'
|
||||
import MarketMakerDashboard from './components/marketMaker/index.vue'
|
||||
import AnimatedNumber from '@/views/agent/user/AnimatedNumber.vue'
|
||||
import { getUserIndex } from '@/api/modules/dashboard'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
|
|
@ -98,6 +106,7 @@ defineOptions({
|
|||
|
||||
const userStore = useUserStore()
|
||||
const userId = userStore.userInfo?.id
|
||||
const userRoleId = userStore.userInfo?.role_id
|
||||
|
||||
const loginTime = ref('--')
|
||||
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ export const search = [
|
|||
dateType: 'datetimerange' as const,
|
||||
placeholder: '请选择时间范围',
|
||||
format: 'YYYY-MM-DD HH:mm:ss',
|
||||
clearable: false,
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
startPlaceholder: '开始时间',
|
||||
endPlaceholder: '结束时间',
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export const search = [
|
|||
dateType: 'date' as const,
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
placeholder: '请选择开始时间',
|
||||
clearable: false,
|
||||
width: '260px',
|
||||
},
|
||||
{
|
||||
|
|
@ -43,6 +44,7 @@ export const search = [
|
|||
dateType: 'date' as const,
|
||||
valueFormat: 'YYYY-MM-DD',
|
||||
placeholder: '请选择结束时间',
|
||||
clearable: false,
|
||||
width: '260px',
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export const search = [
|
|||
dateType: 'datetime' as const,
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
placeholder: '请选择开始时间',
|
||||
clearable: false,
|
||||
width: '200',
|
||||
},
|
||||
{
|
||||
|
|
@ -23,6 +24,7 @@ export const search = [
|
|||
dateType: 'datetime' as const,
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
placeholder: '请选择结束时间',
|
||||
clearable: false,
|
||||
width: '200',
|
||||
},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -347,7 +347,7 @@ const handleSendCaptcha = async () => {
|
|||
await sendCaptcha(type, receiver, currentState, () => {
|
||||
const params: any = {
|
||||
reg_type: userStore.userInfo!.reg_type,
|
||||
type: type === 'email' ? 'transfer_code' : 'bindMobile_code'
|
||||
type: type === 'email' ? 'bindEmail_code' : 'bindMobile_code'
|
||||
};
|
||||
if (type === 'email') {
|
||||
params.email = receiver;
|
||||
|
|
|
|||
|
|
@ -19,12 +19,12 @@
|
|||
<el-button type="primary" text size="small" @click="openDetailDrawer(row)" v-if="row.id">
|
||||
查看详情
|
||||
</el-button>
|
||||
<el-divider direction="vertical" v-if="row.id" />
|
||||
<el-button type="primary" text size="small" @click="handleChange(row)" v-anth="'user_editUser'">
|
||||
<el-divider direction="vertical" v-if="row.id" v-auth="'user_editUser'"/>
|
||||
<el-button type="primary" text size="small" @click="handleChange(row)" v-auth="'user_editUser'">
|
||||
修改
|
||||
</el-button>
|
||||
<el-divider direction="vertical" v-if="row.id" />
|
||||
<el-button type="warning" text size="small" @click="handleAdjustAmount(row)" v-anth="'user_updateCapital'">
|
||||
<el-divider direction="vertical" v-if="row.id" v-auth="'user_updateCapital'"/>
|
||||
<el-button type="warning" text size="small" @click="handleAdjustAmount(row)" v-auth="'user_updateCapital'">
|
||||
调整金额
|
||||
</el-button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -37,14 +37,14 @@
|
|||
<el-icon><Postcard /></el-icon>
|
||||
<span>身份证</span>
|
||||
</div>
|
||||
<div
|
||||
<!-- <div
|
||||
class="type-item"
|
||||
:class="{ active: form.idType === 'passport' }"
|
||||
@click="switchType('passport')"
|
||||
>
|
||||
<el-icon><Ticket /></el-icon>
|
||||
<span>护照</span>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
<!-- 拍摄提示 -->
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export const search = [
|
|||
{ label: '已发布', value: 2 },
|
||||
],
|
||||
width: '220px',
|
||||
clearable: false,
|
||||
},
|
||||
]
|
||||
|
||||
|
|
|
|||
|
|
@ -73,21 +73,31 @@
|
|||
import { Monitor, Download, Link } from '@element-plus/icons-vue'
|
||||
defineOptions({ name: 'Exe' })
|
||||
|
||||
const downloadUrlMap: Record<string, string> = {
|
||||
type DownloadType = 'pc' | 'android' | 'ios'
|
||||
|
||||
const downloadUrlMap: Record<DownloadType, string> = {
|
||||
pc: 'https://adminh5.apimcapital.top/pc/Apim Capital-1.0.24.exe',
|
||||
android: 'https://adminh5.apimcapital.top/app/xxxx.apk',
|
||||
android: 'https://adminh5.apimcapital.top/app/app-production-release.apk',
|
||||
ios: 'https://adminh5.apimcapital.top/app/xxxx.apk'
|
||||
}
|
||||
|
||||
const handleDownload = (type: 'pc' | 'android' | 'ios') => {
|
||||
const url = downloadUrlMap[type]
|
||||
const handleDownload = (type: DownloadType) => {
|
||||
const url: string = downloadUrlMap[type]
|
||||
// 使用 window.open 在新页签打开下载链接,
|
||||
// 避免在当前页点击 a.download 时被 SPA 路由拦截跳转到登录页
|
||||
const newWindow = window.open(url, '_blank', 'noopener,noreferrer')
|
||||
// 某些浏览器会拦截弹窗,做一次降级处理
|
||||
if (!newWindow) {
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.target = '_blank'
|
||||
a.rel = 'noopener noreferrer'
|
||||
if (type === 'pc') a.download = 'Apim Capital-1.0.24.exe'
|
||||
if (type === 'android') a.download = 'ApimCapital.apk'
|
||||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
|||
|
|
@ -36,6 +36,18 @@
|
|||
<el-input v-model="loginForm.password" placeholder="登录密码" type="password" show-password size="large" @keyup.enter.prevent="handleLogin" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="agreeAgreement">
|
||||
<div style="display: flex;align-items: center; justify-content: flex-start;">
|
||||
<el-checkbox v-model="loginForm.agreeAgreement"></el-checkbox>
|
||||
<div style="font-size: 13px;flex-wrap: wrap;margin-left: 8px;line-height: 17px;">
|
||||
我已阅读并同意
|
||||
<span class="doc-link" @click="openDoc('服务条款.docx', '服务条款')">《服务条款》</span>、
|
||||
<span class="doc-link" @click="openDoc('隐私协议.docx', '隐私协议')">《隐私协议》</span>和
|
||||
<span class="doc-link" @click="openDoc('风险提示.docx', '风险提示')">《风险提示》</span>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-button type="primary" class="submit-btn" @click="handleLogin" :loading="buttonLoading">登 录</el-button>
|
||||
|
||||
<div class="link-row">
|
||||
|
|
@ -45,16 +57,33 @@
|
|||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 协议预览弹窗 -->
|
||||
<el-dialog v-model="dialogVisible" :title="dialogTitle" width="70%" top="3vh">
|
||||
<div class="doc-preview" v-html="docHtml"></div>
|
||||
<template #footer>
|
||||
<div class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||
<el-button type="primary" @click="downloadCurrentDoc">下载文档</el-button>
|
||||
</div>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import mammoth from 'mammoth'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useUserStore } from '@/stores/modules/user'
|
||||
import { useButtonLoading } from '@/composables/useLoading'
|
||||
import { setStorage, getStorage } from '@/utils/storage'
|
||||
import { countryOptions } from '@/views/user/config/mock'
|
||||
import { passwordRules, emailRules, phoneRules } from './config/rules'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import termFile from '@/assets/file/服务条款.docx?url'
|
||||
import privacyFile from '@/assets/file/隐私协议.docx?url'
|
||||
import riskFile from '@/assets/file/风险提示.docx?url'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
|
||||
const router = useRouter()
|
||||
const { buttonLoading, startButtonLoading, stopButtonLoading } = useButtonLoading()
|
||||
|
|
@ -65,18 +94,69 @@ const tabList = ref([
|
|||
{ label: '手机登录', name: 'phone' },
|
||||
])
|
||||
const activeTab = ref<string>('email')
|
||||
const loginFormRef = ref()
|
||||
const loginFormRef = ref<FormInstance>()
|
||||
const loginForm = ref({
|
||||
email: '',
|
||||
countryCode: '+86',
|
||||
phone: '',
|
||||
password: '',
|
||||
agreeAgreement: false,
|
||||
})
|
||||
|
||||
const rules = {
|
||||
const rules: FormRules = {
|
||||
email: emailRules().email,
|
||||
phone: phoneRules(loginForm.value).phone,
|
||||
password: passwordRules(loginForm.value).password,
|
||||
agreeAgreement: [
|
||||
{
|
||||
validator: (rule: any, value: any, callback: any) => {
|
||||
if (!value) {
|
||||
callback(new Error('请勾选同意登录协议'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
},
|
||||
trigger: 'change',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
// 弹窗相关
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
const docHtml = ref('')
|
||||
const currentDocName = ref('')
|
||||
|
||||
const fileMap: Record<string, string> = {
|
||||
'服务条款.docx': termFile,
|
||||
'隐私协议.docx': privacyFile,
|
||||
'风险提示.docx': riskFile,
|
||||
}
|
||||
|
||||
// 打开并解析docx弹窗预览
|
||||
async function openDoc(fileName: string, title: string) {
|
||||
currentDocName.value = fileName
|
||||
dialogTitle.value = title
|
||||
dialogVisible.value = true
|
||||
docHtml.value = '加载中...'
|
||||
const url = fileMap[fileName]
|
||||
try {
|
||||
const res = await fetch(url)
|
||||
const buf = await res.arrayBuffer()
|
||||
const ret = await mammoth.convertToHtml({ arrayBuffer: buf })
|
||||
docHtml.value = ret.value
|
||||
} catch {
|
||||
docHtml.value = '加载失败,请下载查看'
|
||||
}
|
||||
}
|
||||
|
||||
// 下载当前打开的文档
|
||||
function downloadCurrentDoc() {
|
||||
const url = fileMap[currentDocName.value]
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.download = currentDocName.value
|
||||
a.click()
|
||||
}
|
||||
|
||||
function generateDeviceNo() {
|
||||
|
|
@ -86,6 +166,12 @@ function generateDeviceNo() {
|
|||
async function handleLogin() {
|
||||
if (!loginFormRef.value) return
|
||||
|
||||
// 先检查协议勾选状态
|
||||
if (!loginForm.value.agreeAgreement) {
|
||||
ElMessage.error('请勾选同意登录协议')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
await loginFormRef.value.validate()
|
||||
startButtonLoading()
|
||||
|
|
@ -97,7 +183,7 @@ async function handleLogin() {
|
|||
mobile: loginForm.value.countryCode + loginForm.value.phone,
|
||||
login_type: activeTab.value === 'email' ? 1 : 2,
|
||||
password: loginForm.value.password,
|
||||
device_no: deviceNo+'',
|
||||
device_no: deviceNo + '',
|
||||
})
|
||||
setStorage('device_no', deviceNo)
|
||||
} finally {
|
||||
|
|
@ -332,4 +418,50 @@ function handleForgotPassword() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--el-color-primary);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.doc-link {
|
||||
color: var(--el-color-primary);
|
||||
cursor: pointer;
|
||||
margin: 0 4px;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
/* 弹窗预览全局样式,不要scoped */
|
||||
.doc-preview {
|
||||
max-height: 65vh;
|
||||
overflow-y: auto;
|
||||
line-height: 1.8;
|
||||
font-size: 15px;
|
||||
padding: 10px;
|
||||
|
||||
h1, h2, h3, h4 {
|
||||
margin: 16px 0 8px;
|
||||
}
|
||||
|
||||
p {
|
||||
margin: 8px 0;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
margin: 12px 0;
|
||||
|
||||
td, th {
|
||||
border: 1px solid #ccc;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ const fileMap: Record<string, string> = {
|
|||
// 打开并解析docx弹窗预览
|
||||
|
||||
async function openDoc(fileName: string, title: string) {
|
||||
currentDocName.value = fileName
|
||||
dialogTitle.value = title
|
||||
dialogVisible.value = true
|
||||
docHtml.value = '加载中...'
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ export const searchOptions = [
|
|||
placeholder: '开始时间',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
format: 'YYYY-MM-DD',
|
||||
clearable: false,
|
||||
width: '220px',
|
||||
defaultTime: 'start' as const,
|
||||
},
|
||||
|
|
@ -61,6 +62,7 @@ export const searchOptions = [
|
|||
label: '结束时间',
|
||||
type: 'date' as const,
|
||||
dateType: 'datetime' as const,
|
||||
clearable: false,
|
||||
placeholder: '结束时间',
|
||||
valueFormat: 'YYYY-MM-DD HH:mm:ss',
|
||||
format: 'YYYY-MM-DD',
|
||||
|
|
|
|||
Loading…
Reference in New Issue