Explorar el Código

应用模块调整

“陈博论” hace 10 meses
padre
commit
4446e4a96d

+ 1 - 1
web/.env.development

@@ -5,4 +5,4 @@ ENV = 'development'
 VITE_BASE_PATH = './'
 
 # 本地环境接口地址 - 尾部无需带'/'
-VITE_AXIOS_BASE_URL = 'http://user.adjdds.com'
+VITE_AXIOS_BASE_URL = 'https://user.adjdds.com'

+ 17 - 12
web/src/api/backend/package/product.ts

@@ -3,7 +3,7 @@ import createAxios from '/@/utils/axios'
 // 列表
 export function list(data: anyObj) {
     return createAxios({
-        url: '/admin/Package.product/index',
+        url: '/admin/package.product/index',
         method: 'get',
         params: data,
     })
@@ -12,34 +12,39 @@ export function list(data: anyObj) {
 // 添加、编辑
 export function edit(data: anyObj) {
     return createAxios({
-        url: '/admin/Package.product/edit',
+        url: '/admin/package.product/edit',
         method: 'post',
         data: data,
     })
 }
 
 // 删除
-export function del(data:anyObj) {
+export function del(data: anyObj) {
     return createAxios({
-        url: '/admin/Package.product/delete',
+        url: '/admin/package.product/delete',
         method: 'post',
         data: data,
     })
 }
 
 // 更新产品状态
-export function batchStatus(data:anyObj) {
-    return createAxios({
-        url: '/admin/Package.product/batchStatus',
-        method: 'post',
-        data: data,
-    })
+export function batchStatus(data: anyObj) {
+    return createAxios(
+        {
+            url: '/admin/package.product/batchStatus',
+            method: 'post',
+            data: data,
+        },
+        {
+            showSuccessMessage: true
+        }
+    )
 }
 
 // 批量更新库存状态
-export function batchStockStatus(data:anyObj) {
+export function batchStockStatus(data: anyObj) {
     return createAxios({
-        url: '/admin/Package.product/batchStockStatus',
+        url: '/admin/package.product/batchStockStatus',
         method: 'post',
         data: data,
     })

+ 2 - 1
web/src/components/baInput/components/baUpload.vue

@@ -196,7 +196,8 @@ const onElChange = (file: UploadFileExt, files: UploadFiles) => {
     })
         .then((res) => {
             if (res.code == 1) {
-                file.serverUrl = res.data.file.url
+                // 如果 returnFullUrl 为 true,优先使用 full_url 字段,否则使用 url 字段
+                file.serverUrl = props.returnFullUrl && res.data.file.full_url ? res.data.file.full_url : res.data.file.url
                 file.status = 'success'
                 emits('update:modelValue', getAllUrls())
                 triggerEvent('success', [res, file, files])

+ 2 - 16
web/src/views/backend/package/package/index.vue

@@ -68,22 +68,6 @@ const baTable = new baTableClass(
                 align: 'center',
                 minWidth: 160,
             },
-            {
-                label: '套餐类型',
-                prop: 'type',
-                render: 'tag',
-                custom: {
-                    '1': 'primary',
-                    '2': 'success',
-                },
-                replaceValue: {
-                    '1': '个人套餐',
-                    '2': '企业套餐',
-                },
-                align: 'center',
-                minWidth: 140,
-            },
-            
             {
                 label: '状态',
                 prop: 'status',
@@ -174,6 +158,8 @@ const handleFormSuccess = async (submitData: any) => {
         const res = await edit({
             id: submitData.id,
             name: submitData.name,
+            description: submitData.description,
+            status: submitData.status,
         })
         
         // 如果是新增,获取返回的套餐ID,并切换到编辑模式

+ 57 - 11
web/src/views/backend/package/package/popupForm.vue

@@ -13,6 +13,12 @@
 					<el-form-item label="套餐名称" prop="name">
 						<el-input v-model="formData.name" placeholder="请输入套餐名称" />
 					</el-form-item>
+					<el-form-item label="状态" prop="status">
+						<el-radio-group v-model="formData.status">
+							<el-radio :value="1">启用</el-radio>
+							<el-radio :value="2">禁用</el-radio>
+						</el-radio-group>
+					</el-form-item>
 					<!-- 套餐规格列表 -->
 					<el-form-item label="套餐规格" v-if="currentEditId">
 						<div class="spec-section">
@@ -79,6 +85,14 @@
 							</div>
 						</div>
 					</el-form-item>
+					<el-form-item label="说明" prop="description">
+						<el-input
+							v-model="formData.description"
+							type="textarea"
+							:rows="3"
+							placeholder="请输入套餐说明"
+						/>
+					</el-form-item>
 				</el-form>
 			</div>
 		</el-scrollbar>
@@ -96,7 +110,7 @@
 </template>
 
 <script setup lang="ts">
-import { ref, reactive, watch } from 'vue'
+import { ref, reactive, watch, nextTick } from 'vue'
 import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
 import { useI18n } from 'vue-i18n'
 import { specList, deleteSpec } from '/@/api/backend/package/package'
@@ -113,6 +127,8 @@ const currentEditId = ref<number | null>(null)
 
 const loading = ref(false)
 const specLoading = ref(false)
+// 防止重复请求的标志
+const specListLoading = ref(false)
 
 const formRef = ref<FormInstance>()
 const specDialogRef = ref<InstanceType<typeof SpecDialog>>()
@@ -121,6 +137,8 @@ const emits = defineEmits(['success'])
 
 const formData = reactive({
 	name: '',
+	description: '',
+	status: 1, // 1.启用 2.禁用
 })
 
 // 规格列表
@@ -131,6 +149,9 @@ const rules: FormRules = {
 	name: [
 		{ required: true, message: '请输入套餐名称', trigger: 'change' }
 	],
+	status: [
+		{ required: true, message: '请选择状态', trigger: 'change' }
+	],
 }
 
 // 打开对话框(新增)
@@ -142,8 +163,7 @@ const openDialog = () => {
 }
 
 // 编辑:接收列表行数据,回填到表单
-const editDialog = (row: TableRow & { id?: number; name?: string }) => {
-	dialogVisible.value = true
+const editDialog = (row: TableRow & { id?: number; name?: string; description?: string; status?: number }) => {
 	dialogType.value = 'edit'
 	currentEditId.value = row.id ?? null
 
@@ -152,25 +172,33 @@ const editDialog = (row: TableRow & { id?: number; name?: string }) => {
 
 	// 回填基础信息
 	formData.name = row.name ?? ''
+	formData.description = row.description ?? ''
+	formData.status = row.status ?? 1
 
-	// 加载规格列表
-	if (currentEditId.value) {
-		loadSpecList()
-	}
+	// 最后设置 dialogVisible,让 watch 统一处理规格列表加载
+	dialogVisible.value = true
 }
 
 // 重置表单
 const resetForm = () => {
 	formRef.value?.clearValidate()
 	formData.name = ''
+	formData.description = ''
+	formData.status = 1
 	specListData.value = []
+	// 重置加载标志
+	specListLoading.value = false
 }
 
 // 加载规格列表
 const loadSpecList = async () => {
 	if (!currentEditId.value) return
 	
+	// 防止重复请求
+	if (specListLoading.value) return
+	specListLoading.value = true
 	specLoading.value = true
+	
 	try {
 		const res = await specList({ package_id: currentEditId.value })
 		if (res.data && Array.isArray(res.data)) {
@@ -179,6 +207,10 @@ const loadSpecList = async () => {
 			specListData.value = []
 		}
 	} catch (error: any) {
+		// 如果是请求被取消的错误,不显示错误消息
+		if (error?.message && error.message.includes('canceled')) {
+			return
+		}
 		ElMessage({
 			type: 'error',
 			message: error?.msg || '加载规格列表失败',
@@ -186,6 +218,7 @@ const loadSpecList = async () => {
 		specListData.value = []
 	} finally {
 		specLoading.value = false
+		specListLoading.value = false
 	}
 }
 
@@ -251,6 +284,8 @@ const onSubmit = async () => {
 			id: currentEditId.value,
 			type: dialogType.value,
 			name: formData.name,
+			description: formData.description,
+			status: formData.status,
 		}
 
 		emits('success', submitData)
@@ -281,10 +316,21 @@ const handleClose = () => {
 	resetForm()
 }
 
-// 监听对话框打开,如果是编辑模式且有ID,加载规格列表
-watch([dialogVisible, currentEditId], ([visible, id]) => {
-	if (visible && dialogType.value === 'edit' && id) {
-		loadSpecList()
+// 监听对话框打开和ID变化,如果是编辑模式且有ID,加载规格列表
+watch([dialogVisible, currentEditId], ([visible, id], [oldVisible, oldId]) => {
+	// 只在以下情况加载规格列表:
+	// 1. 对话框从关闭变为打开,且是编辑模式,且有ID
+	// 2. ID发生变化(例如从新增切换到编辑),且对话框是打开的,且是编辑模式
+	const shouldLoad = visible && 
+		dialogType.value === 'edit' && 
+		id && 
+		((!oldVisible && visible) || (id !== oldId && visible))
+	
+	if (shouldLoad) {
+		// 使用 nextTick 确保所有状态都已更新
+		nextTick(() => {
+			loadSpecList()
+		})
 	}
 })
 

+ 265 - 77
web/src/views/backend/package/package/specDialog.vue

@@ -1,7 +1,13 @@
 <template>
 	<!-- 规格对话框表单 -->
-	<el-dialog class="ba-operate-dialog" :close-on-click-modal="false" :destroy-on-close="true" v-model="dialogVisible"
-		@close="handleClose" width="700px">
+	<el-dialog 
+		class="ba-operate-dialog spec-dialog" 
+		:close-on-click-modal="false" 
+		:destroy-on-close="true" 
+		v-model="dialogVisible"
+		@close="handleClose" 
+		width="750px"
+	>
 		<template #header>
 			<div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
 				{{ dialogType === 'add' ? '添加规格' : '编辑规格' }}
@@ -9,81 +15,127 @@
 		</template>
 		<el-scrollbar v-loading="loading" class="ba-table-form-scrollbar">
 			<div class="ba-operate-form">
-				<el-form ref="formRef" :model="formData" :rules="rules" label-width="120px" class="form-container">
+				<el-form 
+					ref="formRef" 
+					:model="formData" 
+					:rules="rules" 
+					label-width="110px" 
+					class="form-container"
+				>
 					<el-form-item label="规格名称" prop="spec_name">
-						<el-input v-model="formData.spec_name" placeholder="请输入规格名称" />
-					</el-form-item>
-					
-					<el-form-item label="价格" prop="price">
-						<el-input-number
-							v-model.number="formData.price"
-							:min="0"
-							placeholder="价格"
-							style="width: 100%"
+						<el-input 
+							v-model="formData.spec_name" 
+							placeholder="请输入规格名称"
+							clearable
+							maxlength="50"
+							show-word-limit
 						/>
-						<span class="form-unit">元</span>
 					</el-form-item>
 					
-					<el-form-item label="库存" prop="stock">
-						<el-input-number
-							v-model.number="formData.stock"
-							:min="0"
-							placeholder="库存数量"
-							style="width: 100%"
-						/>
-					</el-form-item>
-					
-					<el-form-item label="套餐类型" prop="type">
-						<el-select v-model="formData.type" placeholder="请选择套餐类型" style="width: 100%">
-							<el-option
-								v-for="opt in packageTypeOptions"
-								:key="opt.value"
-								:label="opt.label"
-								:value="opt.value"
-							/>
-						</el-select>
-					</el-form-item>
+					<el-row :gutter="20">
+						<el-col :span="12">
+							<el-form-item label="价格" prop="price">
+								<div class="input-with-unit">
+									<el-input-number
+										v-model.number="formData.price"
+										:min="0"
+										:precision="2"
+										placeholder="请输入价格"
+										style="width: 100%"
+										controls-position="right"
+									/>
+									<span class="form-unit">元</span>
+								</div>
+							</el-form-item>
+						</el-col>
+						<el-col :span="12">
+							<el-form-item label="库存" prop="stock">
+								<div class="input-with-unit">
+									<el-input-number
+										v-model.number="formData.stock"
+										:min="0"
+										placeholder="请输入库存"
+										style="width: 100%"
+										controls-position="right"
+									/>
+									<span class="form-unit">件</span>
+								</div>
+							</el-form-item>
+						</el-col>
+					</el-row>
 					
-					<el-form-item label="卡类型" prop="card_type">
-						<el-select
-							v-model="formData.card_type"
-							placeholder="请选择卡类型"
-							style="width: 100%"
-							@change="handleCardTypeChange"
-						>
-							<el-option
-								v-for="opt in cardTypeOptions"
-								:key="opt.value"
-								:label="opt.label"
-								:value="opt.value"
-							/>
-						</el-select>
-					</el-form-item>
+					<el-row :gutter="20">
+						<el-col :span="12">
+							<el-form-item label="套餐类型" prop="type">
+								<el-select 
+									v-model="formData.type" 
+									placeholder="请选择套餐类型" 
+									style="width: 100%"
+									clearable
+								>
+									<el-option
+										v-for="opt in packageTypeOptions"
+										:key="opt.value"
+										:label="opt.label"
+										:value="opt.value"
+									/>
+								</el-select>
+							</el-form-item>
+						</el-col>
+						<el-col :span="12">
+							<el-form-item label="卡类型" prop="card_type">
+								<el-select
+									v-model="formData.card_type"
+									placeholder="请选择卡类型"
+									style="width: 100%"
+									@change="handleCardTypeChange"
+									clearable
+								>
+									<el-option
+										v-for="opt in cardTypeOptions"
+										:key="opt.value"
+										:label="opt.label"
+										:value="opt.value"
+									/>
+								</el-select>
+							</el-form-item>
+						</el-col>
+					</el-row>
 					
 					<el-form-item
+						v-if="formData.card_type === '1'"
 						label="次卡次数"
 						prop="unit_value"
-						v-if="formData.card_type === '1'"
 					>
-						<el-input-number
-							v-model.number="formData.unit_value"
-							:min="0"
-							placeholder="请输入次卡次数"
-							style="width: 100%"
-						/>
+						<div class="input-with-unit">
+							<el-input-number
+								v-model.number="formData.unit_value"
+								:min="1"
+								placeholder="请输入次卡次数"
+								style="width: 100%"
+								controls-position="right"
+							/>
+							<span class="form-unit">次</span>
+						</div>
+						<div class="form-tip">当选择次卡时必填</div>
 					</el-form-item>
 					
 					<el-form-item
+						v-if="formData.card_type === '2'"
 						label="积分数"
 						prop="points_value"
-						v-if="formData.card_type === '2'"
 					>
-						<el-input-number
-							v-model.number="formData.points_value"
-							:min="0"
-							placeholder="请输入积分数"
-							style="width: 100%"
-						/>
+						<div class="input-with-unit">
+							<el-input-number
+								v-model.number="formData.points_value"
+								:min="1"
+								placeholder="请输入积分数"
+								style="width: 100%"
+								controls-position="right"
+							/>
+							<span class="form-unit">分</span>
+						</div>
+						<div class="form-tip">当选择按积分时必填</div>
 					</el-form-item>
 					
 					<el-form-item label="说明描述" prop="description">
@@ -92,6 +144,8 @@
 							type="textarea"
 							:rows="4"
 							placeholder="请输入说明描述"
+							maxlength="500"
+							show-word-limit
 						/>
 					</el-form-item>
 				</el-form>
@@ -99,9 +153,10 @@
 		</el-scrollbar>
 		<template #footer>
 			<div class="dialog-footer">
-				<el-button @click="handleClose" size="large" class="cancel-btn">{{ t('Cancel') }}</el-button>
-				<el-button @click="onSubmit()" type="primary" size="large" :loading="loading"
-					class="submit-btn">确定</el-button>
+				<el-button @click="handleClose" size="default">{{ t('Cancel') }}</el-button>
+				<el-button @click="onSubmit()" type="primary" size="default" :loading="loading">
+					确定
+				</el-button>
 			</div>
 		</template>
 	</el-dialog>
@@ -218,14 +273,27 @@ const editDialog = (pkgId: number, specData: any) => {
 	// 清理校验状态
 	formRef.value?.clearValidate()
 
-	// 回填数据
+	// 辅助函数:安全地将值转换为数字
+	const toNumber = (value: any): number | undefined => {
+		if (value == null || value === '') return undefined
+		const num = Number(value)
+		return isNaN(num) ? undefined : num
+	}
+
+	// 回填数据,确保数字类型正确转换
 	formData.spec_name = specData.spec_name || specData.specName || ''
-	formData.price = specData.price
-	formData.stock = specData.stock
+	// 价格:转换为数字,如果为空或无效则设为 undefined
+	formData.price = toNumber(specData.price)
+	// 库存:转换为数字,如果为空或无效则设为 undefined
+	formData.stock = toNumber(specData.stock)
 	formData.type = String(specData.type || specData.packageType || '')
 	formData.card_type = String(specData.card_type || specData.chargeType || '')
-	formData.unit_value = specData.unit_value || (specData.card_type === '1' ? specData.quantity : undefined)
-	formData.points_value = specData.points_value || (specData.card_type === '2' ? specData.quantity : undefined)
+	// 次卡次数:转换为数字,如果为空或无效则设为 undefined
+	formData.unit_value = toNumber(specData.unit_value) || 
+		(specData.card_type === '1' ? toNumber(specData.quantity) : undefined)
+	// 积分数:转换为数字,如果为空或无效则设为 undefined
+	formData.points_value = toNumber(specData.points_value) || 
+		(specData.card_type === '2' ? toNumber(specData.quantity) : undefined)
 	formData.description = specData.description || specData.remark || ''
 }
 
@@ -264,9 +332,11 @@ const onSubmit = async () => {
 		// 构建提交数据(按照用户要求的字段)
 		const submitData: any = {
 			id: currentSpecId.value || '',
+			package_id: packageId.value,
 			spec_name: formData.spec_name,
-			price: formData.price,
-			stock: formData.stock,
+			// 确保数字类型正确
+			price: formData.price != null ? Number(formData.price) : undefined,
+			stock: formData.stock != null ? Number(formData.stock) : undefined,
 			type: formData.type,
 			card_type: formData.card_type,
 			description: formData.description,
@@ -274,12 +344,12 @@ const onSubmit = async () => {
 
 		// 根据卡类型添加对应的值
 		// 当选择次卡时必填 unit_value,选择其他天数的卡可以不传
-		if (formData.card_type === '1' && formData.unit_value !== undefined) {
-			submitData.unit_value = formData.unit_value
+		if (formData.card_type === '1' && formData.unit_value !== undefined && formData.unit_value != null) {
+			submitData.unit_value = Number(formData.unit_value)
 		}
 		// 当选择积分时必填 points_value,选择其他天数的卡可以不传
-		if (formData.card_type === '2' && formData.points_value !== undefined) {
-			submitData.points_value = formData.points_value
+		if (formData.card_type === '2' && formData.points_value !== undefined && formData.points_value != null) {
+			submitData.points_value = Number(formData.points_value)
 		}
 
 		// 调用接口
@@ -313,10 +383,128 @@ defineExpose({ openDialog, editDialog })
 </script>
 
 <style scoped lang="scss">
+.spec-dialog {
+	:deep(.el-dialog__body) {
+		padding: 20px 28px;
+		max-height: 70vh;
+		overflow: hidden;
+	}
+	
+	:deep(.el-dialog__footer) {
+		padding: 16px 28px;
+		border-top: 1px solid #ebeef5;
+	}
+	
+	:deep(.ba-table-form-scrollbar) {
+		max-height: calc(70vh - 100px);
+	}
+}
+
 .form-container {
+	padding: 4px 0;
+	
+	:deep(.el-form-item) {
+		margin-bottom: 20px;
+		
+		&:last-child {
+			margin-bottom: 0;
+		}
+		
+		.el-form-item__label {
+			font-weight: 500;
+			color: #606266;
+			padding-right: 12px;
+			padding-bottom: 0;
+			line-height: 32px;
+		}
+		
+		.el-form-item__content {
+			line-height: 32px;
+		}
+	}
+	
+	:deep(.el-row) {
+		margin-bottom: 20px;
+		
+		&:last-child {
+			margin-bottom: 0;
+		}
+		
+		.el-col {
+			.el-form-item {
+				margin-bottom: 0;
+			}
+		}
+	}
+	
+	.input-with-unit {
+		display: flex;
+		align-items: center;
+		gap: 8px;
+		
+		.el-input-number {
+			flex: 1;
+		}
+	}
+	
 	.form-unit {
-		margin-left: 8px;
-		color: #666;
+		color: #909399;
+		font-size: 14px;
+		white-space: nowrap;
+		min-width: 24px;
+	}
+	
+	.form-tip {
+		margin-top: 4px;
+		font-size: 12px;
+		color: #909399;
+		line-height: 1.5;
+	}
+	
+	:deep(.el-input__wrapper) {
+		border-radius: 4px;
+		transition: all 0.2s;
+		
+		&:hover {
+			box-shadow: 0 0 0 1px #c0c4cc inset;
+		}
+	}
+	
+	:deep(.el-textarea__inner) {
+		border-radius: 4px;
+		transition: all 0.2s;
+		
+		&:hover {
+			border-color: #c0c4cc;
+		}
+	}
+	
+	:deep(.el-select) {
+		.el-select__wrapper {
+			border-radius: 4px;
+			transition: all 0.2s;
+			
+			&:hover {
+				box-shadow: 0 0 0 1px #c0c4cc inset;
+			}
+		}
+	}
+	
+	:deep(.el-input-number) {
+		.el-input__wrapper {
+			border-radius: 4px;
+		}
+	}
+}
+
+.dialog-footer {
+	display: flex;
+	justify-content: flex-end;
+	gap: 12px;
+	
+	.el-button {
+		min-width: 88px;
+		border-radius: 4px;
 	}
 }
 </style>

+ 84 - 15
web/src/views/backend/package/product/index.vue

@@ -6,7 +6,27 @@
         <!-- 表格顶部菜单 -->
         <TableHeader :buttons="['refresh', 'quickSearch', 'columnDisplay']"
             :quick-search-placeholder="t('Quick search placeholder', { fields: '产品名称' })">
-            <el-button type="primary" @click="onAdd">添加</el-button>
+            <el-form style="margin-left: 5px;padding-top: 20px;" inline :model="seachForm" @submit.prevent="handleSearch">
+                <el-form-item >
+                    <el-input v-model="seachForm.name" placeholder="请输入产品名称" @blur="handleSearch" />
+                </el-form-item>
+                <el-form-item>
+                    <el-select v-model="seachForm.stock_status" placeholder="请选择库存状态" style="width: 150px;" clearable filterable @change="handleSearch">
+                        <el-option label="充足" value="1" />
+                        <el-option label="即将售罄" value="2" />
+                        <el-option label="售罄" value="3" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item>
+                    <el-select v-model="seachForm.status" placeholder="请选择产品状态" style="width: 150px;" clearable filterable @change="handleSearch">
+                        <el-option label="启用" value="1" />
+                        <el-option label="禁用" value="2" />
+                    </el-select>
+                </el-form-item>
+                <el-form-item>
+                    <el-button type="primary" @click="onAdd">添加</el-button>
+                </el-form-item>
+            </el-form>
         </TableHeader>
 
         <!-- 表格 -->
@@ -15,8 +35,8 @@
             <template #status>
                 <el-table-column prop="status" align="center" label="状态" min-width="100">
                     <template #default="scope">
-                        <el-switch v-model="scope.row.status" 
-                            active-value="1" inactive-value="0" />
+                        <el-switch v-model="scope.row.status" @change="changeStatus(scope.row)"
+                            :active-value="1" :inactive-value="2" />
                     </template>
                 </el-table-column>
             </template>
@@ -24,19 +44,19 @@
                 <el-table-column prop="operate" align="center" label="操作" min-width="150">
                     <template #default="scope">
                         <el-button type="primary" size="small" @click="onEdit(scope.row)">编辑</el-button>
-                        <el-button type="danger" size="small" @click="del(scope.row)">删除</el-button>
+                        <el-button type="danger" size="small" @click="handleDel(scope.row)">删除</el-button>
                     </template>
                 </el-table-column>
             </template>
         </Table>
 
         <!-- 表单 -->
-        <PopupForm />
+        <PopupForm @success="baTable.getIndex()" :packageOptions="packageOptions" />
     </div>
 </template>
 
 <script setup lang="ts">
-import { provide } from 'vue'
+import { ref,reactive,provide, onMounted } from 'vue'
 import { cloneDeep } from 'lodash-es'
 import baTableClass from '/@/utils/baTable'
 import Table from '/@/components/table/index.vue'
@@ -44,15 +64,30 @@ import TableHeader from '/@/components/table/header/index.vue'
 import PopupForm from './popupForm.vue'
 import { baTableApi } from '/@/api/common'
 import { useI18n } from 'vue-i18n'
-
-type TableRow = Record<string, any>
+import { batchStatus,del } from '/@/api/backend/package/product'
+import { list } from '/@/api/backend/package/package'
+import { ElMessageBox } from 'element-plus'
 
 defineOptions({
     name: 'package/product',
 })
-
 const { t } = useI18n()
 
+const seachForm = reactive({
+    name:"",
+    stock_status:"",
+    status:""
+})
+
+// 搜索
+const handleSearch = () =>{
+    baTable.table.filter!.page = 1
+    baTable.table.filter!.name = seachForm.name
+    baTable.table.filter!.stock_status = seachForm.stock_status
+    baTable.table.filter!.status = seachForm.status
+    baTable.getIndex()
+}
+
 const baTable = new baTableClass(
     new baTableApi('/admin/package.product/'),
     {
@@ -74,7 +109,7 @@ const baTable = new baTableClass(
             },
             {
                 label: '关联套餐',
-                prop: 'package',
+                prop: 'package_name',
                 align: 'center',
                 minWidth: 150
             },
@@ -144,12 +179,11 @@ const baTable = new baTableClass(
     },
     {
         defaultItems: {
-            id: undefined,
+            id: null,
             name: '',
             package_id: '',
             description: '',
             image: '',
-            stock_status: '1',
             status: '1',
             type: '1',
             link: '',
@@ -166,10 +200,38 @@ baTable.mount()
 baTable.getData()
 provide('baTable', baTable)
 
-const del = (row: TableRow) => {
-    baTable.onTableAction('delete', { row })
+interface packageType {
+    value: string | number
+    label: string | number
+}
+const packageOptions = ref<packageType[]>([])
+// 获取套餐列表
+const getPackageList = () => {
+    list({ limit: 9999 }).then((res: any) => {
+        packageOptions.value = res.data.list.map((item: any) => ({
+            label: item.name,
+            value: item.id,
+        }))
+    })
+}
+
+// 删除
+const handleDel = (row: TableRow) => {
+    ElMessageBox.confirm(`确定删除产品"${row.name}"吗?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning',
+    }).then(() => {
+        del({ id: row.id }).then(() => {
+            baTable.getIndex()
+        })
+    })
 }
 
+// 改变状态
+const changeStatus = (row: TableRow) => {
+    batchStatus({ ids: [row.id], status: row.status })
+}
 const openForm = (type: 'Add' | 'Edit', row?: TableRow) => {
     if (type === 'Edit' && !row) return
     baTable.form.items = row ? cloneDeep(row) : cloneDeep(baTable.form.defaultItems)
@@ -180,7 +242,14 @@ const openForm = (type: 'Add' | 'Edit', row?: TableRow) => {
 const onAdd = () => openForm('Add')
 const onEdit = (row: TableRow) => openForm('Edit', row)
  
+onMounted(()=>{
+    getPackageList()
+})
 </script>
 
-<style scoped lang="scss"></style>
+<style scoped lang="scss">
+.el-form--inline .el-form-item {
+    margin-right: 8px;
+}
+</style>
 

+ 41 - 94
web/src/views/backend/package/product/popupForm.vue

@@ -1,87 +1,39 @@
 <template>
     <!-- 对话框表单 -->
-    <el-dialog
-        class="ba-operate-dialog"
-        :close-on-click-modal="false"
-        :model-value="['Add', 'Edit'].includes(baTable.form.operate!)"
-        @close="baTable.toggleForm"
-        :destroy-on-close="true"
-    >
+    <el-dialog class="ba-operate-dialog" :close-on-click-modal="false"
+        :model-value="['Add', 'Edit'].includes(baTable.form.operate!)" @close="baTable.toggleForm"
+        :destroy-on-close="true">
         <template #header>
             <div class="title" v-drag="['.ba-operate-dialog', '.el-dialog__header']" v-zoom="'.ba-operate-dialog'">
                 {{ baTable.form.operate ? t(baTable.form.operate) : '' }}
             </div>
         </template>
         <el-scrollbar v-loading="baTable.form.loading" class="ba-table-form-scrollbar">
-            <div
-                class="ba-operate-form"
-                :class="'ba-' + baTable.form.operate + '-form'"
-                :style="config.layout.shrink ? '' : 'width: calc(100% - ' + baTable.form.labelWidth! / 2 + 'px)'"
-            >
-                <el-form
-                    ref="formRef"
-                    @keyup.enter="btnSubmit"
-                    :model="baTable.form.items"
+            <div class="ba-operate-form" :class="'ba-' + baTable.form.operate + '-form'"
+                :style="config.layout.shrink ? '' : 'width: calc(100% - ' + baTable.form.labelWidth! / 2 + 'px)'">
+                <el-form ref="formRef" @keyup.enter="btnSubmit" :model="baTable.form.items"
                     :label-position="config.layout.shrink ? 'top' : 'right'"
-                    :label-width="baTable.form.labelWidth + 'px'"
-                    :rules="rules"
-                    v-if="!baTable.form.loading"
-                >
-                    <FormItem
-                        label="产品名称"
-                        prop="name"
-                        v-model="baTable.form.items!.name"
-                        type="string"
-                        :placeholder="t('Please input field', { field: '产品名称' })"
-                    />
+                    :label-width="baTable.form.labelWidth + 'px'" :rules="rules" v-if="!baTable.form.loading">
+                    <FormItem label="产品名称" prop="name" v-model="baTable.form.items!.name" type="string"
+                        :placeholder="t('Please input field', { field: '产品名称' })" />
                     <el-form-item label="关联套餐" prop="package_id">
-                        <el-select
-                            v-model="baTable.form.items!.package_id"
-                            placeholder="请选择关联套餐"
-                            style="width: 100%"
-                        >
-                            <el-option
-                                v-for="item in packageOptions"
-                                :key="item.value"
-                                :label="item.label"
-                                :value="item.value"
-                            />
+                        <el-select v-model="baTable.form.items!.package_id" placeholder="请选择关联套餐" style="width: 100%">
+                            <el-option v-for="item in packageOptions" :key="item.value" :label="item.label"
+                                :value="item.value" />
                         </el-select>
                     </el-form-item>
-                    <FormItem
-                        label="产品图片"
-                        prop="image"
-                        type="image"
-                        v-model="baTable.form.items!.image"
-                    />
-                    <FormItem
-                        label="产品状态"
-                        prop="status"
-                        type="radio"
-                        v-model="baTable.form.items!.status"
-                        :input-attr="{ border: true, content: statusOptions }"
-                    />
-                    <FormItem
-                        label="产品类型"
-                        prop="type"
-                        type="radio"
-                        v-model="baTable.form.items!.type"
-                        :input-attr="{ border: true, content: typeOptions }"
-                    />
+                    <FormItem label="产品图片" prop="image" type="image" v-model="baTable.form.items!.image" :input-attr="{ returnFullUrl: true }" />
+                    <FormItem label="产品状态" prop="status" type="radio" v-model="baTable.form.items!.status"
+                        :input-attr="{ border: true, content: statusOptions }" />
+                    <FormItem label="产品类型" prop="type" type="radio" v-model="baTable.form.items!.type"
+                        :input-attr="{ border: true, content: typeOptions }" />
                     <el-form-item label="产品链接" prop="link">
-                        <el-input
-                            v-model="baTable.form.items!.link"
-                            placeholder="请输入产品链接"
-                        />
+                        <el-input v-model="baTable.form.items!.link" placeholder="请输入产品链接" />
                     </el-form-item>
-                    
+
                     <el-form-item label="产品描述" prop="description">
-                        <el-input
-                            v-model="baTable.form.items!.description"
-                            type="textarea"
-                            :rows="4"
-                            placeholder="请输入产品描述"
-                        />
+                        <el-input v-model="baTable.form.items!.description" type="textarea" :rows="4"
+                            placeholder="请输入产品描述" />
                     </el-form-item>
                 </el-form>
             </div>
@@ -90,7 +42,8 @@
             <div :style="'width: calc(100% - ' + baTable.form.labelWidth! / 1.8 + 'px)'">
                 <el-button @click="baTable.toggleForm('')">{{ t('Cancel') }}</el-button>
                 <el-button v-blur :loading="baTable.form.submitLoading" @click="btnSubmit" type="primary">
-                    {{ baTable.form.operateIds && baTable.form.operateIds.length > 1 ? t('Save and edit next item') : t('Save') }}
+                    {{ baTable.form.operateIds && baTable.form.operateIds.length > 1 ? t('Save and edit next item') :
+                    t('Save') }}
                 </el-button>
             </div>
         </template>
@@ -98,7 +51,7 @@
 </template>
 
 <script setup lang="ts">
-import { reactive, inject,  useTemplateRef } from 'vue'
+import { reactive, inject, useTemplateRef, ref, onMounted } from 'vue'
 import { useI18n } from 'vue-i18n'
 import type baTableClass from '/@/utils/baTable'
 import FormItem from '/@/components/formItem/index.vue'
@@ -111,6 +64,7 @@ const config = useConfig()
 const formRef = useTemplateRef('formRef')
 const baTable = inject('baTable') as baTableClass
 const emit = defineEmits(['success'])
+const prop = defineProps(['packageOptions'])
 
 const { t } = useI18n()
 
@@ -124,50 +78,43 @@ const rules: Partial<Record<string, FormItemRule[]>> = reactive({
     status: [buildValidatorData({ name: 'required', title: '产品状态' })],
 })
 
-const packageOptions = [
-    { label: '聚德神笔A套餐', value: '聚德神笔A套餐' },
-    { label: '聚德神笔B套餐', value: '聚德神笔B套餐' },
-    { label: '企业定制套餐', value: '企业定制套餐' },
-]
-
 const typeOptions = {
-    '1': '网页',
-    '2': '软件',
+    "1": '网页',
+    "2": '软件',
 }
 
 const statusOptions = {
-    '1': '启用',
-    '0': '禁用',
+    "1": '启用',
+    "2": '禁用',
 }
 
 // 提交
 const btnSubmit = () => {
-	formRef.value?.validate((valid: boolean) => {
-		if (valid) {
+    formRef.value?.validate((valid: boolean) => {
+        if (valid) {
             baTable.table.loading = true
 
             let params = {
                 id: baTable.form.items!.id,
                 name: baTable.form.items!.name,
-                email:baTable.form.items!.email,
-                company:baTable.form.items!.company,
-                password:baTable.form.items!.password,
+                package_id: baTable.form.items!.package_id,
+                image: baTable.form.items!.image,
+                type: baTable.form.items!.type,
+                link: baTable.form.items!.link,
                 status: baTable.form.items!.status,
+                description: baTable.form.items!.description,
             }
 
             edit(params).then(() => {
                 baTable.toggleForm()
                 emit('success')
-            }).finally(()=>{
+            }).finally(() => {
                 baTable.table.loading = false
             })
-			
-		}
-	})
-}
 
+        }
+    })
+}
 </script>
 
-<style scoped lang="scss">
-
-</style>
+<style scoped lang="scss"></style>