feat(subscription): 添加订阅功能并优化过期处理逻辑
- 扩展 trialExpiredType 类型,新增 'subscribe' 状态以支持主动订阅场景 - 新增 openSubscriptionDialog 方法,用于处理 VIP 状态点击事件 - 优化 VIP 状态卡片 UI,添加悬停与点击效果,提升交互体验 - 调整过期状态样式,保持水平布局并移除冗余按钮样式 - 在 Rakuten 组件中引入请求中断机制,提升任务控制灵活性- 更新 TrialExpiredDialog 组件,支持订阅类型提示与微信复制反馈- 修复部分 API 调用未传递 signal 参数的问题,增强请求管理能力 - 切换 Ruoyi 服务地址至生产环境配置,确保接口通信正常 - 移除部分无用代码与样式,精简组件结构
This commit is contained in:
@@ -84,7 +84,7 @@ const showSettingsDialog = ref(false)
|
|||||||
|
|
||||||
// 试用期过期对话框
|
// 试用期过期对话框
|
||||||
const showTrialExpiredDialog = ref(false)
|
const showTrialExpiredDialog = ref(false)
|
||||||
const trialExpiredType = ref<'device' | 'account' | 'both'>('device')
|
const trialExpiredType = ref<'device' | 'account' | 'both' | 'subscribe'>('device')
|
||||||
|
|
||||||
// 菜单配置 - 复刻ERP客户端格式
|
// 菜单配置 - 复刻ERP客户端格式
|
||||||
const menuConfig = [
|
const menuConfig = [
|
||||||
@@ -310,7 +310,7 @@ async function refreshVipStatus() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 判断过期类型
|
// 判断过期类型
|
||||||
function checkExpiredType(): 'device' | 'account' | 'both' {
|
function checkExpiredType(): 'device' | 'account' | 'both' | 'subscribe' {
|
||||||
const accountExpired = vipExpireTime.value && new Date() > vipExpireTime.value
|
const accountExpired = vipExpireTime.value && new Date() > vipExpireTime.value
|
||||||
const deviceExpired = deviceTrialExpired.value
|
const deviceExpired = deviceTrialExpired.value
|
||||||
|
|
||||||
@@ -320,6 +320,17 @@ function checkExpiredType(): 'device' | 'account' | 'both' {
|
|||||||
return 'account' // 默认
|
return 'account' // 默认
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 打开订阅对话框
|
||||||
|
function openSubscriptionDialog() {
|
||||||
|
// 如果VIP有效,显示订阅/续费提示;如果已过期,显示过期提示
|
||||||
|
if (vipStatus.value.isVip) {
|
||||||
|
trialExpiredType.value = 'subscribe'
|
||||||
|
} else {
|
||||||
|
trialExpiredType.value = checkExpiredType()
|
||||||
|
}
|
||||||
|
showTrialExpiredDialog.value = true
|
||||||
|
}
|
||||||
|
|
||||||
// 提供给子组件使用
|
// 提供给子组件使用
|
||||||
provide('refreshVipStatus', refreshVipStatus)
|
provide('refreshVipStatus', refreshVipStatus)
|
||||||
provide('checkExpiredType', checkExpiredType)
|
provide('checkExpiredType', checkExpiredType)
|
||||||
@@ -519,22 +530,18 @@ onUnmounted(() => {
|
|||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<!-- VIP状态卡片 -->
|
<!-- VIP状态卡片 -->
|
||||||
<div v-if="isAuthenticated" class="vip-status-card" :class="'vip-' + vipStatus.status">
|
<div v-if="isAuthenticated" class="vip-status-card" :class="'vip-' + vipStatus.status" @click="openSubscriptionDialog">
|
||||||
<div class="vip-info">
|
<div class="vip-info">
|
||||||
<div class="vip-status-text">
|
<div class="vip-status-text">
|
||||||
<template v-if="vipStatus.isVip">
|
<template v-if="vipStatus.isVip">
|
||||||
<span v-if="vipStatus.status === 'warning'">即将到期</span>
|
<span v-if="vipStatus.status === 'warning'">即将到期</span>
|
||||||
<span v-else>订阅中</span>
|
<span v-else>订阅中</span>
|
||||||
</template>
|
</template>
|
||||||
<template v-else>
|
|
||||||
<span>已过期</span>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="vip-expire-date" v-if="vipExpireTime">
|
<div class="vip-expire-date" v-if="vipExpireTime">
|
||||||
有效期至:{{ vipExpireTime ? new Date(vipExpireTime).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }) : '-' }}
|
有效期至:{{ vipExpireTime ? new Date(vipExpireTime).toLocaleDateString('zh-CN', { year: 'numeric', month: 'long', day: 'numeric' }) : '-' }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -924,6 +931,17 @@ onUnmounted(() => {
|
|||||||
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.15);
|
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.15);
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.vip-status-card:hover {
|
||||||
|
box-shadow: 0 3px 10px rgba(255, 215, 0, 0.25);
|
||||||
|
transform: translateY(-1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.vip-status-card:active {
|
||||||
|
transform: translateY(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 正常状态和警告状态 - 统一温暖金色渐变 */
|
/* 正常状态和警告状态 - 统一温暖金色渐变 */
|
||||||
@@ -934,15 +952,10 @@ onUnmounted(() => {
|
|||||||
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.15);
|
box-shadow: 0 2px 8px rgba(255, 215, 0, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 过期状态 - 灰色,垂直布局 */
|
/* 过期状态 - 灰色,保持水平布局 */
|
||||||
.vip-status-card.vip-expired {
|
.vip-status-card.vip-expired {
|
||||||
background: linear-gradient(135deg, #FAFAFA 0%, #E8E8E8 100%);
|
background: linear-gradient(135deg, #FAFAFA 0%, #E8E8E8 100%);
|
||||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||||
flex-direction: column;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: stretch;
|
|
||||||
padding: 10px;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.vip-info {
|
.vip-info {
|
||||||
@@ -970,33 +983,6 @@ onUnmounted(() => {
|
|||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 右侧徽章按钮 */
|
|
||||||
.vip-badge {
|
|
||||||
padding: 5px 10px;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
white-space: nowrap;
|
|
||||||
flex-shrink: 0;
|
|
||||||
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 2px 6px rgba(74, 144, 226, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 过期状态续费按钮 - 置底 */
|
|
||||||
.vip-renew-btn {
|
|
||||||
padding: 7px 0;
|
|
||||||
text-align: center;
|
|
||||||
background: linear-gradient(135deg, #4A90E2 0%, #357ABD 100%);
|
|
||||||
color: white;
|
|
||||||
border-radius: 5px;
|
|
||||||
font-size: 11px;
|
|
||||||
font-weight: 600;
|
|
||||||
width: 100%;
|
|
||||||
flex-shrink: 0;
|
|
||||||
box-shadow: 0 2px 6px rgba(74, 144, 226, 0.3);
|
|
||||||
}
|
|
||||||
|
|
||||||
.vip-status-card.vip-expired .vip-info {
|
.vip-status-card.vip-expired .vip-info {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ export type HttpMethod = 'GET' | 'POST' | 'DELETE';
|
|||||||
|
|
||||||
export const CONFIG = {
|
export const CONFIG = {
|
||||||
CLIENT_BASE: 'http://localhost:8081',
|
CLIENT_BASE: 'http://localhost:8081',
|
||||||
//RUOYI_BASE: 'http://8.138.23.49:8085',
|
RUOYI_BASE: 'http://8.138.23.49:8085',
|
||||||
RUOYI_BASE: 'http://192.168.1.89:8085',
|
//RUOYI_BASE: 'http://192.168.1.89:8085',
|
||||||
SSE_URL: 'http://192.168.1.89:8085/monitor/account/events'
|
SSE_URL: 'http://8.138.23.49:8085/monitor/account/events'
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
function resolveBase(path: string): string {
|
function resolveBase(path: string): string {
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { http } from './http'
|
import { http } from './http'
|
||||||
|
|
||||||
export const rakutenApi = {
|
export const rakutenApi = {
|
||||||
getProducts(params: { file?: File; shopName?: string; batchId?: string }) {
|
getProducts(params: { file?: File; shopName?: string; batchId?: string }, signal?: AbortSignal) {
|
||||||
const formData = new FormData()
|
const formData = new FormData()
|
||||||
if (params.file) formData.append('file', params.file)
|
if (params.file) formData.append('file', params.file)
|
||||||
if (params.batchId) formData.append('batchId', params.batchId)
|
if (params.batchId) formData.append('batchId', params.batchId)
|
||||||
if (params.shopName) formData.append('shopName', params.shopName)
|
if (params.shopName) formData.append('shopName', params.shopName)
|
||||||
return http.upload('/api/rakuten/products', formData)
|
return http.upload('/api/rakuten/products', formData, signal)
|
||||||
},
|
},
|
||||||
|
|
||||||
search1688(imageUrl: string, sessionId?: string) {
|
search1688(imageUrl: string, sessionId?: string, signal?: AbortSignal) {
|
||||||
const payload: Record<string, unknown> = { imageUrl }
|
const payload: Record<string, unknown> = { imageUrl }
|
||||||
if (sessionId) payload.sessionId = sessionId
|
if (sessionId) payload.sessionId = sessionId
|
||||||
return http.post('/api/rakuten/search1688', payload)
|
return http.post('/api/rakuten/search1688', payload, signal)
|
||||||
},
|
},
|
||||||
|
|
||||||
getLatestProducts() {
|
getLatestProducts() {
|
||||||
|
|||||||
@@ -32,9 +32,9 @@ const dragActive = ref(false)
|
|||||||
|
|
||||||
// 试用期过期弹框
|
// 试用期过期弹框
|
||||||
const showTrialExpiredDialog = ref(false)
|
const showTrialExpiredDialog = ref(false)
|
||||||
const trialExpiredType = ref<'device' | 'account' | 'both'>('account')
|
const trialExpiredType = ref<'device' | 'account' | 'both' | 'subscribe'>('account')
|
||||||
|
|
||||||
const checkExpiredType = inject<() => 'device' | 'account' | 'both'>('checkExpiredType')
|
const checkExpiredType = inject<() => 'device' | 'account' | 'both' | 'subscribe'>('checkExpiredType')
|
||||||
|
|
||||||
// 计算属性 - 当前页数据
|
// 计算属性 - 当前页数据
|
||||||
const paginatedData = computed(() => {
|
const paginatedData = computed(() => {
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
modelValue: boolean
|
modelValue: boolean
|
||||||
expiredType: 'device' | 'account' | 'both' // 设备过期、账号过期、都过期
|
expiredType: 'device' | 'account' | 'both' | 'subscribe' // 设备过期、账号过期、都过期、主动订阅
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Emits {
|
interface Emits {
|
||||||
@@ -19,12 +20,14 @@ const visible = computed({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const titleText = computed(() => {
|
const titleText = computed(() => {
|
||||||
|
if (props.expiredType === 'subscribe') return '订阅服务'
|
||||||
if (props.expiredType === 'both') return '试用已到期'
|
if (props.expiredType === 'both') return '试用已到期'
|
||||||
if (props.expiredType === 'account') return '账号试用已到期'
|
if (props.expiredType === 'account') return '账号试用已到期'
|
||||||
return '设备试用已到期'
|
return '设备试用已到期'
|
||||||
})
|
})
|
||||||
|
|
||||||
const subtitleText = computed(() => {
|
const subtitleText = computed(() => {
|
||||||
|
if (props.expiredType === 'subscribe') return '联系客服订阅或续费,享受完整服务'
|
||||||
if (props.expiredType === 'both') return '试用已到期,请联系客服订阅以获取完整服务'
|
if (props.expiredType === 'both') return '试用已到期,请联系客服订阅以获取完整服务'
|
||||||
if (props.expiredType === 'account') return '账号试用已到期,请联系客服订阅'
|
if (props.expiredType === 'account') return '账号试用已到期,请联系客服订阅'
|
||||||
return '当前设备试用已到期,请更换新设备体验或联系客服订阅'
|
return '当前设备试用已到期,请更换新设备体验或联系客服订阅'
|
||||||
@@ -35,8 +38,11 @@ function handleConfirm() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function copyWechat() {
|
function copyWechat() {
|
||||||
navigator.clipboard.writeText('_linhong')
|
navigator.clipboard.writeText('_linhong').then(() => {
|
||||||
// ElMessage.success('微信号已复制')
|
ElMessage.success('微信号已复制')
|
||||||
|
}).catch(() => {
|
||||||
|
ElMessage.error('复制失败,请手动复制')
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -71,6 +77,7 @@ function copyWechat() {
|
|||||||
<div class="wechat-label">客服微信</div>
|
<div class="wechat-label">客服微信</div>
|
||||||
<div class="wechat-id">_linhong</div>
|
<div class="wechat-id">_linhong</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="copy-icon">📋</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 按钮 -->
|
<!-- 按钮 -->
|
||||||
@@ -137,11 +144,13 @@ function copyWechat() {
|
|||||||
margin-bottom: 20px;
|
margin-bottom: 20px;
|
||||||
width: 90%;
|
width: 90%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
transition: background 0.3s;
|
transition: all 0.3s;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wechat-card:hover {
|
.wechat-card:hover {
|
||||||
background: #ebebeb;
|
background: #e8f5e9;
|
||||||
|
box-shadow: 0 2px 8px rgba(9, 187, 7, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.wechat-icon {
|
.wechat-icon {
|
||||||
@@ -170,6 +179,18 @@ function copyWechat() {
|
|||||||
color: #1f1f1f;
|
color: #1f1f1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.copy-icon {
|
||||||
|
margin-left: auto;
|
||||||
|
font-size: 16px;
|
||||||
|
opacity: 0.5;
|
||||||
|
transition: all 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wechat-card:hover .copy-icon {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1.1);
|
||||||
|
}
|
||||||
|
|
||||||
.confirm-btn {
|
.confirm-btn {
|
||||||
height: 40px;
|
height: 40px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ const tableLoading = ref(false)
|
|||||||
const exportLoading = ref(false)
|
const exportLoading = ref(false)
|
||||||
const statusMessage = ref('')
|
const statusMessage = ref('')
|
||||||
const statusType = ref<'info' | 'success' | 'warning' | 'error'>('info')
|
const statusType = ref<'info' | 'success' | 'warning' | 'error'>('info')
|
||||||
|
let abortController: AbortController | null = null
|
||||||
|
|
||||||
// 查询与上传
|
// 查询与上传
|
||||||
const singleShopName = ref('')
|
const singleShopName = ref('')
|
||||||
@@ -55,9 +56,9 @@ const activeStep = computed(() => {
|
|||||||
|
|
||||||
// 试用期过期弹框
|
// 试用期过期弹框
|
||||||
const showTrialExpiredDialog = ref(false)
|
const showTrialExpiredDialog = ref(false)
|
||||||
const trialExpiredType = ref<'device' | 'account' | 'both'>('account')
|
const trialExpiredType = ref<'device' | 'account' | 'both' | 'subscribe'>('account')
|
||||||
|
|
||||||
const checkExpiredType = inject<() => 'device' | 'account' | 'both'>('checkExpiredType')
|
const checkExpiredType = inject<() => 'device' | 'account' | 'both' | 'subscribe'>('checkExpiredType')
|
||||||
|
|
||||||
// 左侧:上传文件名与地区
|
// 左侧:上传文件名与地区
|
||||||
const selectedFileName = ref('')
|
const selectedFileName = ref('')
|
||||||
@@ -153,7 +154,7 @@ async function searchProductInternal(product: any) {
|
|||||||
showTrialExpiredDialog.value = true
|
showTrialExpiredDialog.value = true
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const res: any = await rakutenApi.search1688(product.imgUrl, currentBatchId.value)
|
const res: any = await rakutenApi.search1688(product.imgUrl, currentBatchId.value, abortController?.signal)
|
||||||
const data = res.data
|
const data = res.data
|
||||||
if (!hasValid1688Data(data)) return false
|
if (!hasValid1688Data(data)) return false
|
||||||
const skuJson = data.skuPriceJson || data.skuPrice
|
const skuJson = data.skuPriceJson || data.skuPrice
|
||||||
@@ -176,7 +177,8 @@ async function searchProductWithRetry(product: any, maxRetry = 2) {
|
|||||||
try {
|
try {
|
||||||
const ok = await searchProductInternal(product)
|
const ok = await searchProductInternal(product)
|
||||||
if (ok) return true
|
if (ok) return true
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
|
if (e.name === 'AbortError') return false
|
||||||
console.warn('search1688 failed', e)
|
console.warn('search1688 failed', e)
|
||||||
}
|
}
|
||||||
if (attempt < maxRetry) await delay(600)
|
if (attempt < maxRetry) await delay(600)
|
||||||
@@ -244,6 +246,8 @@ async function handleStartSearch() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abortController = new AbortController()
|
||||||
|
|
||||||
if (pendingFile.value) {
|
if (pendingFile.value) {
|
||||||
try {
|
try {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -255,7 +259,7 @@ async function handleStartSearch() {
|
|||||||
progressPercentage.value = 0
|
progressPercentage.value = 0
|
||||||
totalProducts.value = 0
|
totalProducts.value = 0
|
||||||
processedProducts.value = 0
|
processedProducts.value = 0
|
||||||
const resp: any = await rakutenApi.getProducts({file: pendingFile.value, batchId: currentBatchId.value})
|
const resp: any = await rakutenApi.getProducts({file: pendingFile.value, batchId: currentBatchId.value}, abortController?.signal)
|
||||||
const products = (resp.data.products || []).map((p: any) => ({...p, skuPrices: parseSkuPrices(p)}))
|
const products = (resp.data.products || []).map((p: any) => ({...p, skuPrices: parseSkuPrices(p)}))
|
||||||
|
|
||||||
if (products.length === 0) {
|
if (products.length === 0) {
|
||||||
@@ -264,9 +268,11 @@ async function handleStartSearch() {
|
|||||||
|
|
||||||
allProducts.value = products
|
allProducts.value = products
|
||||||
pendingFile.value = null
|
pendingFile.value = null
|
||||||
} catch (e) {
|
} catch (e: any) {
|
||||||
statusType.value = 'error'
|
if (e.name !== 'AbortError') {
|
||||||
statusMessage.value = '解析失败,请重试'
|
statusType.value = 'error'
|
||||||
|
statusMessage.value = '解析失败,请重试'
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
tableLoading.value = false
|
tableLoading.value = false
|
||||||
@@ -280,17 +286,21 @@ async function handleStartSearch() {
|
|||||||
progressPercentage.value = 100
|
progressPercentage.value = 100
|
||||||
statusType.value = 'success'
|
statusType.value = 'success'
|
||||||
statusMessage.value = ''
|
statusMessage.value = ''
|
||||||
|
abortController = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
statusType.value = 'warning'
|
statusType.value = 'warning'
|
||||||
statusMessage.value = '没有可处理的商品,请先导入或查询店铺'
|
statusMessage.value = '没有可处理的商品,请先导入或查询店铺'
|
||||||
|
abortController = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
await startBatch1688Search(items)
|
await startBatch1688Search(items)
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopTask() {
|
function stopTask() {
|
||||||
|
abortController?.abort()
|
||||||
|
abortController = null
|
||||||
loading.value = false
|
loading.value = false
|
||||||
tableLoading.value = false
|
tableLoading.value = false
|
||||||
statusType.value = 'warning'
|
statusType.value = 'warning'
|
||||||
@@ -305,6 +315,7 @@ async function startBatch1688Search(products: any[]) {
|
|||||||
progressPercentage.value = 100
|
progressPercentage.value = 100
|
||||||
statusType.value = 'success'
|
statusType.value = 'success'
|
||||||
statusMessage.value = '所有商品都已获取1688数据!'
|
statusMessage.value = '所有商品都已获取1688数据!'
|
||||||
|
abortController = null
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loading.value = true
|
loading.value = true
|
||||||
@@ -323,6 +334,7 @@ async function startBatch1688Search(products: any[]) {
|
|||||||
statusMessage.value = ''
|
statusMessage.value = ''
|
||||||
}
|
}
|
||||||
loading.value = false
|
loading.value = false
|
||||||
|
abortController = null
|
||||||
}
|
}
|
||||||
|
|
||||||
async function serialSearch1688(products: any[]) {
|
async function serialSearch1688(products: any[]) {
|
||||||
|
|||||||
@@ -6,26 +6,19 @@ import AccountManager from '../common/AccountManager.vue'
|
|||||||
import { batchConvertImages } from '../../utils/imageProxy'
|
import { batchConvertImages } from '../../utils/imageProxy'
|
||||||
import { handlePlatformFileExport } from '../../utils/settings'
|
import { handlePlatformFileExport } from '../../utils/settings'
|
||||||
import { getUsernameFromToken } from '../../utils/token'
|
import { getUsernameFromToken } from '../../utils/token'
|
||||||
|
|
||||||
const TrialExpiredDialog = defineAsyncComponent(() => import('../common/TrialExpiredDialog.vue'))
|
const TrialExpiredDialog = defineAsyncComponent(() => import('../common/TrialExpiredDialog.vue'))
|
||||||
|
|
||||||
const refreshVipStatus = inject<() => Promise<boolean>>('refreshVipStatus')
|
const refreshVipStatus = inject<() => Promise<boolean>>('refreshVipStatus')
|
||||||
|
|
||||||
// 接收VIP状态
|
// 接收VIP状态
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
isVip: boolean
|
isVip: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
type Shop = { id: string; shopName: string }
|
type Shop = { id: string; shopName: string }
|
||||||
|
|
||||||
const accounts = ref<BanmaAccount[]>([])
|
const accounts = ref<BanmaAccount[]>([])
|
||||||
const accountId = ref<number>()
|
const accountId = ref<number>()
|
||||||
// 收起功能移除
|
// 收起功能移除
|
||||||
|
|
||||||
const shopList = ref<Shop[]>([])
|
const shopList = ref<Shop[]>([])
|
||||||
const selectedShops = ref<string[]>([])
|
const selectedShops = ref<string[]>([])
|
||||||
const dateRange = ref<string[]>([])
|
const dateRange = ref<string[]>([])
|
||||||
|
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const exportLoading = ref(false)
|
const exportLoading = ref(false)
|
||||||
const progressPercentage = ref(0)
|
const progressPercentage = ref(0)
|
||||||
@@ -44,9 +37,8 @@ let abortController: AbortController | null = null
|
|||||||
|
|
||||||
// 试用期过期弹框
|
// 试用期过期弹框
|
||||||
const showTrialExpiredDialog = ref(false)
|
const showTrialExpiredDialog = ref(false)
|
||||||
const trialExpiredType = ref<'device' | 'account' | 'both'>('account')
|
const trialExpiredType = ref<'device' | 'account' | 'both' | 'subscribe'>('account')
|
||||||
|
const checkExpiredType = inject<() => 'device' | 'account' | 'both' | 'subscribe'>('checkExpiredType')
|
||||||
const checkExpiredType = inject<() => 'device' | 'account' | 'both'>('checkExpiredType')
|
|
||||||
function selectAccount(id: number) {
|
function selectAccount(id: number) {
|
||||||
accountId.value = id
|
accountId.value = id
|
||||||
loadShops()
|
loadShops()
|
||||||
@@ -106,7 +98,6 @@ async function fetchData() {
|
|||||||
|
|
||||||
// 刷新VIP状态
|
// 刷新VIP状态
|
||||||
if (refreshVipStatus) await refreshVipStatus()
|
if (refreshVipStatus) await refreshVipStatus()
|
||||||
|
|
||||||
// VIP检查
|
// VIP检查
|
||||||
if (!props.isVip) {
|
if (!props.isVip) {
|
||||||
if (checkExpiredType) trialExpiredType.value = checkExpiredType()
|
if (checkExpiredType) trialExpiredType.value = checkExpiredType()
|
||||||
@@ -123,14 +114,11 @@ async function fetchData() {
|
|||||||
fetchCurrentPage.value = 1
|
fetchCurrentPage.value = 1
|
||||||
fetchTotalItems.value = 0
|
fetchTotalItems.value = 0
|
||||||
currentBatchId.value = `ZEBRA_${Date.now()}`
|
currentBatchId.value = `ZEBRA_${Date.now()}`
|
||||||
|
|
||||||
const [startDate = '', endDate = ''] = dateRange.value || []
|
const [startDate = '', endDate = ''] = dateRange.value || []
|
||||||
await fetchPageData(startDate, endDate)
|
await fetchPageData(startDate, endDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function fetchPageData(startDate: string, endDate: string) {
|
async function fetchPageData(startDate: string, endDate: string) {
|
||||||
if (!isFetching.value) return
|
if (!isFetching.value) return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await zebraApi.getOrders({
|
const response = await zebraApi.getOrders({
|
||||||
accountId: Number(accountId.value) || undefined,
|
accountId: Number(accountId.value) || undefined,
|
||||||
@@ -145,10 +133,8 @@ async function fetchPageData(startDate: string, endDate: string) {
|
|||||||
const data = (response as any)?.data || response
|
const data = (response as any)?.data || response
|
||||||
const orders = data.orders || []
|
const orders = data.orders || []
|
||||||
allOrderData.value = [...allOrderData.value, ...orders]
|
allOrderData.value = [...allOrderData.value, ...orders]
|
||||||
|
|
||||||
fetchTotalPages.value = data.totalPages || 0
|
fetchTotalPages.value = data.totalPages || 0
|
||||||
fetchTotalItems.value = data.total || 0
|
fetchTotalItems.value = data.total || 0
|
||||||
|
|
||||||
if (fetchCurrentPage.value < fetchTotalPages.value && isFetching.value) {
|
if (fetchCurrentPage.value < fetchTotalPages.value && isFetching.value) {
|
||||||
progressPercentage.value = Math.round((fetchCurrentPage.value / fetchTotalPages.value) * 100)
|
progressPercentage.value = Math.round((fetchCurrentPage.value / fetchTotalPages.value) * 100)
|
||||||
fetchCurrentPage.value++
|
fetchCurrentPage.value++
|
||||||
|
|||||||
@@ -39,7 +39,6 @@ spring:
|
|||||||
server:
|
server:
|
||||||
port: 8081
|
port: 8081
|
||||||
address: 127.0.0.1
|
address: 127.0.0.1
|
||||||
|
|
||||||
# 外部API服务配置
|
# 外部API服务配置
|
||||||
api:
|
api:
|
||||||
server:
|
server:
|
||||||
@@ -61,7 +60,6 @@ project:
|
|||||||
version: @project.version@
|
version: @project.version@
|
||||||
build:
|
build:
|
||||||
time: @maven.build.timestamp@
|
time: @maven.build.timestamp@
|
||||||
|
|
||||||
logging:
|
logging:
|
||||||
level:
|
level:
|
||||||
com.tashow.erp: INFO
|
com.tashow.erp: INFO
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import org.springframework.web.bind.annotation.*;
|
|||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/monitor/device")
|
@RequestMapping("/monitor/device")
|
||||||
@Anonymous
|
@Anonymous
|
||||||
|
|||||||
Reference in New Issue
Block a user