fix: 用户商品模块
This commit is contained in:
@@ -1,6 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="tags-view-container">
|
<div class="tags-view-container">
|
||||||
<div class="tags-view-wrapper">
|
<div class="tags-view-wrapper" ref="scrollWrapperRef"
|
||||||
|
@wheel.prevent="handleWheel"
|
||||||
|
@mouseenter="hovered = true" @mouseleave="hovered = false"
|
||||||
|
@scroll="updateScrollbar">
|
||||||
<div class="tags-view-scroll">
|
<div class="tags-view-scroll">
|
||||||
<router-link
|
<router-link
|
||||||
v-for="tag in visitedViews"
|
v-for="tag in visitedViews"
|
||||||
@@ -22,6 +25,9 @@
|
|||||||
</el-icon>
|
</el-icon>
|
||||||
</router-link>
|
</router-link>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="scroll-track" :class="{ visible: hovered && hasOverflow }">
|
||||||
|
<div class="scroll-thumb" :style="thumbStyle" @mousedown="onThumbDown"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 右键菜单 -->
|
<!-- 右键菜单 -->
|
||||||
@@ -181,6 +187,65 @@ const openMenu = (e, tag) => {
|
|||||||
selectedTag.value = tag
|
selectedTag.value = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 横向滚轮 + 自定义滚动条
|
||||||
|
const scrollWrapperRef = ref(null)
|
||||||
|
const hovered = ref(false)
|
||||||
|
const hasOverflow = ref(false)
|
||||||
|
const thumbStyle = ref({ width: '0px', left: '0px' })
|
||||||
|
|
||||||
|
const handleWheel = (e) => {
|
||||||
|
if (scrollWrapperRef.value) {
|
||||||
|
scrollWrapperRef.value.scrollLeft += e.deltaY || e.deltaX
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getTrackWidth = () => {
|
||||||
|
const el = scrollWrapperRef.value
|
||||||
|
if (!el) return 0
|
||||||
|
return el.clientWidth - 24
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateScrollbar = () => {
|
||||||
|
const el = scrollWrapperRef.value
|
||||||
|
if (!el) return
|
||||||
|
hasOverflow.value = el.scrollWidth > el.clientWidth
|
||||||
|
if (!hasOverflow.value) return
|
||||||
|
const tw = getTrackWidth()
|
||||||
|
const ratio = el.clientWidth / el.scrollWidth
|
||||||
|
const thumbW = Math.max(ratio * tw, 30)
|
||||||
|
const maxScroll = el.scrollWidth - el.clientWidth
|
||||||
|
const scrollRatio = maxScroll > 0 ? el.scrollLeft / maxScroll : 0
|
||||||
|
const thumbLeft = scrollRatio * (tw - thumbW)
|
||||||
|
thumbStyle.value = { width: thumbW + 'px', left: thumbLeft + 'px' }
|
||||||
|
}
|
||||||
|
|
||||||
|
const onThumbDown = (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
const el = scrollWrapperRef.value
|
||||||
|
if (!el) return
|
||||||
|
const startX = e.clientX
|
||||||
|
const startScroll = el.scrollLeft
|
||||||
|
const maxScroll = el.scrollWidth - el.clientWidth
|
||||||
|
const tw = getTrackWidth()
|
||||||
|
const ratio = el.clientWidth / el.scrollWidth
|
||||||
|
const thumbW = Math.max(ratio * tw, 30)
|
||||||
|
const movable = tw - thumbW
|
||||||
|
|
||||||
|
const onMove = (ev) => {
|
||||||
|
const dx = ev.clientX - startX
|
||||||
|
const scrollDelta = movable > 0 ? (dx / movable) * maxScroll : 0
|
||||||
|
el.scrollLeft = Math.min(Math.max(startScroll + scrollDelta, 0), maxScroll)
|
||||||
|
}
|
||||||
|
const onUp = () => {
|
||||||
|
document.removeEventListener('mousemove', onMove)
|
||||||
|
document.removeEventListener('mouseup', onUp)
|
||||||
|
}
|
||||||
|
document.addEventListener('mousemove', onMove)
|
||||||
|
document.addEventListener('mouseup', onUp)
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(visitedViews, () => nextTick(updateScrollbar), { deep: true })
|
||||||
|
|
||||||
// 关闭右键菜单
|
// 关闭右键菜单
|
||||||
const closeMenu = () => {
|
const closeMenu = () => {
|
||||||
visible.value = false
|
visible.value = false
|
||||||
@@ -201,10 +266,13 @@ const handleClickOutside = () => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
initTags()
|
initTags()
|
||||||
document.addEventListener('click', handleClickOutside)
|
document.addEventListener('click', handleClickOutside)
|
||||||
|
nextTick(updateScrollbar)
|
||||||
|
window.addEventListener('resize', updateScrollbar)
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
document.removeEventListener('click', handleClickOutside)
|
document.removeEventListener('click', handleClickOutside)
|
||||||
|
window.removeEventListener('resize', updateScrollbar)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -220,12 +288,13 @@ onBeforeUnmount(() => {
|
|||||||
.tags-view-wrapper {
|
.tags-view-wrapper {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
padding: 0 12px;
|
padding: 0 12px;
|
||||||
overflow-x: auto;
|
overflow-x: scroll;
|
||||||
|
overflow-y: hidden;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
scrollbar-width: none;
|
||||||
|
-ms-overflow-style: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tags-view-wrapper::-webkit-scrollbar {
|
.tags-view-wrapper::-webkit-scrollbar {
|
||||||
@@ -239,6 +308,37 @@ onBeforeUnmount(() => {
|
|||||||
gap: 4px;
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.scroll-track {
|
||||||
|
position: absolute;
|
||||||
|
left: 12px;
|
||||||
|
right: 12px;
|
||||||
|
bottom: 1px;
|
||||||
|
height: 3px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.25s;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-track.visible {
|
||||||
|
opacity: 1;
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-thumb {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: rgba(180,188,199,0.45);
|
||||||
|
transition: background 0.15s;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-thumb:hover {
|
||||||
|
background: rgba(180,188,199,0.65);
|
||||||
|
}
|
||||||
|
|
||||||
.tag, .active-tag {
|
.tag, .active-tag {
|
||||||
height: 32px;
|
height: 32px;
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
|
|||||||
@@ -101,15 +101,20 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="280" fixed="right">
|
<el-table-column label="操作" width="180" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<div class="action-buttons">
|
<el-button link type="primary" size="small" @click="handleDetail(row)">详情</el-button>
|
||||||
<el-button link type="primary" size="small" @click="handleDetail(row)">详情</el-button>
|
<el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||||
<el-button link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
<el-dropdown trigger="click" @command="cmd => handleMoreCmd(cmd, row)" style="margin-left:8px">
|
||||||
<el-button link type="warning" size="small" @click="openRemindList(row)">提醒记录</el-button>
|
<el-button link type="primary" size="small" style="transform: translateY(4px);">更多<el-icon style="margin-left:2px"><ArrowDown /></el-icon></el-button>
|
||||||
<el-button link type="success" size="small" @click="handleSendRemind(row)">发送提醒</el-button>
|
<template #dropdown>
|
||||||
<el-button link type="danger" size="small" @click="handleDelete(row)">删除</el-button>
|
<el-dropdown-menu>
|
||||||
</div>
|
<el-dropdown-item command="remind">提醒记录</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="send">发送提醒</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="delete" divided style="color:#F56C6C">删除商品</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
|
|
||||||
@@ -410,23 +415,31 @@
|
|||||||
<el-button type="primary" size="small" :icon="Refresh" @click="loadRemindList">刷新</el-button>
|
<el-button type="primary" size="small" :icon="Refresh" @click="loadRemindList">刷新</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="remindList" v-loading="remindLoading" stripe size="small" :max-height="400">
|
<el-table :data="remindList" v-loading="remindLoading" stripe size="small" :max-height="400">
|
||||||
<el-table-column prop="id" label="ID" width="70" />
|
<el-table-column prop="id" label="ID" width="60" />
|
||||||
<el-table-column prop="user_goods_id" label="用户商品ID" width="110" />
|
<el-table-column label="用户" width="100">
|
||||||
<el-table-column prop="user_id" label="用户ID" width="80" />
|
<template #default="{ row }">{{ row.user?.UserName || row.user?.user_name || `#${row.userId ?? row.user_id ?? '-'}` }}</template>
|
||||||
<el-table-column label="提醒类型" width="100">
|
</el-table-column>
|
||||||
|
<el-table-column label="提醒类型" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag size="small" :type="row.type === 'manual' ? 'warning' : 'info'">{{ row.type === 'manual' ? '手动' : '自动' }}</el-tag>
|
<el-tag size="small" :type="row.type === 'manual' ? 'warning' : 'info'">{{ row.type === 'manual' ? '手动' : '自动' }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="发送状态" width="90">
|
<el-table-column label="发送方式" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag size="small" :type="row.status === 'success' ? 'success' : row.status === 'failed' ? 'danger' : 'info'">{{ row.status || '-' }}</el-tag>
|
<el-tag size="small" :type="row.method === 'sms' ? 'success' : row.method === 'email' ? 'primary' : 'info'">{{ row.method === 'sms' ? '短信' : row.method === 'email' ? '邮件' : (row.method || '-') }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="发送状态" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small" :type="row.success ? 'success' : 'danger'">{{ row.success ? '成功' : '失败' }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="发送时间" min-width="160">
|
<el-table-column label="发送时间" min-width="160">
|
||||||
<template #default="{ row }">{{ row.created_at ? dayjs(row.created_at).format('YYYY-MM-DD HH:mm:ss') : (row.send_time || '-') }}</template>
|
<template #default="{ row }">{{ formatRemindTime(row) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="120" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.note || '-' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="message" label="内容" min-width="180" show-overflow-tooltip />
|
|
||||||
</el-table>
|
</el-table>
|
||||||
<div style="display:flex;justify-content:flex-end;margin-top:12px" v-if="remindTotal > remindQuery.count">
|
<div style="display:flex;justify-content:flex-end;margin-top:12px" v-if="remindTotal > remindQuery.count">
|
||||||
<el-pagination v-model:current-page="remindQuery.page" :page-size="remindQuery.count" :total="remindTotal" layout="total, prev, pager, next" small background @current-change="loadRemindList" />
|
<el-pagination v-model:current-page="remindQuery.page" :page-size="remindQuery.count" :total="remindTotal" layout="total, prev, pager, next" small background @current-change="loadRemindList" />
|
||||||
@@ -443,7 +456,7 @@
|
|||||||
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
import { ref, reactive, computed, onMounted, watch } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
|
import { Plus, Refresh, Search, ArrowDown } from '@element-plus/icons-vue'
|
||||||
import { getUserGoodsList, createUserGoods, updateUserGoods, deleteUserGoods, getUserVmList, getExpireRemindList, sendExpireRemind } from '@/api/admin/userVm'
|
import { getUserGoodsList, createUserGoods, updateUserGoods, deleteUserGoods, getUserVmList, getExpireRemindList, sendExpireRemind } from '@/api/admin/userVm'
|
||||||
import { extractApiError } from '@/utils/kvmErrorUtil'
|
import { extractApiError } from '@/utils/kvmErrorUtil'
|
||||||
import { formatToApiTime } from '@/utils/tool'
|
import { formatToApiTime } from '@/utils/tool'
|
||||||
@@ -828,6 +841,12 @@ const handleDetail = (row) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleMoreCmd = (cmd, row) => {
|
||||||
|
if (cmd === 'remind') openRemindList(row)
|
||||||
|
else if (cmd === 'send') handleSendRemind(row)
|
||||||
|
else if (cmd === 'delete') handleDelete(row)
|
||||||
|
}
|
||||||
|
|
||||||
const handleDelete = (row) => {
|
const handleDelete = (row) => {
|
||||||
ElMessageBox.confirm(`确定删除该用户商品吗?`, '删除确认', { type: 'warning' })
|
ElMessageBox.confirm(`确定删除该用户商品吗?`, '删除确认', { type: 'warning' })
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
@@ -854,6 +873,12 @@ const openRemindList = (row) => {
|
|||||||
loadRemindList()
|
loadRemindList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatRemindTime = (row) => {
|
||||||
|
const t = row.CreatedAt || row.created_at || row.send_time
|
||||||
|
if (!t || t.startsWith('0001')) return '-'
|
||||||
|
try { return dayjs(t).format('YYYY-MM-DD HH:mm:ss') } catch { return t }
|
||||||
|
}
|
||||||
|
|
||||||
const loadRemindList = async () => {
|
const loadRemindList = async () => {
|
||||||
remindLoading.value = true
|
remindLoading.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -48,7 +48,7 @@
|
|||||||
|
|
||||||
<div class="table-section">
|
<div class="table-section">
|
||||||
<el-alert v-if="listError" :title="listError" type="warning" :closable="false" show-icon style="margin:12px 20px 0" />
|
<el-alert v-if="listError" :title="listError" type="warning" :closable="false" show-icon style="margin:12px 20px 0" />
|
||||||
<el-table :data="list" v-loading="loading" stripe style="width:100%" @row-click="handleRowClick"
|
<el-table :data="list" v-loading="loading" stripe style="width:100%"
|
||||||
:header-cell-style="{ background: '#f8f9fa', color: '#2c3e50', fontWeight: 600, fontSize: '13px' }">
|
:header-cell-style="{ background: '#f8f9fa', color: '#2c3e50', fontWeight: 600, fontSize: '13px' }">
|
||||||
<el-table-column label="用户商品ID" width="110">
|
<el-table-column label="用户商品ID" width="110">
|
||||||
<template #default="{ row }">{{ row.id }}</template>
|
<template #default="{ row }">{{ row.id }}</template>
|
||||||
@@ -83,14 +83,21 @@
|
|||||||
<el-table-column label="到期时间" width="150">
|
<el-table-column label="到期时间" width="150">
|
||||||
<template #default="{ row }">{{ formatExpireTime(row.expireTime) }}</template>
|
<template #default="{ row }">{{ formatExpireTime(row.expireTime) }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="操作" width="380" fixed="right">
|
<el-table-column label="操作" width="200" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button v-if="row.itemId && row.itemId !== 0" link type="primary" size="small" @click="goDetail(row)">详情</el-button>
|
<el-button v-if="row.itemId && row.itemId !== 0" link type="primary" size="small" @click="goDetail(row)">详情</el-button>
|
||||||
<el-button v-if="row.itemId && row.itemId !== 0" link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
<el-button v-if="row.itemId && row.itemId !== 0" link type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
|
||||||
<el-button v-if="!row.itemId || row.itemId === 0" link type="success" size="small" @click="handleBindVm(row)">绑定虚拟机</el-button>
|
<el-button v-if="!row.itemId || row.itemId === 0" link type="success" size="small" @click="handleBindVm(row)">绑定虚拟机</el-button>
|
||||||
<el-button link type="warning" size="small" @click="openRemindList(row)">提醒记录</el-button>
|
<el-dropdown trigger="click" @command="cmd => handleMoreCmd(cmd, row)" style="margin-left:8px">
|
||||||
<el-button link type="success" size="small" @click="handleSendRemind(row)">发送提醒</el-button>
|
<el-button link type="primary" size="small" style="transform: translateY(4px);">更多<el-icon style="margin-left:2px;"><ArrowDown /></el-icon></el-button>
|
||||||
<el-button link type="danger" size="small" @click="handleDelete(row)" :disabled="!row.itemId || row.itemId === 0">删除</el-button>
|
<template #dropdown>
|
||||||
|
<el-dropdown-menu>
|
||||||
|
<el-dropdown-item command="remind">提醒记录</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="send">发送提醒</el-dropdown-item>
|
||||||
|
<el-dropdown-item command="delete" divided style="color:#F56C6C">删除商品</el-dropdown-item>
|
||||||
|
</el-dropdown-menu>
|
||||||
|
</template>
|
||||||
|
</el-dropdown>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
@@ -589,23 +596,31 @@
|
|||||||
<el-button type="primary" size="small" :icon="Refresh" @click="loadRemindList">刷新</el-button>
|
<el-button type="primary" size="small" :icon="Refresh" @click="loadRemindList">刷新</el-button>
|
||||||
</div>
|
</div>
|
||||||
<el-table :data="remindList" v-loading="remindLoading" stripe size="small" :max-height="400">
|
<el-table :data="remindList" v-loading="remindLoading" stripe size="small" :max-height="400">
|
||||||
<el-table-column prop="id" label="ID" width="70" />
|
<el-table-column prop="id" label="ID" width="60" />
|
||||||
<el-table-column prop="user_goods_id" label="用户商品ID" width="110" />
|
<el-table-column label="用户" width="100">
|
||||||
<el-table-column prop="user_id" label="用户ID" width="80" />
|
<template #default="{ row }">{{ row.user?.UserName || row.user?.user_name || `#${row.userId ?? row.user_id ?? '-'}` }}</template>
|
||||||
<el-table-column label="提醒类型" width="100">
|
</el-table-column>
|
||||||
|
<el-table-column label="提醒类型" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag size="small" :type="row.type === 'manual' ? 'warning' : 'info'">{{ row.type === 'manual' ? '手动' : '自动' }}</el-tag>
|
<el-tag size="small" :type="row.type === 'manual' ? 'warning' : 'info'">{{ row.type === 'manual' ? '手动' : '自动' }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="发送状态" width="90">
|
<el-table-column label="发送方式" width="80">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag size="small" :type="row.status === 'success' ? 'success' : row.status === 'failed' ? 'danger' : 'info'">{{ row.status || '-' }}</el-tag>
|
<el-tag size="small" :type="row.method === 'sms' ? 'success' : row.method === 'email' ? 'primary' : 'info'">{{ row.method === 'sms' ? '短信' : row.method === 'email' ? '邮件' : (row.method || '-') }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="发送状态" width="80">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-tag size="small" :type="row.success ? 'success' : 'danger'">{{ row.success ? '成功' : '失败' }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column label="发送时间" min-width="160">
|
<el-table-column label="发送时间" min-width="160">
|
||||||
<template #default="{ row }">{{ row.created_at ? dayjs(row.created_at).format('YYYY-MM-DD HH:mm:ss') : (row.send_time || '-') }}</template>
|
<template #default="{ row }">{{ formatRemindTime(row) }}</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="120" show-overflow-tooltip>
|
||||||
|
<template #default="{ row }">{{ row.note || '-' }}</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="message" label="内容" min-width="180" show-overflow-tooltip />
|
|
||||||
</el-table>
|
</el-table>
|
||||||
<div style="display:flex;justify-content:flex-end;margin-top:12px" v-if="remindTotal > remindQuery.count">
|
<div style="display:flex;justify-content:flex-end;margin-top:12px" v-if="remindTotal > remindQuery.count">
|
||||||
<el-pagination v-model:current-page="remindQuery.page" :page-size="remindQuery.count" :total="remindTotal" layout="total, prev, pager, next" small background @current-change="loadRemindList" />
|
<el-pagination v-model:current-page="remindQuery.page" :page-size="remindQuery.count" :total="remindTotal" layout="total, prev, pager, next" small background @current-change="loadRemindList" />
|
||||||
@@ -862,7 +877,7 @@
|
|||||||
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
import { ref, reactive, computed, onMounted, nextTick } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import { Plus, Refresh, Search } from '@element-plus/icons-vue'
|
import { Plus, Refresh, Search, ArrowDown } from '@element-plus/icons-vue'
|
||||||
import { getUserVmList, getUserVmDetail, createUserVm, updateUserVm, deleteUserVm, getUserGoodsList, createUserGoods, updateUserGoods, deleteUserGoods, bindUserVm, getExpireRemindList, sendExpireRemind } from '@/api/admin/userVm'
|
import { getUserVmList, getUserVmDetail, createUserVm, updateUserVm, deleteUserVm, getUserGoodsList, createUserGoods, updateUserGoods, deleteUserGoods, bindUserVm, getExpireRemindList, sendExpireRemind } from '@/api/admin/userVm'
|
||||||
import { getProductParameterList, getProductPlanDetail } from '@/api/admin/product'
|
import { getProductParameterList, getProductPlanDetail } from '@/api/admin/product'
|
||||||
import { hasUnit, getArgKey, getBaseUnit, getParamUnits, getParamDefaultUnit, toBaseUnit, fromBaseUnit } from '@/utils/dynamicUnit'
|
import { hasUnit, getArgKey, getBaseUnit, getParamUnits, getParamDefaultUnit, toBaseUnit, fromBaseUnit } from '@/utils/dynamicUnit'
|
||||||
@@ -944,27 +959,6 @@ const loadList = async () => {
|
|||||||
|
|
||||||
const handleSearch = () => { query.page = 1; loadList() }
|
const handleSearch = () => { query.page = 1; loadList() }
|
||||||
|
|
||||||
// 处理表格行点击事件
|
|
||||||
const handleRowClick = async (row) => {
|
|
||||||
if (!row.goodId) {
|
|
||||||
ElMessage.warning('该行没有商品ID')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 调用用户虚拟机列表API,传递当前行的goodId
|
|
||||||
const res = await getUserVmList({
|
|
||||||
page: 1,
|
|
||||||
count: 10,
|
|
||||||
good_id: row.goodId // 传递当前行的商品ID
|
|
||||||
})
|
|
||||||
if (res.data.code === 200) {
|
|
||||||
console.log('用户虚拟机列表:', res.data.data)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取用户虚拟机列表失败:', error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const goDetail = (row) => {
|
const goDetail = (row) => {
|
||||||
if (!row.id) return
|
if (!row.id) return
|
||||||
@@ -1512,13 +1506,23 @@ const getStatusText = (status) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- 删除 ----
|
// ---- 更多操作 ----
|
||||||
const handleDelete = (row) => {
|
const handleMoreCmd = (cmd, row) => {
|
||||||
if (!row.itemId) { ElMessage.warning('该用户商品未绑定虚拟机,无法删除'); return }
|
if (cmd === 'remind') openRemindList(row)
|
||||||
ElMessageBox.confirm('确定删除该用户虚拟机吗?此操作会同时删除远程VM和用户商品记录!', '删除确认', { type: 'error' })
|
else if (cmd === 'send') handleSendRemind(row)
|
||||||
|
else if (cmd === 'delete') handleDeleteGoods(row)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 删除用户商品 ----
|
||||||
|
const handleDeleteGoods = (row) => {
|
||||||
|
const hasBoundVm = row.itemId && row.itemId !== 0
|
||||||
|
const msg = hasBoundVm
|
||||||
|
? `该商品(ID: ${row.id})已绑定虚拟机(VM ID: ${row.itemId}),删除后虚拟机将不再关联此商品记录,确定删除?`
|
||||||
|
: `确定删除该用户商品(ID: ${row.id})吗?`
|
||||||
|
ElMessageBox.confirm(msg, '删除确认', { type: 'error', confirmButtonText: '确认删除' })
|
||||||
.then(async () => {
|
.then(async () => {
|
||||||
try {
|
try {
|
||||||
const res = await deleteUserVm({ user_goods_id: row.id })
|
const res = await deleteUserGoods({ id: row.id })
|
||||||
if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadList() }
|
if (res?.data?.code === 200) { ElMessage.success('删除成功'); loadList() }
|
||||||
else ElMessage.error(extractApiError(res?.data, '删除失败'))
|
else ElMessage.error(extractApiError(res?.data, '删除失败'))
|
||||||
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '删除失败')) }
|
} catch (e) { ElMessage.error(extractApiError(e?.response?.data, '删除失败')) }
|
||||||
@@ -1540,6 +1544,12 @@ const openRemindList = (row) => {
|
|||||||
loadRemindList()
|
loadRemindList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const formatRemindTime = (row) => {
|
||||||
|
const t = row.CreatedAt || row.created_at || row.send_time
|
||||||
|
if (!t || t.startsWith('0001')) return '-'
|
||||||
|
try { return dayjs(t).format('YYYY-MM-DD HH:mm:ss') } catch { return t }
|
||||||
|
}
|
||||||
|
|
||||||
const loadRemindList = async () => {
|
const loadRemindList = async () => {
|
||||||
remindLoading.value = true
|
remindLoading.value = true
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user