Files
one-pipe-system/src/views/package-management/series-grants/packages.vue
sexygoat 62851342c3
All checks were successful
构建并部署前端到测试环境 / build-and-deploy (push) Successful in 6m11s
fix: permission
2026-04-24 09:55:44 +08:00

445 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<ArtTableFullScreen>
<div class="grant-package-list-page">
<ElCard shadow="never" class="art-table-card">
<div class="page-header">
<div class="header-left">
<ElButton @click="handleBack">
<template #icon>
<ElIcon><ArrowLeft /></ElIcon>
</template>
返回
</ElButton>
<span class="grant-name">{{ grantName }}</span>
</div>
<ElButton
type="primary"
@click="showAddPackageDialog"
v-permission="'series_grants:manage_packages_list'"
>
添加授权套餐
</ElButton>
</div>
<ArtTable
ref="tableRef"
row-key="package_id"
:loading="loading"
:data="packageList"
:marginTop="10"
:actions="getActions"
:actionsWidth="150"
:inlineActionsCount="2"
>
<template #default>
<ElTableColumn prop="package_name" label="套餐名称" minWidth="150" />
<ElTableColumn prop="package_code" label="套餐编码" minWidth="120" />
<ElTableColumn label="成本价" width="120">
<template #default="{ row }">
<span class="amount-value">¥{{ (row.cost_price / 100).toFixed(2) }}</span>
</template>
</ElTableColumn>
<ElTableColumn label="上架状态" width="100" align="center">
<template #default="{ row }">
<ElTag v-if="row.shelf_status === 1" type="success" size="small">上架</ElTag>
<ElTag v-else-if="row.shelf_status === 2" type="info" size="small">下架</ElTag>
<span v-else>-</span>
</template>
</ElTableColumn>
<ElTableColumn label="状态" width="100" align="center">
<template #default="{ row }">
<ElTag v-if="row.status === 1" type="success" size="small">启用</ElTag>
<ElTag v-else-if="row.status === 2" type="danger" size="small">禁用</ElTag>
<span v-else>-</span>
</template>
</ElTableColumn>
</template>
</ArtTable>
</ElCard>
<ElDialog
v-model="packageDialogVisible"
:title="packageDialogType === 'add' ? '添加套餐' : '编辑套餐'"
width="500px"
:close-on-click-modal="false"
@closed="handlePackageDialogClosed"
>
<ElForm ref="packageFormRef" :model="packageForm" :rules="packageRules" label-width="100px">
<ElFormItem label="选择套餐" prop="package_id" v-if="packageDialogType === 'add'">
<ElSelect
v-model="packageForm.package_id"
placeholder="请选择套餐"
style="width: 100%"
filterable
remote
:remote-method="searchAvailablePackages"
:loading="packageLoading"
clearable
>
<template v-if="availablePackages.length === 0 && !packageLoading && seriesId">
<ElOption disabled value="" label="该系列没有可选套餐" />
</template>
<ElOption
v-for="pkg in availablePackages"
:key="pkg.id"
:label="`${pkg.package_name} (${pkg.package_code})`"
:value="pkg.id"
:disabled="isPackageAlreadyAdded(pkg.id)"
/>
</ElSelect>
</ElFormItem>
<ElFormItem label="套餐名称" v-if="packageDialogType === 'edit'">
<span>{{ packageForm.package_name }}</span>
</ElFormItem>
<ElFormItem label="套餐编码" v-if="packageDialogType === 'edit'">
<span>{{ packageForm.package_code }}</span>
</ElFormItem>
<ElFormItem label="成本价(元)" prop="cost_price_yuan">
<ElInputNumber
v-model="packageForm.cost_price_yuan"
:min="packageForm.original_cost_price || 0"
:max="
packageForm.original_cost_price ? packageForm.original_cost_price * 1.5 : undefined
"
:precision="2"
:step="0.01"
:controls="false"
style="width: 100%"
placeholder="请输入成本价"
/>
<div v-if="packageForm.original_cost_price" class="form-tip">
原价: ¥{{ packageForm.original_cost_price.toFixed(2) }}, 最高: ¥{{
(packageForm.original_cost_price * 1.5).toFixed(2)
}}
(50%溢价)
</div>
</ElFormItem>
</ElForm>
<template #footer>
<div class="dialog-footer">
<ElButton @click="packageDialogVisible = false">取消</ElButton>
<ElButton type="primary" @click="handleSavePackage" :loading="submitLoading">
保存
</ElButton>
</div>
</template>
</ElDialog>
</div>
</ArtTableFullScreen>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import {
ElCard,
ElButton,
ElIcon,
ElTable,
ElTableColumn,
ElTag,
ElDialog,
ElSelect,
ElOption,
ElInputNumber,
ElForm,
ElFormItem,
FormInstance,
FormRules
} from 'element-plus'
import { ArrowLeft } from '@element-plus/icons-vue'
import { ShopSeriesGrantService, PackageManageService } from '@/api/modules'
import type { GrantPackageInfo, GrantPackageItem, PackageResponse } from '@/types/api'
import { useAuth } from '@/composables/useAuth'
defineOptions({ name: 'SeriesGrantPackages' })
const route = useRoute()
const router = useRouter()
const { hasAuth } = useAuth()
const loading = ref(false)
const submitLoading = ref(false)
const packageLoading = ref(false)
const tableRef = ref()
const grantId = computed(() => Number(route.params.id))
const seriesId = ref<number>(0)
const grantName = ref<string>('')
const packageList = ref<GrantPackageInfo[]>([])
const availablePackages = ref<PackageResponse[]>([])
const packageFormRef = ref<FormInstance>()
const packageDialogVisible = ref(false)
const packageDialogType = ref<'add' | 'edit'>('add')
const packageForm = ref<{
package_id?: number
package_name?: string
package_code?: string
cost_price_yuan: number
original_cost_price?: number
}>({
cost_price_yuan: 0
})
const packageRules = computed<FormRules>(() => ({
package_id: [
{ required: packageDialogType.value === 'add', message: '请选择套餐', trigger: 'change' }
],
cost_price_yuan: [
{ required: true, message: '请输入成本价', trigger: 'blur' },
{
validator: (rule, value, callback) => {
if (value === undefined || value === null || value === '') {
callback(new Error('请输入成本价'))
} else if (value < 0) {
callback(new Error('成本价不能小于0'))
} else if (
packageForm.value.original_cost_price &&
value < packageForm.value.original_cost_price
) {
callback(
new Error(`成本价不能低于原价 ¥${packageForm.value.original_cost_price.toFixed(2)}`)
)
} else if (
packageForm.value.original_cost_price &&
value > packageForm.value.original_cost_price * 1.5
) {
callback(
new Error(
`成本价不能高于原价的50%溢价(最高:¥${(packageForm.value.original_cost_price * 1.5).toFixed(2)}`
)
)
} else {
callback()
}
},
trigger: 'blur'
}
]
}))
const getActions = (row: GrantPackageInfo) => {
const actions: any[] = []
if (hasAuth('series_grants:edit_packages_list')) {
actions.push({
label: '编辑',
handler: () => showEditPackageDialog(row),
type: 'primary'
})
}
if (hasAuth('series_grants:delete_packages_list')) {
actions.push({
label: '删除',
handler: () => handleDeletePackage(row),
type: 'danger'
})
}
return actions
}
const handleBack = () => {
router.back()
}
const fetchPackageList = async () => {
if (!grantId.value) return
loading.value = true
try {
const res = await ShopSeriesGrantService.getShopSeriesGrantDetail(grantId.value)
if (res.code === 0) {
packageList.value = res.data.packages || []
seriesId.value = res.data.series_id
grantName.value = `${res.data.series_name} - ${res.data.shop_name}`
}
} catch (error) {
console.error('获取套餐列表失败:', error)
} finally {
loading.value = false
}
}
const showAddPackageDialog = () => {
packageDialogType.value = 'add'
packageForm.value = {
package_id: undefined,
cost_price_yuan: 0
}
loadAvailablePackages()
packageDialogVisible.value = true
}
const showEditPackageDialog = (row: GrantPackageInfo) => {
packageDialogType.value = 'edit'
const pkgOption = availablePackages.value.find((p) => p.id === row.package_id)
const originalCostPrice = pkgOption?.cost_price ? pkgOption.cost_price / 100 : 0
packageForm.value = {
package_id: row.package_id,
package_name: row.package_name,
package_code: row.package_code,
cost_price_yuan: row.cost_price / 100,
original_cost_price: originalCostPrice
}
packageDialogVisible.value = true
}
const loadAvailablePackages = async (packageName?: string) => {
if (!seriesId.value) return
packageLoading.value = true
try {
const params: any = {
page: 1,
page_size: 50,
series_id: seriesId.value
}
if (packageName) {
params.package_name = packageName
}
const res = await PackageManageService.getPackages(params)
if (res.code === 0) {
availablePackages.value = res.data.items
}
} catch (error) {
console.error('加载套餐选项失败:', error)
} finally {
packageLoading.value = false
}
}
const searchAvailablePackages = (query: string) => {
if (query) {
loadAvailablePackages(query)
} else {
loadAvailablePackages()
}
}
const isPackageAlreadyAdded = (packageId: number) => {
return packageList.value.some((p) => p.package_id === packageId)
}
const handleSavePackage = async () => {
if (!packageFormRef.value || !grantId.value) return
await packageFormRef.value.validate(async (valid) => {
if (!valid) return
submitLoading.value = true
try {
const packages: GrantPackageItem[] = []
if (packageDialogType.value === 'add') {
packages.push({
package_id: packageForm.value.package_id,
cost_price: Math.round(packageForm.value.cost_price_yuan * 100)
})
} else {
packages.push({
package_id: packageForm.value.package_id,
cost_price: Math.round(packageForm.value.cost_price_yuan * 100)
})
}
await ShopSeriesGrantService.manageGrantPackages(grantId.value, { packages })
packageDialogVisible.value = false
await fetchPackageList()
} catch (error) {
console.error(error)
} finally {
submitLoading.value = false
}
})
}
const handleDeletePackage = (row: GrantPackageInfo) => {
import('element-plus').then(({ ElMessageBox }) => {
ElMessageBox.confirm(`确定删除套餐 ${row.package_name} 的授权吗?`, '删除确认', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
.then(async () => {
submitLoading.value = true
try {
const packages: GrantPackageItem[] = [
{
package_id: row.package_id,
remove: true
}
]
await ShopSeriesGrantService.manageGrantPackages(grantId.value, { packages })
await fetchPackageList()
} catch (error) {
console.error(error)
} finally {
submitLoading.value = false
}
})
.catch(() => {})
})
}
const handlePackageDialogClosed = () => {
packageFormRef.value?.resetFields()
packageForm.value = {
cost_price_yuan: 0
}
availablePackages.value = []
}
onMounted(() => {
fetchPackageList()
})
</script>
<style scoped lang="scss">
.grant-package-list-page {
padding: 16px;
.page-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 16px;
.header-left {
display: flex;
align-items: center;
gap: 16px;
}
.grant-name {
font-weight: 600;
font-size: 16px;
color: var(--el-text-color-primary);
}
}
.amount-value {
font-weight: 600;
color: var(--el-color-warning);
}
.form-tip {
font-size: 12px;
color: var(--el-text-color-secondary);
margin-top: 4px;
}
.dialog-footer {
text-align: right;
}
}
</style>