| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270 |
- <template>
- <div class="page-container" v-loading="loading">
- <div class="page-head">
- <div>
- <div class="crumb">
- <a class="link-back" @click="goBack">资源台账</a> <b>/</b> <b>资源详情</b>
- </div>
- <h1>{{ detail.name || '资源详情' }}</h1>
- </div>
- <div class="head-tools">
- <el-button type="primary" @click="openRenew">续费</el-button>
- <el-button @click="goBack">返回</el-button>
- </div>
- </div>
- <div class="page-card" v-if="detail.id">
- <div class="card-head"><h2>基础信息</h2></div>
- <el-descriptions :column="3" border>
- <el-descriptions-item label="资源名称">{{ detail.name }}</el-descriptions-item>
- <el-descriptions-item label="类型">{{ detail.typeName || '-' }}</el-descriptions-item>
- <el-descriptions-item label="服务商">{{ detail.providerName || '-' }}</el-descriptions-item>
- <el-descriptions-item label="账号信息">{{ detail.accountInfo || '-' }}</el-descriptions-item>
- <el-descriptions-item label="负责人">{{ detail.ownerName || '-' }}</el-descriptions-item>
- <el-descriptions-item label="状态">
- <span :class="['status-badge', statusClass(detail.status)]">{{ detail.statusText }}</span>
- </el-descriptions-item>
- <el-descriptions-item label="开通日期">{{ detail.startDate || '-' }}</el-descriptions-item>
- <el-descriptions-item label="到期日期">{{ detail.expireDate || '-' }}</el-descriptions-item>
- <el-descriptions-item label="费用">¥{{ fmtMoney(detail.cost) }}</el-descriptions-item>
- <el-descriptions-item label="计费周期">{{ costCycleText(detail.costCycle) }}</el-descriptions-item>
- <el-descriptions-item label="购买时长">{{ detail.purchaseDurationText || '-' }}</el-descriptions-item>
- <el-descriptions-item label="关联项目">
- <el-tag v-for="p in detail.projects" :key="p.id" size="small" style="margin-right: 4px">
- {{ p.name }} ({{ p.allocationPercent != null ? p.allocationPercent : 100 }}%)
- </el-tag>
- <span v-if="!detail.projects?.length" class="muted-tip">-</span>
- </el-descriptions-item>
- <el-descriptions-item label="备注" :span="3">{{ detail.remark || '-' }}</el-descriptions-item>
- </el-descriptions>
- </div>
- <div class="page-card">
- <div class="card-head">
- <h2>续费历史</h2>
- <span class="hint">共 {{ renewals.length }} 次</span>
- </div>
- <el-table :data="renewals" v-loading="renewLoading">
- <el-table-column label="序号" width="60" header-align="right" align="right">
- <template #default="{ $index }">{{ $index + 1 }}</template>
- </el-table-column>
- <el-table-column prop="renewDate" label="续费日期" width="130" />
- <el-table-column label="到期日变更" min-width="200">
- <template #default="{ row }">
- <span class="mono-num">{{ row.oldExpireDate || '-' }}</span>
- <span class="arrow"> → </span>
- <span class="mono-num renew-target">{{ row.newExpireDate }}</span>
- </template>
- </el-table-column>
- <el-table-column label="续费时长" width="110">
- <template #default="{ row }">{{ durationText(row.durationCode) }}</template>
- </el-table-column>
- <el-table-column label="续费金额" width="120" header-align="right" align="right">
- <template #default="{ row }"><span class="mono-num">¥{{ fmtMoney(row.cost) }}</span></template>
- </el-table-column>
- <el-table-column prop="remark" label="备注" min-width="140" show-overflow-tooltip />
- <el-table-column label="操作人" width="100">
- <template #default="{ row }">{{ operatorName(row.operatorId) }}</template>
- </el-table-column>
- <el-table-column label="操作" width="80" fixed="right">
- <template #default="{ row }">
- <el-button type="danger" link size="small" @click="deleteRenewal(row)">删除</el-button>
- </template>
- </el-table-column>
- </el-table>
- <el-empty v-if="!renewLoading && renewals.length === 0" description="暂无续费记录" :image-size="80" />
- </div>
- <el-dialog v-model="renewVisible" title="资源续费" width="520px" @closed="resetRenewForm">
- <el-form :model="renewForm" :rules="renewRules" ref="renewRef" label-width="90px">
- <el-form-item label="续费日期">
- <el-date-picker v-model="renewForm.renewDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
- </el-form-item>
- <el-form-item label="新到期日期" prop="newExpireDate">
- <el-date-picker v-model="renewForm.newExpireDate" type="date" value-format="YYYY-MM-DD" style="width: 100%" />
- </el-form-item>
- <el-form-item label="续费时长">
- <el-select v-model="renewForm.durationCode" placeholder="请选择" style="width: 100%" clearable>
- <el-option label="1个月" :value="1" />
- <el-option label="2个月" :value="2" />
- <el-option label="3个月" :value="3" />
- <el-option label="6个月" :value="4" />
- <el-option label="1年" :value="5" />
- <el-option label="2年" :value="6" />
- <el-option label="3年" :value="7" />
- </el-select>
- </el-form-item>
- <el-form-item label="续费金额">
- <el-input-number v-model="renewForm.cost" :min="0" :precision="2" style="width: 100%" />
- </el-form-item>
- <el-form-item label="备注">
- <el-input v-model="renewForm.remark" type="textarea" :rows="2" />
- </el-form-item>
- </el-form>
- <template #footer>
- <el-button @click="renewVisible = false">取消</el-button>
- <el-button type="primary" :loading="renewing" @click="submitRenew">确定续费</el-button>
- </template>
- </el-dialog>
- </div>
- </template>
- <script setup>
- import { ref, reactive, onMounted } from 'vue'
- import { useRoute, useRouter } from 'vue-router'
- import { ElMessage } from 'element-plus'
- import { resourceDetailApi, resourceRenewApi, resourceRenewalsApi, resourceDeleteRenewalApi } from '@/api/resource'
- import { userEnabledListApi } from '@/api/user'
- import { resourceStatusClass } from '@/utils/status'
- const route = useRoute()
- const router = useRouter()
- const resourceId = Number(route.params.id)
- const detail = reactive({})
- const renewals = ref([])
- const loading = ref(false)
- const renewLoading = ref(false)
- const userOptions = ref([])
- const renewVisible = ref(false)
- const renewing = ref(false)
- const renewRef = ref(null)
- const renewForm = reactive({
- renewDate: todayStr(),
- newExpireDate: '',
- durationCode: 5,
- cost: 0,
- remark: ''
- })
- const renewRules = {
- newExpireDate: [{ required: true, message: '请选择新到期日期', trigger: 'change' }]
- }
- function todayStr() {
- const d = new Date()
- const p = (n) => String(n).padStart(2, '0')
- return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
- }
- function fmtMoney(v) {
- return Number(v || 0).toLocaleString('zh-CN', { minimumFractionDigits: 2, maximumFractionDigits: 2 })
- }
- function costCycleText(c) {
- return c === 1 ? '月付' : c === 2 ? '年付' : c === 3 ? '一次性' : '-'
- }
- function durationText(code) {
- const map = { 1: '1个月', 2: '2个月', 3: '3个月', 4: '6个月', 5: '1年', 6: '2年', 7: '3年' }
- return code != null ? (map[code] || '-') : '-'
- }
- function statusClass(s) {
- return resourceStatusClass(s)
- }
- function operatorName(id) {
- if (id == null) return '-'
- return userOptions.value.find((u) => u.id === id)?.realName || `用户${id}`
- }
- function goBack() {
- router.push('/resource')
- }
- async function loadDetail() {
- loading.value = true
- try {
- const res = await resourceDetailApi(resourceId)
- Object.assign(detail, res.data)
- } finally {
- loading.value = false
- }
- }
- async function loadRenewals() {
- renewLoading.value = true
- try {
- const res = await resourceRenewalsApi(resourceId)
- renewals.value = res.data || []
- } finally {
- renewLoading.value = false
- }
- }
- function openRenew() {
- renewForm.renewDate = todayStr()
- // 默认顺延一年作为便利初值, 用户可改
- renewForm.newExpireDate = detail.expireDate ? addYears(detail.expireDate, 1) : addYears(todayStr(), 1)
- renewForm.durationCode = 5
- renewForm.cost = detail.cost || 0
- renewForm.remark = ''
- renewVisible.value = true
- }
- function addYears(dateStr, years) {
- const d = new Date(dateStr)
- d.setFullYear(d.getFullYear() + years)
- const p = (n) => String(n).padStart(2, '0')
- return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())}`
- }
- function resetRenewForm() {
- renewRef.value?.clearValidate()
- }
- async function submitRenew() {
- const valid = await renewRef.value.validate().catch(() => false)
- if (!valid) return
- renewing.value = true
- try {
- await resourceRenewApi(resourceId, { ...renewForm })
- ElMessage.success('续费成功')
- renewVisible.value = false
- await Promise.all([loadDetail(), loadRenewals()])
- } finally {
- renewing.value = false
- }
- }
- async function deleteRenewal(row) {
- try {
- await ElMessageBox.confirm(
- `确定删除这条续费记录吗?\n续费至 ${row.newExpireDate} ,金额 ¥${fmtMoney(row.cost)}。\n删除后资源的到期日将按剩余续费记录回滚。`,
- '删除续费记录',
- { type: 'warning', confirmButtonText: '删除', cancelButtonText: '取消' }
- )
- } catch {
- return
- }
- try {
- await resourceDeleteRenewalApi(resourceId, row.id)
- ElMessage.success('已删除续费记录')
- await Promise.all([loadDetail(), loadRenewals()])
- } catch (e) {
- ElMessage.error(e?.response?.data?.message || '删除失败')
- }
- }
- onMounted(async () => {
- await Promise.all([loadDetail(), loadRenewals()])
- const res = await userEnabledListApi().catch(() => ({ data: [] }))
- userOptions.value = res.data || []
- })
- </script>
- <style scoped>
- .link-back {
- color: var(--color-primary);
- cursor: pointer;
- }
- .link-back:hover {
- text-decoration: underline;
- }
- .muted-tip {
- color: var(--color-text-placeholder);
- font-size: 12px;
- }
- .mono-num {
- font-variant-numeric: tabular-nums;
- }
- .arrow {
- color: var(--color-text-placeholder);
- }
- .renew-target {
- color: var(--color-primary);
- font-weight: 600;
- }
- </style>
|