Compare commits
10 Commits
50732616d0
...
2e09a787fd
| Author | SHA1 | Date |
|---|---|---|
|
|
2e09a787fd | |
|
|
e9fbb956e4 | |
|
|
3c7c5ae69e | |
|
|
cf6de1d1d7 | |
|
|
8d83be7cb3 | |
|
|
22eb76a440 | |
|
|
e14dd66e2b | |
|
|
7010ed1238 | |
|
|
cc502b2ac6 | |
|
|
de39f3a54f |
|
|
@ -1,14 +1,17 @@
|
|||
# 开发环境标识(Vite 内置变量,无需修改)
|
||||
NODE_ENV = development
|
||||
# 测试环境标识
|
||||
NODE_ENV = test
|
||||
|
||||
# 开发环境接口基础地址(对应你配置的 https://adminapi.apimcapital.top/api)
|
||||
VITE_API_BASE_URL = https://adminapi.apimcapital.top/api
|
||||
# 测试环境接口基础地址
|
||||
VITE_API_BASE_URL = https://adminapi.duxiang.wiki/api
|
||||
|
||||
# 请求超时时间(毫秒)
|
||||
VITE_REQUEST_TIMEOUT = 10000
|
||||
|
||||
# 可选:开发环境其他配置(如是否开启调试)
|
||||
# 可选:测试环境其他配置
|
||||
VITE_DEBUG = true
|
||||
|
||||
# 交易mqtt
|
||||
VITE_MQTT_TRANSACTION_HOST=wss://adminapi.apimcapital.top/mqtt
|
||||
VITE_MQTT_TRANSACTION_HOST = wss://adminapi.duxiang.wiki/mqtt
|
||||
|
||||
# exe 下载基础地址
|
||||
VITE_EXE_DOWNLOAD_BASE_URL = https://adminh5.duxiang.wiki
|
||||
|
|
|
|||
|
|
@ -11,4 +11,7 @@ VITE_REQUEST_TIMEOUT = 10000
|
|||
VITE_DEBUG = false
|
||||
|
||||
# 交易mqtt
|
||||
VITE_MQTT_TRANSACTION_HOST=wss://adminapi.apimcapital.top/mqtt
|
||||
VITE_MQTT_TRANSACTION_HOST=wss://adminapi.apimcapital.top/mqtt
|
||||
|
||||
# exe 下载基础地址
|
||||
VITE_EXE_DOWNLOAD_BASE_URL = https://adminh5.apimcapital.top
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# 测试环境标识
|
||||
NODE_ENV = test
|
||||
|
||||
# 测试环境接口基础地址
|
||||
VITE_API_BASE_URL = https://adminapi.duxiang.wiki/api
|
||||
|
||||
# 请求超时时间(毫秒)
|
||||
VITE_REQUEST_TIMEOUT = 10000
|
||||
|
||||
# 可选:测试环境其他配置
|
||||
VITE_DEBUG = true
|
||||
|
||||
# 交易mqtt
|
||||
VITE_MQTT_TRANSACTION_HOST = wss://adminapi.duxiang.wiki/mqtt
|
||||
|
||||
# exe 下载基础地址
|
||||
VITE_EXE_DOWNLOAD_BASE_URL = https://adminh5.duxiang.wiki
|
||||
|
|
@ -5,8 +5,10 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"dev:test": "vite --mode test",
|
||||
"build": "run-p type-check \"build-only {@}\" --",
|
||||
"build:prod": "vite build --mode production",
|
||||
"build:test": "vite build --mode test",
|
||||
"preview": "vite preview",
|
||||
"build-only": "vite build",
|
||||
"type-check": "vue-tsc --build",
|
||||
|
|
|
|||
|
|
@ -279,15 +279,26 @@ const emit = defineEmits<{
|
|||
(e: 'filter-change', filters: { [key: string]: any }): void
|
||||
}>()
|
||||
|
||||
// 获取路由和用户 Store
|
||||
const route = useRoute()
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 缓存 key 版本号(当列配置结构升级时可手动改这个值,使旧缓存失效)
|
||||
const CACHE_VERSION = 'v1'
|
||||
|
||||
// 生成缓存 key
|
||||
const getCacheKey = () => {
|
||||
const baseKey = props.columnSettingStorageKey || String(route.name || route.path)
|
||||
const userId = String(userStore.userInfo?.id || 'guest')
|
||||
return `table-column-setting-${baseKey}-${userId}`
|
||||
const baseKey = props.columnSettingStorageKey
|
||||
|| String((route.name as string) || route.path || 'default')
|
||||
const userId = String((userStore.userInfo as any)?.id || 'guest')
|
||||
return `table-column-setting-${CACHE_VERSION}-${baseKey}-${userId}`
|
||||
}
|
||||
|
||||
// 获取列的唯一标识(用于匹配缓存中的列)
|
||||
const getColumnKey = (col: TableColumn): string => {
|
||||
if (col.prop) return `prop:${col.prop}`
|
||||
if (col.slotName) return `slot:${col.slotName}`
|
||||
if (col.type) return `type:${col.type}`
|
||||
return `label:${col.label}`
|
||||
}
|
||||
|
||||
// 气泡确认框显示状态
|
||||
|
|
@ -306,63 +317,59 @@ const finalColumns = computed(() => {
|
|||
})
|
||||
|
||||
// 初始化列配置(从缓存或默认值)
|
||||
// 逻辑:原始 props.columns 为准,缓存只控制“可见性”和“顺序”。
|
||||
// - 原始列中与缓存匹配的项 → 应用缓存的 visible / sortOrder
|
||||
// - 原始列中新增的项 → 追加到末尾,visible 默认 true
|
||||
// - 缓存中存在但原始列已删除的项 → 丢弃
|
||||
const initColumns = () => {
|
||||
if (!props.columns || props.columns.length === 0) {
|
||||
localColumns.value = []
|
||||
return
|
||||
}
|
||||
|
||||
// 读取缓存(可能为空)
|
||||
const cacheKey = getCacheKey()
|
||||
let cachedConfig: any[] = []
|
||||
const cached = localStorage.getItem(cacheKey)
|
||||
|
||||
if (cached) {
|
||||
try {
|
||||
const cachedConfig = JSON.parse(cached)
|
||||
// 按 sortOrder 排序
|
||||
cachedConfig.sort((a: any, b: any) => (a.sortOrder || 0) - (b.sortOrder || 0))
|
||||
|
||||
// 合并缓存和原始配置,保持缓存的顺序
|
||||
localColumns.value = cachedConfig.map((cached: any) => {
|
||||
// 优先按 prop 匹配
|
||||
const originalCol = props.columns?.find((c) => c.prop === cached.prop)
|
||||
if (originalCol) {
|
||||
return { ...originalCol, ...cached }
|
||||
}
|
||||
// 对于没有 prop 的列(如操作列),按 slotName 匹配
|
||||
if (cached.slotName) {
|
||||
const slotCol = props.columns?.find((c) => c.slotName === cached.slotName)
|
||||
if (slotCol) {
|
||||
return { ...slotCol, ...cached }
|
||||
}
|
||||
}
|
||||
return null
|
||||
}).filter(Boolean)
|
||||
const parsed = JSON.parse(cached)
|
||||
if (Array.isArray(parsed)) cachedConfig = parsed
|
||||
} catch {
|
||||
// 解析失败,使用默认配置
|
||||
localColumns.value = props.columns.map((col) => ({
|
||||
...col,
|
||||
visible: col.visible !== false,
|
||||
fixed: col.fixed || false,
|
||||
}))
|
||||
cachedConfig = []
|
||||
}
|
||||
} else {
|
||||
// 无缓存,使用默认配置
|
||||
localColumns.value = props.columns.map((col) => ({
|
||||
...col,
|
||||
visible: col.visible !== false,
|
||||
fixed: col.fixed || false,
|
||||
}))
|
||||
}
|
||||
|
||||
// 构建缓存映射:key -> { visible, sortOrder }
|
||||
const cachedMap = new Map<string, { visible: boolean; sortOrder: number }>()
|
||||
cachedConfig.forEach((c, idx) => {
|
||||
cachedMap.set(c.key || getColumnKey(c), {
|
||||
visible: c.visible !== false,
|
||||
sortOrder: typeof c.sortOrder === 'number' ? c.sortOrder : idx,
|
||||
})
|
||||
})
|
||||
|
||||
// 以原始列为主,命中缓存则用缓存的 visible / sortOrder,否则追加到末尾
|
||||
const maxOrder = Math.max(-1, ...Array.from(cachedMap.values()).map((v) => v.sortOrder))
|
||||
let nextOrder = maxOrder + 1
|
||||
|
||||
localColumns.value = props.columns.map((col) => {
|
||||
const hit = cachedMap.get(getColumnKey(col))
|
||||
if (hit) {
|
||||
return { ...col, visible: hit.visible, sortOrder: hit.sortOrder }
|
||||
}
|
||||
return { ...col, visible: col.visible !== false, sortOrder: nextOrder++ }
|
||||
})
|
||||
|
||||
// 按 sortOrder 排序
|
||||
localColumns.value.sort((a, b) => (a.sortOrder ?? 0) - (b.sortOrder ?? 0))
|
||||
}
|
||||
|
||||
// 保存配置到 localStorage
|
||||
// 保存配置到 localStorage(仅缓存 key / visible / sortOrder)
|
||||
const saveToStorage = () => {
|
||||
const config = localColumns.value.map((col, index) => ({
|
||||
prop: col.prop,
|
||||
visible: col.visible,
|
||||
label: col.label,
|
||||
width: col.width,
|
||||
slotName: col.slotName,
|
||||
key: getColumnKey(col),
|
||||
visible: col.visible !== false,
|
||||
sortOrder: index,
|
||||
}))
|
||||
localStorage.setItem(getCacheKey(), JSON.stringify(config))
|
||||
|
|
@ -390,48 +397,33 @@ const handleReset = () => {
|
|||
}
|
||||
|
||||
// 监听 columns 变化
|
||||
// 注意:当原始 columns 变化时(如父组件新增/删除了列配置),
|
||||
// 需要重新合并缓存,否则新字段不会展示。
|
||||
let lastColumnsKey = ''
|
||||
watch(
|
||||
() => props.columns,
|
||||
(newColumns) => {
|
||||
if (newColumns && newColumns.length > 0) {
|
||||
// 只有在本地列为空时才初始化(不要覆盖已加载的缓存)
|
||||
if (localColumns.value.length === 0) {
|
||||
initColumns()
|
||||
}
|
||||
}
|
||||
if (!newColumns || newColumns.length === 0) return
|
||||
// 通过长度+关键字段生成 key,避免不必要的重新初始化
|
||||
const newKey = newColumns.map((c) => getColumnKey(c)).join('|')
|
||||
if (newKey === lastColumnsKey && localColumns.value.length > 0) return
|
||||
lastColumnsKey = newKey
|
||||
initColumns()
|
||||
},
|
||||
{ immediate: true }
|
||||
{ immediate: true, deep: true }
|
||||
)
|
||||
|
||||
// 组件挂载时初始化列配置
|
||||
// 组件挂载时,如果列还没初始化,再次尝试
|
||||
onMounted(() => {
|
||||
// 延迟初始化,确保 props.columns 已准备好
|
||||
setTimeout(() => {
|
||||
if (props.columns && props.columns.length > 0 && localColumns.value.length === 0) {
|
||||
initColumns()
|
||||
}
|
||||
}, 100)
|
||||
if (props.columns && props.columns.length > 0 && localColumns.value.length === 0) {
|
||||
initColumns()
|
||||
}
|
||||
})
|
||||
|
||||
// 监听 tableData 变化,清除单选选中状态
|
||||
watch(
|
||||
() => props.tableData,
|
||||
() => {
|
||||
if (props.singleSelect) {
|
||||
selectedRadioRow.value = null
|
||||
}
|
||||
}
|
||||
)
|
||||
const tableRef = ref<TableInstance>()
|
||||
|
||||
// 监听 tableData 变化,清除单选选中状态
|
||||
watch(
|
||||
() => props.tableData,
|
||||
() => {
|
||||
if (props.singleSelect) {
|
||||
selectedRadioRow.value = null
|
||||
}
|
||||
}
|
||||
)
|
||||
// 维护当前选中行状态
|
||||
const selectedRow = ref<any>(null)
|
||||
|
||||
// 处理勾选变化
|
||||
const handleSelectionChange = (rows: any[]) => {
|
||||
|
|
@ -444,11 +436,6 @@ const handleCurrentChange = (row: any) => {
|
|||
emit('current-change', row)
|
||||
}
|
||||
|
||||
const tableRef = ref<TableInstance>()
|
||||
|
||||
// 维护当前选中行状态
|
||||
const selectedRow = ref<any>(null)
|
||||
|
||||
// 处理行点击事件
|
||||
const handleRowClick = (row: any) => {
|
||||
if (!row) return
|
||||
|
|
@ -467,23 +454,17 @@ const handleRowContextmenu = (row: any, column: any, event: MouseEvent) => {
|
|||
emit('row-contextmenu', row, column, event)
|
||||
}
|
||||
|
||||
// 监听 tableData 变化,清除单选选中状态
|
||||
// 监听 tableData 变化(清除单选/行选中状态)
|
||||
watch(
|
||||
() => props.tableData,
|
||||
() => {
|
||||
if (props.singleSelect) {
|
||||
selectedRadioRow.value = null
|
||||
}
|
||||
}
|
||||
)
|
||||
// 监听 tableData 变化
|
||||
watch(
|
||||
() => props.tableData,
|
||||
() => {
|
||||
selectedRow.value = null
|
||||
tableRef.value?.setCurrentRow(null)
|
||||
},
|
||||
{ deep: true },
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 处理下拉筛选命令
|
||||
|
|
@ -561,6 +542,11 @@ defineExpose({
|
|||
}
|
||||
}
|
||||
|
||||
::v-deep(.cell) {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.column-setting-content {
|
||||
.setting-header {
|
||||
display: flex;
|
||||
|
|
|
|||
|
|
@ -48,8 +48,6 @@ export const useTransactionStore = defineStore('transaction', () => {
|
|||
const positionList = ref<PositionItem[]>([])
|
||||
/** 做市商浮动盈亏数据 Map<account_id, float_profit_loss> */
|
||||
const marketFloatProfitMap = ref<Map<string, string>>(new Map())
|
||||
/** 做市商平仓盈亏数据 Map<account_id, close_profit_loss> */
|
||||
const marketCloseProfitMap = ref<Map<string, string>>(new Map())
|
||||
|
||||
// ------------------ 初始化 ------------------
|
||||
|
||||
|
|
@ -120,23 +118,20 @@ export const useTransactionStore = defineStore('transaction', () => {
|
|||
* 格式:account_id:float_profit_loss:close_profit_loss (例:84:0.000000:0.000000)
|
||||
*/
|
||||
const handleMarketFloatProfit = (data: string): void => {
|
||||
console.log('data:', data);
|
||||
|
||||
if (!data) return
|
||||
|
||||
const fields = data.split(':')
|
||||
if (fields.length < 3) return
|
||||
if (fields.length < 2) return
|
||||
|
||||
const account_id = fields[0]
|
||||
const float_profit_loss = fields[1] as string
|
||||
const close_profit_loss = fields[2] as string
|
||||
const float_profit_loss = fields[1] as string
|
||||
|
||||
// 更新浮动盈亏 Map
|
||||
marketFloatProfitMap.value.set(account_id, float_profit_loss)
|
||||
console.log('marketFloatProfitMap.value:', marketFloatProfitMap.value);
|
||||
marketFloatProfitMap.value = new Map(marketFloatProfitMap.value)
|
||||
|
||||
// 更新平仓盈亏 Map
|
||||
marketCloseProfitMap.value.set(account_id, close_profit_loss)
|
||||
marketCloseProfitMap.value = new Map(marketCloseProfitMap.value)
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +173,6 @@ export const useTransactionStore = defineStore('transaction', () => {
|
|||
mqttClient?.disconnect()
|
||||
positionList.value = []
|
||||
marketFloatProfitMap.value.clear()
|
||||
marketCloseProfitMap.value.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -193,7 +187,6 @@ export const useTransactionStore = defineStore('transaction', () => {
|
|||
mqttClient,
|
||||
positionList,
|
||||
marketFloatProfitMap,
|
||||
marketCloseProfitMap,
|
||||
initMarketConditionsMQTT,
|
||||
getPositionList,
|
||||
requestMarketFloatProfit,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export const tableColumns = [
|
|||
// 手续费分成比例
|
||||
{ prop: 'commissionRatio', label: '手续费分成比例', slotName: 'commissionRatio', width: 140 },
|
||||
// 注册时间
|
||||
{ prop: 'createTime', label: '注册时间', sortable: true, minWidth: 180 },
|
||||
{ prop: 'createTime', label: '注册时间', sortable: true, minWidth: '200px' },
|
||||
// 操作
|
||||
{ label: '操作', slotName: 'action', width: 200 }
|
||||
]
|
||||
|
|
|
|||
|
|
@ -295,9 +295,9 @@ const sendCodeApi = async (receiver: string) => {
|
|||
params.email = receiver
|
||||
} else {
|
||||
params.mobile = receiver
|
||||
// 从手机号中提取区号
|
||||
const match = receiver.match(/^\+(\d{1,4})/)
|
||||
params.mobile_area = match ? '+' + match[1] : '+86'
|
||||
// 手机区号取缓存 foreign_userInfo 的 mobile_area
|
||||
const foreignUserInfo = getStorage<any>('foreign_userInfo')
|
||||
params.mobile_area = foreignUserInfo?.mobile_area || '+86'
|
||||
}
|
||||
return userStore.sendCode(params)
|
||||
}
|
||||
|
|
@ -399,21 +399,6 @@ const handleTransferTabClick = async (type: string) => {
|
|||
transferType.value = type
|
||||
await resetTransferForm()
|
||||
}
|
||||
/**
|
||||
* @description 切换验证方式
|
||||
* @param type 验证方式
|
||||
*/
|
||||
const handleVerifyParams = (type: string) => {
|
||||
if (type === 'external') {
|
||||
if (verifyForm.value.verifyType === 'email') {
|
||||
return userInfo.email
|
||||
} else {
|
||||
return userInfo.mobile
|
||||
}
|
||||
} else {
|
||||
return transferForm.value.transferInAccount
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 提交转账表单
|
||||
|
|
@ -428,7 +413,7 @@ const handleSubmit = async () => {
|
|||
const params = {
|
||||
type: transferType.value === 'internal' ? '1' : '2',
|
||||
from: String(transferForm.value.transferOutAccount) + '',
|
||||
to: handleVerifyParams(transferType.value) + '',
|
||||
to: transferForm.value.transferInAccount + '',
|
||||
amount: transferForm.value.transferAmount,
|
||||
verify_type: '',
|
||||
verify_code: '',
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@
|
|||
<div class="amount-section">
|
||||
<div class="section-label">
|
||||
{{ t('withdraw.dialog.amount') }}
|
||||
<span class="max-balance">(最大可取:{{ totalWalletBalance }} {{ currentCurrency }})</span>
|
||||
<span class="max-balance">(最大可取:{{ totalWalletBalance }})</span>
|
||||
</div>
|
||||
<el-input v-model="withdrawAmount" type="number" :placeholder="t('withdraw.dialog.amountPlaceholder')"
|
||||
@input="handleAmountInput">
|
||||
|
|
@ -139,7 +139,7 @@ import { ref, computed, watch } from 'vue'
|
|||
import { useI18n } from 'vue-i18n'
|
||||
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
|
||||
import Dialog from '@/components/common/Dialog.vue'
|
||||
import { getWalletCapital, getWithdrawChannel, getAgentWithdrawChannel, agentWithdraw, withdraw } from '@/api/modules/capital/withdraw'
|
||||
import { getWalletCapital, agentWithdraw, withdraw } from '@/api/modules/capital/withdraw'
|
||||
import { getStorage } from '@/utils/storage'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
|
@ -182,11 +182,6 @@ interface WalletInfo {
|
|||
closing_profit: string
|
||||
}
|
||||
|
||||
interface ChannelItem {
|
||||
id: number
|
||||
name: string
|
||||
}
|
||||
|
||||
interface CalculatedItem {
|
||||
type: string
|
||||
typeLabel: string
|
||||
|
|
@ -238,16 +233,19 @@ const availableBalance = computed(() => {
|
|||
const amount = parseFloat(walletInfo.value.amount || '0')
|
||||
const freeze = parseFloat(walletInfo.value.freeze || '0')
|
||||
// 钱包余额总和
|
||||
const walletSum =
|
||||
let walletSum =
|
||||
Math.max(0, parseFloat(walletInfo.value.freeze)) +
|
||||
Math.max(0, parseFloat(walletInfo.value.risk || '0')) +
|
||||
Math.max(0, parseFloat(walletInfo.value.service || '0')) +
|
||||
Math.max(0, parseFloat(walletInfo.value.commission_fee_handling || '0')) +
|
||||
Math.max(0, parseFloat(walletInfo.value.commission_point_diff || '0'))
|
||||
// Math.max(0, parseFloat(walletInfo.value.closing_profit || '0'))
|
||||
Math.max(0, parseFloat(walletInfo.value.commission_point_diff || '0'));
|
||||
|
||||
if (+roleId === 3 || +roleId === 4) {
|
||||
walletSum += Math.max(0, parseFloat(walletInfo.value.closing_profit || '0'))
|
||||
}
|
||||
return floorMoney(amount - walletSum)
|
||||
})
|
||||
|
||||
|
||||
// 计算钱包余额总和(用于验证输入金额上限)
|
||||
// roleId >= 5 时,totalWalletBalance 需要包含 availableBalance
|
||||
const totalWalletBalance = computed(() => {
|
||||
|
|
@ -261,8 +259,7 @@ const totalWalletBalance = computed(() => {
|
|||
parseFloat(walletInfo.value.commission_point_diff || '0') +
|
||||
parseFloat(walletInfo.value.service || '0') +
|
||||
parseFloat(walletInfo.value.risk || '0') +
|
||||
parseFloat(Math.min(0, +walletInfo.value.closing_profit) + '')
|
||||
|
||||
parseFloat(Math.max(0, +walletInfo.value.closing_profit) + '')
|
||||
if (roleId >= 5) {
|
||||
// roleId >= 5 时,总余额包含可用余额
|
||||
// availableBalance = amount - freeze - walletSum
|
||||
|
|
@ -354,9 +351,6 @@ const walletInfo = ref<WalletInfo>({
|
|||
closing_profit: '0',
|
||||
})
|
||||
|
||||
// 渠道列表
|
||||
const channelList = ref<ChannelItem[]>([])
|
||||
|
||||
// 计算后的扣除明细
|
||||
const calculatedData = ref<CalculatedItem[]>([])
|
||||
|
||||
|
|
@ -452,31 +446,6 @@ const initData = async () => {
|
|||
} catch (error) {
|
||||
console.error('获取钱包信息失败', error)
|
||||
}
|
||||
|
||||
// 获取渠道列表
|
||||
try {
|
||||
const api = isAgent.value ? getWithdrawChannel : getAgentWithdrawChannel
|
||||
const res: any = await api()
|
||||
if (res && Array.isArray(res)) {
|
||||
channelList.value = res.map((item: any) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
channel_code: item.channel_code || '',
|
||||
}))
|
||||
} else if (res?.data) {
|
||||
channelList.value = res.data.map((item: any) => ({
|
||||
id: item.id,
|
||||
name: item.name,
|
||||
channel_code: item.channel_code || '',
|
||||
}))
|
||||
}
|
||||
// 如果有默认渠道,设置当前渠道编码
|
||||
if (channelList.value.length > 0) {
|
||||
currentChannelCode.value = channelList.value[0].channel_code || ''
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取渠道列表失败', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 计算扣除明细
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ export const formItemsData = [
|
|||
{
|
||||
label: '渠道费用',
|
||||
prop: 'channel_fee',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 999999,
|
||||
controls: false,
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@
|
|||
<el-button link type="primary" size="small" @click="handleEditCustomer(row)">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-divider direction="vertical" />
|
||||
<el-button link type="warning" size="small" @click="handleAdjustAmount(row)">
|
||||
<el-divider direction="vertical" v-auth="'user_updateCapital'" />
|
||||
<el-button link type="warning" size="small" @click="handleAdjustAmount(row)" v-auth="'user_updateCapital'">
|
||||
调整金额
|
||||
</el-button>
|
||||
</template>
|
||||
|
|
|
|||
|
|
@ -36,9 +36,9 @@
|
|||
<template #floatProfitLoss="{ row }">
|
||||
<span>{{ getMarketFloatProfit(row.id) }}</span>
|
||||
</template>
|
||||
<template #closeProfitLoss="{ row }">
|
||||
<!-- <template #closeProfitLoss="{ row }">
|
||||
<span>{{ getMarketCloseProfit(row.id) }}</span>
|
||||
</template>
|
||||
</template> -->
|
||||
<template #pointSpread="{ row }">
|
||||
<el-button type="primary" size="small" @click="handleViewPointDiff(row)"> 查看 </el-button>
|
||||
</template>
|
||||
|
|
@ -71,7 +71,7 @@
|
|||
label="设置做市状态"
|
||||
@click="handleAction('status')"
|
||||
/>
|
||||
<context-menu-item
|
||||
<!-- <context-menu-item
|
||||
v-if="
|
||||
([1, 2, 3, 4, 5].includes(userStore!.userInfo!.role_id) &&
|
||||
userStore!.userInfo!.role_id === currentRow.role_id)
|
||||
|
|
@ -79,7 +79,7 @@
|
|||
label="取款"
|
||||
divided
|
||||
@click="handleAction('deposit')"
|
||||
/>
|
||||
/> -->
|
||||
</context-menu>
|
||||
|
||||
<!-- 分页 -->
|
||||
|
|
@ -370,7 +370,7 @@ import {
|
|||
updateCapital,
|
||||
editAgentBail,
|
||||
} from '@/api/modules/profile/business.ts'
|
||||
import { getAgentWithdrawChannel, agentWithdraw } from '@/api/modules/capital/withdraw'
|
||||
import { getWithdrawChannel, agentWithdraw } from '@/api/modules/capital/withdraw'
|
||||
|
||||
const userStore = useUserStore()
|
||||
|
||||
|
|
@ -531,6 +531,14 @@ const menuVisible = ref(false)
|
|||
const menuOptions = reactive({ x: 0, y: 0, zIndex: 3000 })
|
||||
const currentRow = ref<any>(null)
|
||||
const rightClickDialogVisible = ref(false)
|
||||
// 判断右键菜单是否有可显示项
|
||||
const hasMenuItems = computed(() => {
|
||||
if (!currentRow.value) return false
|
||||
return (
|
||||
[3, 4].includes(currentRow.value.role_id) &&
|
||||
userStore?.userInfo?.role_id === 1
|
||||
)
|
||||
})
|
||||
const rightClickDialogType = ref<'account' | 'acceptOrders' | 'transfer' | 'status' | 'deposit' | 'withdraw'>(
|
||||
'account',
|
||||
)
|
||||
|
|
@ -573,6 +581,8 @@ const handleRowContextMenu = (row: any, column: any, event: any) => {
|
|||
return
|
||||
event?.preventDefault()
|
||||
currentRow.value = row
|
||||
// 判断右键菜单是否有内容,没有内容则不弹出菜单
|
||||
if (!hasMenuItems.value) return
|
||||
menuOptions.x = event?.clientX
|
||||
menuOptions.y = event?.clientY
|
||||
menuOptions.zIndex = 3000
|
||||
|
|
@ -734,7 +744,7 @@ const withdrawChannelOptions = ref<{ label: string; value: number }[]>([])
|
|||
// 获取取款渠道列表
|
||||
const fetchWithdrawChannel = async () => {
|
||||
try {
|
||||
const res = await getAgentWithdrawChannel()
|
||||
const res = await getWithdrawChannel()
|
||||
withdrawChannelOptions.value = res.map((item: any) => ({
|
||||
label: item.name,
|
||||
value: item.id,
|
||||
|
|
@ -1419,3 +1429,4 @@ const handleCancel = async () => {
|
|||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ export const marketTableColumns = [
|
|||
//浮动盈亏
|
||||
{ label: '浮动盈亏', slotName: 'floatProfitLoss', align: 'center' as const, width: '100px', isNumber: true },
|
||||
//平仓盈亏
|
||||
{ label: '平仓盈亏', slotName: 'closeProfitLoss', align: 'center' as const, width: '150px', isNumber: true },
|
||||
{ label: '平仓盈亏', prop: 'closeProfitLoss', align: 'center' as const, width: '150px', isNumber: true },
|
||||
//风险金
|
||||
{ label: '风险金', prop: 'riskMargin', align: 'center' as const, width: '150px', isNumber: true },
|
||||
//服务费
|
||||
|
|
|
|||
|
|
@ -536,10 +536,10 @@ const handlePasswordConfirm = async () => {
|
|||
};
|
||||
|
||||
// 如果是手机验证,添加 mobile_area 参数
|
||||
if (passwordForm.verifyType === 'mobile' && userInfo.mobile) {
|
||||
const match = userInfo.mobile.match(/^\+(\d{1,4})/);
|
||||
params.mobile_area = match ? '+' + match[1] : '+86';
|
||||
}
|
||||
// if (passwordForm.verifyType === 'mobile' && userInfo.mobile) {
|
||||
// // 手机区号取缓存 userInfo 的 mobile_area
|
||||
// params.mobile_area = userInfo.mobile_area || '+86';
|
||||
// }
|
||||
|
||||
await updatePwdByCaptchaApi(params);
|
||||
passwordDialogVisible.value = false;
|
||||
|
|
|
|||
|
|
@ -75,19 +75,27 @@ defineOptions({ name: 'Exe' })
|
|||
|
||||
type DownloadType = 'pc' | 'android' | 'ios'
|
||||
|
||||
// exe 下载基础地址,根据不同环境从 .env.* 注入
|
||||
// 生产:adminh5.apimcapital.top;测试:adminh5.duxiang.wiki
|
||||
const exeBaseUrl = import.meta.env.VITE_EXE_DOWNLOAD_BASE_URL || 'https://adminh5.apimcapital.top'
|
||||
|
||||
// 测试环境使用 debug 包,生产环境使用 release 包
|
||||
const isTestEnv = import.meta.env.MODE === 'test' || import.meta.env.NODE_ENV === 'test'
|
||||
const androidApkName = isTestEnv ? 'app-production-debug.apk' : 'app-production-release.apk'
|
||||
|
||||
const downloadUrlMap: Record<DownloadType, string> = {
|
||||
pc: 'https://adminh5.apimcapital.top/pc/Apim Capital-1.0.24.exe',
|
||||
android: 'https://adminh5.apimcapital.top/app/app-production-release.apk',
|
||||
ios: 'https://adminh5.apimcapital.top/app/xxxx.apk'
|
||||
pc: `${exeBaseUrl}/pc/Apim Capital-1.0.27.exe`,
|
||||
android: `${exeBaseUrl}/app/${androidApkName}`,
|
||||
ios: `${exeBaseUrl}/app/xxxx.apk`
|
||||
}
|
||||
|
||||
const handleDownload = (type: DownloadType) => {
|
||||
const url: string = downloadUrlMap[type]
|
||||
// 使用 window.open 在新页签打开下载链接,
|
||||
// 避免在当前页点击 a.download 时被 SPA 路由拦截跳转到登录页
|
||||
const newWindow = window.open(url, '_blank', 'noopener,noreferrer')
|
||||
// const newWindow = window.open(url, '_blank', 'noopener,noreferrer')
|
||||
// 某些浏览器会拦截弹窗,做一次降级处理
|
||||
if (!newWindow) {
|
||||
// if (!newWindow) {
|
||||
const a = document.createElement('a')
|
||||
a.href = url
|
||||
a.target = '_blank'
|
||||
|
|
@ -97,7 +105,7 @@ const handleDownload = (type: DownloadType) => {
|
|||
document.body.appendChild(a)
|
||||
a.click()
|
||||
document.body.removeChild(a)
|
||||
}
|
||||
// }
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
@ -142,6 +150,9 @@ const handleDownload = (type: DownloadType) => {
|
|||
|
||||
&.pc {
|
||||
background: linear-gradient(135deg, #409eff, #2b7cd3);
|
||||
color: #fff;
|
||||
font-size: 23px;
|
||||
font-weight: 900;
|
||||
}
|
||||
&.android {
|
||||
background: linear-gradient(135deg, #36c66c, #26a357);
|
||||
|
|
@ -192,4 +203,4 @@ const handleDownload = (type: DownloadType) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
v-for="tab in tabList"
|
||||
:key="tab.name"
|
||||
:class="['tab-item', { active: activeTab === tab.name }]"
|
||||
@click="activeTab = tab.name"
|
||||
@click="switchTab(tab.name)"
|
||||
>
|
||||
{{ tab.label }}
|
||||
</div>
|
||||
|
|
@ -121,6 +121,18 @@ const rules: FormRules = {
|
|||
],
|
||||
}
|
||||
|
||||
// 切换登录方式:清空输入框并重置表单校验
|
||||
function switchTab(name: string) {
|
||||
if (activeTab.value === name) return
|
||||
activeTab.value = name
|
||||
// 清空邮箱、手机号、密码
|
||||
loginForm.value.email = ''
|
||||
loginForm.value.phone = ''
|
||||
loginForm.value.password = ''
|
||||
// 重置表单校验状态,清除错误提示
|
||||
loginFormRef.value?.clearValidate()
|
||||
}
|
||||
|
||||
// 弹窗相关
|
||||
const dialogVisible = ref(false)
|
||||
const dialogTitle = ref('')
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import mammoth from 'mammoth'
|
||||
import { ref, onUnmounted, onMounted } from 'vue'
|
||||
import { ref, onUnmounted, onMounted, watch } from 'vue'
|
||||
import termFile from '@/assets/file/服务条款.docx?url'
|
||||
import privacyFile from '@/assets/file/隐私协议.docx?url'
|
||||
import riskFile from '@/assets/file/风险提示.docx?url'
|
||||
|
|
@ -136,6 +136,17 @@ const registerForm = ref<any>({
|
|||
inviteCode: '',
|
||||
agreeAgreement: false,
|
||||
})
|
||||
|
||||
// 切换注册方式时,清空上次输入的数据与校验状态
|
||||
watch(activeTab, () => {
|
||||
registerForm.value.email = ''
|
||||
registerForm.value.countryCode = '+86'
|
||||
registerForm.value.phone = ''
|
||||
registerForm.value.code = ''
|
||||
// 清除表单校验提示,避免上一次的错误一直显示
|
||||
registerFormRef.value?.clearValidate()
|
||||
})
|
||||
|
||||
const registerRules = ref<FormRules>({
|
||||
email: emailRules().email,
|
||||
phone: phoneRules(registerForm.value).phone,
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ const handleRefresh = async (row?: AddressRow) => {
|
|||
ElMessage.success('刷新成功')
|
||||
const index = tableData.value.findIndex(item => item.address === targetRow.address)
|
||||
if (index !== -1) {
|
||||
tableData.value[index]!.trxBalance = res.data;
|
||||
tableData.value[index]!.usdtBalance = res.data;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
|
|
|
|||
Loading…
Reference in New Issue