feat: 编辑时不可修改订阅计划状态&布局修改

This commit is contained in:
2025-12-17 14:02:58 +08:00
parent 5a7e5e92a8
commit b160709f16

View File

@@ -79,56 +79,61 @@
<a-card class="table-card" :bordered="false"> <a-card class="table-card" :bordered="false">
<div class="table-card__header"> <div class="table-card__header">
<div class="table-card__title">Subscription Plan</div>
<a-button type="primary" @click="openCreate">New Subscription Plan</a-button> <a-button type="primary" @click="openCreate">New Subscription Plan</a-button>
</div> </div>
<a-table <div ref="historyTable" class="table-wrapper">
:data-source="tableData" <a-table
:columns="columns" :data-source="tableData"
:loading="tableLoading" :columns="columns"
row-key="id" :loading="tableLoading"
:pagination="{ :bordered="false"
showSizeChanger: true, row-key="id"
current: searchForm.page, @change="changePage"
pageSize: searchForm.size, @resizeColumn="handleResizeColumn"
total: searchForm.total, :scroll="{ y: historyTableHeight }"
showQuickJumper: true, :pagination="{
bordered: false showSizeChanger: true,
}" current: searchForm.page,
> pageSize: searchForm.size,
<template #bodyCell="{ column, record }"> total: searchForm.total,
<template showQuickJumper: true,
v-if=" bordered: false
column.key === 'currentPeriodStart' || column.key === 'currentPeriodEnd' }"
" >
> <template #bodyCell="{ column, record }">
{{ formatTime(record[column.key], 'YYYY-MM-DD hh:mm:ss') }} <template
</template> v-if="
column.key === 'currentPeriodStart' || column.key === 'currentPeriodEnd'
"
>
{{ formatTime(record[column.key], 'YYYY-MM-DD hh:mm:ss') }}
</template>
<template v-if="column.key === 'status'"> <template v-if="column.key === 'status'">
<a-tag :color="getStatusColor(record.status)"> <a-tag :color="getStatusColor(record.status)">
{{ record.status }} {{ record.status }}
</a-tag> </a-tag>
</template> </template>
<template v-if="column.key === 'adminAccId'"> <template v-if="column.key === 'adminAccId'">
{{ allUserList.find(item => item.value === record.adminAccId)?.label }} {{ allUserList.find(item => item.value === record.adminAccId)?.label }}
</template> </template>
<template v-else-if="column.key === 'actions'"> <template v-else-if="column.key === 'actions'">
<a-space> <a-space>
<a @click="openEdit(record)">Edit</a> <a @click="openEdit(record)">Edit</a>
<a-popconfirm <a-popconfirm
title="Confirm to delete this subscription plan?" title="Confirm to delete this subscription plan?"
ok-text="Confirm" ok-text="Confirm"
cancel-text="Cancel" cancel-text="Cancel"
@confirm="removePlan(record.id)" @confirm="removePlan(record.id)"
> >
<a class="danger-text">Delete</a> <a class="danger-text">Delete</a>
</a-popconfirm> </a-popconfirm>
</a-space> </a-space>
</template>
</template> </template>
</template> </a-table>
</a-table> </div>
</a-card> </a-card>
<div class="subscriptionPlanModal" ref="subscriptionPlanModal"></div> <div class="subscriptionPlanModal" ref="subscriptionPlanModal"></div>
@@ -182,7 +187,7 @@
</div> </div>
<div class="subscriptionPlan_center admin_page"> <div class="subscriptionPlan_center admin_page">
<div class="form_content"> <div class="form_content">
<div class="admin_state_item"> <div class="admin_state_item" v-if="!isEditMode">
<span> <span>
Name: Name:
<span>*</span> <span>*</span>
@@ -191,7 +196,6 @@
v-model:value="formState.name" v-model:value="formState.name"
placeholder="Input the name" placeholder="Input the name"
style="width: 250px" style="width: 250px"
:disabled="isEditMode"
/> />
</div> </div>
<div class="admin_state_item"> <div class="admin_state_item">
@@ -293,7 +297,7 @@
placeholder="Input the credit limit" placeholder="Input the credit limit"
/> />
</div> </div>
<div class="admin_state_item"> <div class="admin_state_item" v-if="!isEditMode">
<span>Status:</span> <span>Status:</span>
<a-select <a-select
v-model:value="formState.status" v-model:value="formState.status"
@@ -369,7 +373,7 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { reactive, ref, onMounted, computed, nextTick } from 'vue' import { reactive, ref, onMounted, onBeforeUnmount, computed, nextTick, useTemplateRef } from 'vue'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { Https } from '@/tool/https' import { Https } from '@/tool/https'
import { formatTime } from '@/tool/util' import { formatTime } from '@/tool/util'
@@ -466,28 +470,69 @@ const getStatusColor = (status?: string) =>
statusColorMap[normalizeStatus(status) as PlanStatus] || 'default' statusColorMap[normalizeStatus(status) as PlanStatus] || 'default'
const columns = [ const columns = [
{ title: 'Name', dataIndex: 'name', key: 'name' }, { title: 'Name', dataIndex: 'name', key: 'name', align: 'center',width: 180 },
{ title: 'ID', dataIndex: 'id', key: 'id' }, { title: 'ID', dataIndex: 'id', key: 'id', align: 'center' ,width: 80},
{ title: 'Organization', dataIndex: 'organizationName', key: 'organizationName' }, {
{ title: 'Admin Account', dataIndex: 'adminAccId', key: 'adminAccId' }, title: 'Organization',
{ title: 'Account Num', dataIndex: 'accountNum', key: 'accountNum' }, dataIndex: 'organizationName',
key: 'organizationName',
align: 'center',
width: 180
},
{ title: 'Admin Account', dataIndex: 'adminAccId', key: 'adminAccId', align: 'center' ,width: 180},
{ title: 'Account Num', dataIndex: 'accountNum', key: 'accountNum', align: 'center' ,width: 120},
{ {
title: 'Start Time', title: 'Start Time',
dataIndex: 'currentPeriodStart', dataIndex: 'currentPeriodStart',
key: 'currentPeriodStart' key: 'currentPeriodStart',
align: 'center',
width:200
}, },
{ {
title: 'End Time', title: 'End Time',
dataIndex: 'currentPeriodEnd', dataIndex: 'currentPeriodEnd',
key: 'currentPeriodEnd' key: 'currentPeriodEnd',
align: 'center',
width:200
}, },
{ title: 'Status', dataIndex: 'status', key: 'status' }, { title: 'Status', dataIndex: 'status', key: 'status', align: 'center' ,width: 100},
{ title: 'Credit Limit', dataIndex: 'creditLimit', key: 'creditLimit' }, {
{ title: 'Operations', key: 'actions', width: 160 } title: 'Credit Limit',
dataIndex: 'creditLimit',
key: 'creditLimit',
align: 'center',
width: 120
},
{ title: 'Operations', key: 'actions', width: 160, align: 'center', fixed: 'right' }
] ]
const historyTable = ref()
const historyTableHeight = ref(0)
const handleResizeColumn = (w: any, col: any) => {
col.width = w
}
const calculateTableHeight = () => {
nextTick(() => {
if (historyTable.value) {
historyTableHeight.value = historyTable.value.clientHeight - 200
}
})
}
const handleResize = () => {
calculateTableHeight()
}
onMounted(async () => { onMounted(async () => {
await getOrganizationList() await getOrganizationList()
await handleSearch() await handleSearch()
calculateTableHeight()
window.addEventListener('resize', handleResize)
})
onBeforeUnmount(() => {
window.removeEventListener('resize', handleResize)
}) })
const handleFetchTableData = async () => { const handleFetchTableData = async () => {
@@ -513,6 +558,12 @@ const resetFormState = () => {
formState.status = undefined formState.status = undefined
} }
const changePage = (pagination: any) => {
searchForm.page = pagination.current
searchForm.size = pagination.pageSize
handleFetchTableData()
}
const handleSearch = () => { const handleSearch = () => {
searchForm.page = 1 searchForm.page = 1
handleFetchTableData() handleFetchTableData()
@@ -555,7 +606,9 @@ const openEdit = (record: SubscriptionPlan) => {
// 检查组织ID是否在已加载的组织列表中如果不在则添加临时项 // 检查组织ID是否在已加载的组织列表中如果不在则添加临时项
if (record.organizationId) { if (record.organizationId) {
const orgExists = organizationOptions.value.some( const orgExists = organizationOptions.value.some(
(org: any) => org.id === record.organizationId || String(org.id) === String(record.organizationId) (org: any) =>
org.id === record.organizationId ||
String(org.id) === String(record.organizationId)
) )
if (!orgExists) { if (!orgExists) {
// 从表格数据中获取组织名称,如果存在则添加临时项 // 从表格数据中获取组织名称,如果存在则添加临时项
@@ -695,25 +748,24 @@ const getOrganizationList = async (isLoadMore = false) => {
} }
organizationLoading.value = true organizationLoading.value = true
try { try {
const rv: any = await Https.axiosPost( const { total, ...requestParams } = organizationParams
Https.httpUrls.queryOrganization, const rv: any = await Https.axiosPost(Https.httpUrls.queryOrganization, requestParams)
organizationParams
)
if (rv) { if (rv) {
const newRecords = rv.records || [] const newRecords = rv.records || []
// 去重如果新数据中包含已存在的组织ID则移除旧项包括临时项保留新项 // 遍历新数据,如果已存在则覆盖,不存在则追加
const existingIds = new Set( newRecords.forEach((newOrg: any) => {
organizationOptions.value.map((org: any) => String(org.id)) const newOrgId = String(newOrg.id)
) const existingIndex = organizationOptions.value.findIndex(
const newIds = new Set(newRecords.map((org: any) => String(org.id))) (org: any) => String(org.id) === newOrgId
)
// 移除会被新数据覆盖的旧项(包括临时项) if (existingIndex !== -1) {
organizationOptions.value = organizationOptions.value.filter( // 如果已存在,用新数据覆盖旧项
(org: any) => !newIds.has(String(org.id)) organizationOptions.value[existingIndex] = newOrg
) } else {
// 如果不存在,追加到末尾
// 追加新数据 organizationOptions.value.push(newOrg)
organizationOptions.value = [...organizationOptions.value, ...newRecords] }
})
organizationParams.total = rv.total || 0 organizationParams.total = rv.total || 0
} }
} finally { } finally {
@@ -802,33 +854,64 @@ const filterOption = (input: string, option: any) => {
<style lang="less" scoped> <style lang="less" scoped>
.subscription-plan { .subscription-plan {
padding: 20px 24px 32px 0; padding: 2rem 2.4rem 3.2rem 0;
display: flex;
height: 100%;
flex-direction: column;
.search-card { .search-card {
margin-bottom: 16px; margin-bottom: 1.6rem;
} }
.table-card { .table-card {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
:deep(.ant-card-body) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
padding: 2.4rem;
}
.table-card__header { .table-card__header {
display: flex; display: flex;
justify-content: space-between; justify-content: flex-end;
align-items: center; margin-bottom: 2.6rem;
margin-bottom: 12px; flex-shrink: 0;
}
.table-card__title { .table-wrapper {
font-size: 18px; flex: 1;
font-weight: 500; overflow: hidden;
} min-height: 0;
} }
.danger-text { .danger-text {
color: #ff4d4f; color: #ff4d4f;
} }
:deep(.ant-table-cell::before) {
display: none;
}
:deep(.ant-table-thead > tr > th) {
border-bottom: none;
}
:deep(.ant-table-tbody > tr > td) {
border: none;
}
:deep(.ant-table-tbody > tr:hover > td) {
background: rgb(202, 202, 202);
}
} }
} }
:deep(.subscriptionPlan_modal) { :deep(.subscriptionPlan_modal) {
.ant-modal-body { .ant-modal-body {
height: calc(65rem * 1.2); // height: calc(65rem * 1.2);
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 2.5rem 3rem; padding: 2.5rem 3rem;