|
|
@@ -0,0 +1,244 @@
|
|
|
+<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 }}</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>
|
|
|
+ <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 } 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
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+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>
|