FastbuildAI新建套餐-前端代码分析
1. 功能概述
FastbuildAI的"新建套餐"功能是充值管理模块的核心功能,允许管理员创建和管理用户充值套餐。该功能位于用户管理菜单下的充值管理页面,提供了完整的套餐配置界面。
1.1 业务流程
- 管理员登录: 使用管理员账号登录FastbuildAI后台管理系统
- 导航到充值管理: 点击"用户管理" → "充值管理"菜单
- 新建套餐: 点击"新建套餐"按钮添加新的充值规则
- 配置套餐信息: 输入充值金额、赠送电力值、售价、标签等信息
- 保存套餐: 点击右下角"保存"按钮提交配置
1.2 功能特性
- 动态表格管理: 支持动态添加和删除充值套餐行
- 实时数据验证: 表单字段实时验证和错误提示
- 权限控制: 基于用户权限控制功能访问
- 多语言支持: 完整的国际化文本配置
- 状态管理: 智能检测数据变更状态
2. 页面路由与菜单结构
2.1 菜单配置
前端配置文件: apps/web/core/i18n/zh/console-menu.json
{"userManagement": {"title": "用户管理","userList": "用户列表","userInfo": "用户信息","userRecharge": "充值管理"}
}
后端菜单配置文件: apps/server/src/core/database/install/menu.json
{"name": "console-menu.userManagement.userRecharge","code": "user-user-recharge","path": "user-recharge","icon": "","component": "/console/user/user-recharge/index","permissionCode": "recharge-config:getConfig","sort": 100,"isHidden": 0,"type": 2,"sourceType": 1,"pluginPackName": null,"children": [{"name": "console-common.save","code": "user-user-recharge-save","path": "","icon": "","component": "","permissionCode": "recharge-config:setConfig","sort": 0,"isHidden": 1,"type": 2,"sourceType": 1,"pluginPackName": null}]
}
2.2 路由结构
基于Nuxt.js的文件系统路由,充值管理页面的路由结构:
apps/web/app/console/user/user-recharge/
├── index.vue # 充值管理主页面
访问路径: /console/user/user-recharge
2.3 菜单层级
控制台首页
└── 用户管理 (userManagement)├── 用户列表 (userList)├── 用户信息 (userInfo)└── 充值管理 (userRecharge) ← 目标页面
3. 核心组件分析
3.1 主组件结构
文件位置: apps/web/app/console/user/user-recharge/index.vue
这是一个完整的Vue 3 Composition API组件,使用TypeScript和Nuxt UI组件库构建。
3.1.1 Script Setup部分
<script setup lang="ts">
import { useMessage } from "@fastbuildai/ui";
import type { TableColumn } from "@nuxt/ui";
import { useI18n } from "vue-i18n";import type { RechargeConfigData, RechargeRule } from "@/models/package-management";
import { apiGetRechargeRules, saveRechargeRules } from "@/services/console/package-management";const { t } = useI18n();
const toast = useMessage();// 响应式数据状态
const rechargeStatus = ref(true);
const changeValue = ref(false);
const rechargeRules = ref<RechargeRule[]>([]);
const rechargeExplain = ref("");// 表格列配置
const columns: TableColumn<RechargeRule>[] = [{id: "move",header: "#",size: 40,enableSorting: false,enableHiding: false,},{id: "rechargeValue",accessorKey: "rechargeValue",header: t("console-marketing.packageManagement.tab.rechargeValue"),},{id: "freeQuantity",accessorKey: "freeQuantity",header: t("console-marketing.packageManagement.tab.freeQuantity"),},{id: "price",accessorKey: "price",header: t("console-marketing.packageManagement.tab.price"),},{id: "label",accessorKey: "label",header: t("console-marketing.packageManagement.tab.label"),},{id: "action",header: t("console-marketing.packageManagement.tab.action"),size: 40,enableSorting: false,enableHiding: false,},
];const oldData = ref<RechargeConfigData>();// 获取充值规则数据
const getRechargeRules = async () => {const data = await apiGetRechargeRules();oldData.value = data;rechargeRules.value = data.rechargeRule.map((item) => ({ ...item }));rechargeStatus.value = data.rechargeStatus;rechargeExplain.value = data.rechargeExplain;
};// 添加新的充值套餐行
const addRow = () => {const newRow = ref<RechargeRule>({givePower: 0,label: "",power: 0,sellPrice: 0,});rechargeRules.value.push(newRow.value);
};// 删除充值套餐行
const deleteRow = (row: RechargeRule) => {rechargeRules.value = rechargeRules.value.filter((item) => item !== row);
};// 保存充值规则配置
const saveRules = async () => {try {await saveRechargeRules({rechargeRule: rechargeRules.value,rechargeStatus: rechargeStatus.value,rechargeExplain: rechargeExplain.value,});getRechargeRules();toast.success(t("console-marketing.packageManagement.saveSuccess"));} catch (error) {console.error(error);}
};watch(rechargeStatus, () => {if (rechargeStatus.value !== oldData.value?.rechargeStatus) {changeValue.value = true;} else {changeValue.value = false;}
});watch(rechargeExplain, () => {if (rechargeExplain.value !== oldData.value?.rechargeExplain) {changeValue.value = true;} else {changeValue.value = false;}
});// 判断充值规则是否变化
const isEqual = (arr1: RechargeRule[], arr2: RechargeRule[] | undefined) => {if (!arr2) return false;if (arr1.length !== arr2.length) return false;return arr1.every((item, index) => {if (item?.power !== arr2[index]?.power ||item?.givePower !== arr2[index]?.givePower ||item?.sellPrice !== arr2[index]?.sellPrice ||item?.label !== arr2[index]?.label) {return false;} else {return true;}});
};watch(rechargeRules,() => {if (!isEqual(rechargeRules.value, oldData.value?.rechargeRule)) {changeValue.value = true;} else {changeValue.value = false;}},{ deep: true },
);onMounted(() => {getRechargeRules();
});
</script>
3.1.2 Template部分
<template><div class="my-px space-y-4 pb-6"><div class="flex flex-col gap-6"><!-- 功能状态开关 --><div class=""><div><div class="mb-4 flex flex-col gap-1"><div class="text-secondary-foreground text-md font-bold">{{ t("console-marketing.packageManagement.statusTitle") }}</div><div class="text-muted-foreground text-xs">{{ t("console-marketing.packageManagement.statusDescription") }}</div></div><USwitch v-model="rechargeStatus" /></div></div><!-- 充值说明配置 --><div v-if="rechargeStatus"><div class="mb-4 flex flex-col gap-1"><div class="text-secondary-foreground text-md font-bold">{{ t("console-marketing.packageManagement.rechargeInstructionsTitle") }}</div><div class="text-muted-foreground text-xs">{{ t("console-marketing.packageManagement.rechargeInstructionsDescription") }}</div></div><div class="w-full text-sm"><UTextareaclass="w-full"v-model="rechargeExplain":rows="6"placeholder="请输入套餐充值说明..."/></div></div><!-- 充值规则表格 --><div class="flex-1"><div class="flex w-full items-center justify-between"><div class="text-secondary-foreground text-md font-bold">{{ t("console-marketing.packageManagement.rechargeRulesTitle") }}</div><div class="flex items-center justify-between gap-2 px-4"><UButtoncolor="primary"variant="outline"icon="tabler:plus":ui="{ leadingIcon: 'size-4' }"@click="addRow">{{ t("console-marketing.packageManagement.button.new") }}</UButton></div></div><!-- 数据表格 --><div class="mt-4"><UTableref="table":data="rechargeRules":columns="columns":ui="{base: 'table-fixed border-separate border-spacing-0',thead: '[&>tr]:bg-elevated/50 [&>tr]:after:content-none',tbody: '[&>tr]:last:[&>td]:border-b-0',th: 'py-2 first:rounded-l-lg last:rounded-r-lg border-y border-default first:border-l last:border-r',td: 'border-b border-default',tr: '[&:has(>td[colspan])]:hidden',}"><!-- 序号列 --><template #move-cell="{ row }">{{ row.index + 1 }}</template><!-- 充值数量输入 --><template #rechargeValue-cell="{ row }"><UInput v-model="row.original.power" type="number" /></template><!-- 赠送数量输入 --><template #freeQuantity-cell="{ row }"><UInput v-model="row.original.givePower" type="number" /></template><!-- 价格输入(带单位) --><template #price-cell="{ row }"><UInputv-model="row.original.sellPrice"type="number"min="0"step="0.01":ui="{trailing: 'bg-muted-foreground/15 pl-2 rounded-tr-lg rounded-br-lg',}"><template #trailing><span>{{ t("console-marketing.packageManagement.tab.priceUnit") }}</span></template></UInput></template><!-- 标签输入 --><template #label-cell="{ row }"><UInput v-model="row.original.label" /></template><!-- 操作按钮 --><template #action-cell="{ row }"><UButtonicon="tabler:trash"color="error"variant="ghost"@click="deleteRow(row.original)"/></template></UTable></div></div><!-- 保存按钮 --><div class="flex justify-end"><AccessControl :codes="['recharge-config:setConfig']"><UButtoncolor="primary":disabled="!changeValue":ui="{ base: 'w-16 flex justify-center items-center' }"@click="saveRules">{{ t("console-marketing.packageManagement.button.save") }}</UButton></AccessControl></div></div></div>
</template>
3.2 数据模型定义
文件位置: apps/web/models/package-management.d.ts
/*** 套餐充值配置响应接口*/
export interface RechargeConfigData {/*** 充值说明*/rechargeExplain: string;/*** 充值规则*/rechargeRule: RechargeRule[];/*** 充值开关:true-开启;false-关闭*/rechargeStatus: boolean;
}/*** 充值规则接口*/
export interface RechargeRule {/*** 赠送数量*/givePower: number;/*** 规则ID(可选)*/id?: string;/*** 标签*/label: string;/*** 充值数量*/power: number;/*** 售价*/sellPrice: string | number;
}
3.3 API接口服务
文件位置: apps/web/services/console/package-management.ts
// ==================== 套餐管理相关 API ====================import type { RechargeConfigData } from "@/models/package-management";/*** 获取套餐充值配置*/
export const apiGetRechargeRules = (): Promise<RechargeConfigData> => {return useConsoleGet("/recharge-config");
};/*** 保存套餐充值配置*/
export const saveRechargeRules = (data: RechargeConfigData): Promise<void> => {return useConsolePost("/recharge-config", data);
};
4. 用户交互流程详解
4.1 页面初始化流程
4.2 新建套餐流程
4.3 数据保存流程
4.4 表单验证机制
组件实现了多层次的数据验证:
- 输入类型验证: 使用
type="number"
确保数值输入 - 最小值限制: 价格字段设置
min="0"
防止负数 - 精度控制: 价格字段使用
step="0.01"
支持小数 - 深度比较: 使用
isEqual
函数精确检测数据变更
4.5 错误处理和用户反馈
// 保存操作的错误处理
const saveRules = async () => {try {await saveRechargeRules({rechargeRule: rechargeRules.value,rechargeStatus: rechargeStatus.value,rechargeExplain: rechargeExplain.value,});getRechargeRules();toast.success(t("console-marketing.packageManagement.saveSuccess"));} catch (error) {console.error(error);// 这里可以添加错误提示toast.error(t("console-marketing.packageManagement.saveFailed"));}
};
5. UI组件详解
5.1 USwitch 开关组件
<USwitch v-model="rechargeStatus" />
功能: 控制充值功能的启用/禁用状态
特点:
- 双向数据绑定
- 响应式状态更新
- 自动触发变更检测
5.2 UTextarea 文本域组件
<UTextareaclass="w-full"v-model="rechargeExplain":rows="6"placeholder="请输入套餐充值说明..."
/>
功能: 输入充值说明文本
特点:
- 多行文本输入
- 固定行数显示
- 占位符提示
5.3 UTable 表格组件
表格组件是页面的核心,具有以下特性:
5.3.1 表格配置
const columns: TableColumn<RechargeRule>[] = [{id: "move",header: "#",size: 40,enableSorting: false,enableHiding: false,},// ... 其他列配置
];
5.3.2 自定义单元格模板
<!-- 价格输入单元格 -->
<template #price-cell="{ row }"><UInputv-model="row.original.sellPrice"type="number"min="0"step="0.01":ui="{trailing: 'bg-muted-foreground/15 pl-2 rounded-tr-lg rounded-br-lg',}"><template #trailing><span>{{ t("console-marketing.packageManagement.tab.priceUnit") }}</span></template></UInput>
</template>
5.4 UInput 输入组件
支持多种输入类型:
- 数值输入:
type="number"
- 文本输入: 默认类型
- 带单位显示: 使用
trailing
插槽
5.5 UButton 按钮组件
<!-- 新建套餐按钮 -->
<UButtoncolor="primary"variant="outline"icon="tabler:plus":ui="{ leadingIcon: 'size-4' }"@click="addRow"
>{{ t("console-marketing.packageManagement.button.new") }}
</UButton><!-- 删除按钮 -->
<UButtonicon="tabler:trash"color="error"variant="ghost"@click="deleteRow(row.original)"
/><!-- 保存按钮 -->
<UButtoncolor="primary":disabled="!changeValue":ui="{ base: 'w-16 flex justify-center items-center' }"@click="saveRules"
>{{ t("console-marketing.packageManagement.button.save") }}
</UButton>
6. 权限控制机制
6.1 AccessControl 组件
文件位置: common/components/access-control.vue
<AccessControl :codes="['recharge-config:setConfig']"><UButtoncolor="primary":disabled="!changeValue"@click="saveRules">{{ t("console-marketing.packageManagement.button.save") }}</UButton>
</AccessControl>
权限控制逻辑:
- 检查用户权限数组中是否包含指定权限码
- Root用户拥有所有权限
- 权限不足时隐藏或禁用相关功能
6.2 权限码说明
recharge-config:setConfig
: 充值配置设置权限- 只有拥有此权限的用户才能看到保存按钮
- 确保数据安全和操作权限控制
7. 国际化支持
7.1 文本配置文件
中文配置 (core/i18n/zh/console-marketing.json
):
{"packageManagement": {"rechargeInstructionsTitle": "充值说明","saveSuccess": "保存成功","saveFailed": "保存失败","statusTitle": "功能状态","statusDescription": "启用后用户可以访问充值功能","rechargeRulesTitle": "充值规则","tab": {"rechargeValue": "充值数量","freeQuantity": "赠送数量","price": "价格","label": "标签","action": "操作","priceUnit": "元"},"button": {"save": "保存","new": "新建套餐"}}
}
英文配置 (core/i18n/en/console-marketing.json
):
{"packageManagement": {"rechargeInstructionsTitle": "Recharge Policy","saveSuccess": "Saved","saveFailed": "Save failed","statusTitle": "Feature Status","statusDescription": "Enable for user access to recharge","rechargeRulesTitle": "Recharge Rules","tab": {"rechargeValue": "Amount","freeQuantity": "Bonus","price": "Price","label": "Label","action": "Actions","priceUnit": "¥"},"button": {"save": "Save","new": "New Package"}}
}
7.2 使用方式
// 在组件中使用国际化
const { t } = useI18n();// 获取翻译文本
const title = t("console-marketing.packageManagement.rechargeInstructionsTitle");
支持的语言:
- 中文 (zh)
- 英文 (en)
- 日文 (jp)
8. 状态管理与数据流
8.1 响应式状态变量
// 核心状态变量
const rechargeStatus = ref(true); // 充值功能开关
const changeValue = ref(false); // 数据变更标志
const rechargeRules = ref<RechargeRule[]>([]); // 充值规则数组
const rechargeExplain = ref(""); // 充值说明文本
const oldData = ref<RechargeConfigData>(); // 原始数据备份
8.2 数据变更监听
8.2.1 简单值监听
// 监听充值状态变更
watch(rechargeStatus, () => {if (rechargeStatus.value !== oldData.value?.rechargeStatus) {changeValue.value = true;} else {changeValue.value = false;}
});// 监听充值说明变更
watch(rechargeExplain, () => {if (rechargeExplain.value !== oldData.value?.rechargeExplain) {changeValue.value = true;} else {changeValue.value = false;}
});
8.2.2 深度对象监听
// 深度比较函数
const isEqual = (arr1: RechargeRule[], arr2: RechargeRule[] | undefined) => {if (!arr2) return false;if (arr1.length !== arr2.length) return false;return arr1.every((item, index) => {if (item?.power !== arr2[index]?.power ||item?.givePower !== arr2[index]?.givePower ||item?.sellPrice !== arr2[index]?.sellPrice ||item?.label !== arr2[index]?.label) {return false;} else {return true;}});
};// 深度监听充值规则数组
watch(rechargeRules,() => {if (!isEqual(rechargeRules.value, oldData.value?.rechargeRule)) {changeValue.value = true;} else {changeValue.value = false;}},{ deep: true },
);
8.3 数据流向图
8.4 生命周期管理
// 组件挂载时初始化数据
onMounted(() => {getRechargeRules();
});// 数据获取函数
const getRechargeRules = async () => {const data = await apiGetRechargeRules();oldData.value = data; // 备份原始数据rechargeRules.value = data.rechargeRule.map((item) => ({ ...item })); // 深拷贝rechargeStatus.value = data.rechargeStatus;rechargeExplain.value = data.rechargeExplain;
};
9. 表单验证与用户交互
9.1 输入验证机制
9.1.1 数值输入验证
<!-- 充值数量输入 -->
<UInput v-model="row.original.power" type="number" /><!-- 赠送数量输入 -->
<UInput v-model="row.original.givePower" type="number" /><!-- 价格输入(带最小值和步长限制) -->
<UInputv-model="row.original.sellPrice"type="number"min="0"step="0.01"
/>
验证特性:
type="number"
: 限制只能输入数字min="0"
: 防止输入负数step="0.01"
: 支持小数点后两位
9.1.2 实时验证反馈
// 数据变更时实时检测
watch(rechargeRules, () => {if (!isEqual(rechargeRules.value, oldData.value?.rechargeRule)) {changeValue.value = true; // 启用保存按钮} else {changeValue.value = false; // 禁用保存按钮}
}, { deep: true });
9.2 用户反馈机制
9.2.1 成功反馈
// 保存成功提示
toast.success(t("console-marketing.packageManagement.saveSuccess"));
9.2.2 错误处理
try {await saveRechargeRules(data);// 成功处理
} catch (error) {console.error(error);// 错误处理
}
9.2.3 按钮状态控制
<UButtoncolor="primary":disabled="!changeValue" <!-- 根据数据变更状态控制按钮可用性 -->@click="saveRules"
>{{ t("console-marketing.packageManagement.button.save") }}
</UButton>
9.3 改进建议
9.3.1 删除确认对话框
// 建议使用模态确认对话框
const deleteRow = async (row: RechargeRule) => {const confirmed = await useModal().confirm({title: "确认删除",content: "确定要删除这个充值套餐吗?此操作不可撤销。",confirmText: "删除",cancelText: "取消"});if (confirmed) {rechargeRules.value = rechargeRules.value.filter((item) => item !== row);}
};
9.3.2 表单验证增强
// 可以添加更严格的验证规则
const validateRule = (rule: RechargeRule): boolean => {if (rule.power <= 0) {toast.error("充值数量必须大于0");return false;}if (rule.sellPrice <= 0) {toast.error("售价必须大于0");return false;}if (!rule.label.trim()) {toast.error("标签不能为空");return false;}return true;
};
10. 技术架构总结
10.1 技术栈
- 前端框架: Vue 3 + Nuxt.js
- UI组件库: Nuxt UI (基于Tailwind CSS)
- 状态管理: Vue 3 Composition API + Pinia
- 类型系统: TypeScript
- 国际化: Vue I18n
- HTTP客户端: Nuxt内置的$fetch
- 权限控制: 自定义AccessControl组件
10.2 架构特点
10.2.1 组件化设计
- 单文件组件: 使用Vue SFC格式,逻辑、模板、样式集中管理
- 组合式API: 采用Composition API提高代码复用性
- 类型安全: 全面使用TypeScript确保类型安全
10.2.2 响应式数据流
- 双向绑定: 表单数据与状态自动同步
- 深度监听: 复杂对象变更的精确检测
- 状态管理: 清晰的数据流向和状态变更
10.2.3 用户体验优化
- 实时反馈: 数据变更即时反映在UI上
- 权限控制: 基于用户权限的功能访问控制
- 国际化: 完整的多语言支持
10.2.4 可维护性
- 模块化: 清晰的文件组织和模块划分
- 类型定义: 完整的TypeScript类型定义
- 代码复用: 通用组件和工具函数的抽象
10.3 设计模式
10.3.1 MVVM模式
View (Template) ↔ ViewModel (Script) ↔ Model (API/Store)
10.3.2 组件通信
- Props Down: 父组件向子组件传递数据
- Events Up: 子组件向父组件发送事件
- Provide/Inject: 跨层级组件通信
10.3.3 状态管理模式
State → View → Action → State
10.4 性能优化
10.4.1 响应式优化
- ref vs reactive: 合理选择响应式API
- 深度监听: 仅在必要时使用deep watch
- 计算属性: 缓存复杂计算结果
10.4.2 组件优化
- 懒加载: 按需加载组件和模块
- 虚拟滚动: 大数据量表格的性能优化
- 防抖节流: 用户输入的性能优化
10.5 安全性考虑
10.5.1 权限控制
- 前端权限: AccessControl组件控制UI显示
- 后端验证: API层面的权限验证
- 角色管理: 基于角色的访问控制
10.5.2 数据验证
- 客户端验证: 表单数据的前端验证
- 服务端验证: API接口的后端验证
- 类型安全: TypeScript类型检查
10.6 扩展性设计
10.6.1 组件扩展
- 插槽机制: 支持内容自定义
- 属性配置: 灵活的组件配置
- 事件系统: 完整的事件通信
10.6.2 功能扩展
- 模块化: 新功能模块的独立开发
- 插件系统: 支持第三方插件扩展
- 配置化: 通过配置文件控制功能
FastbuildAI的新建套餐功能展现了现代Vue.js应用的最佳实践,通过Vue 3 + Nuxt.js + TypeScript的技术组合,实现了一个功能完善、用户体验优秀、可维护强劲的前端管理界面。该实现不仅满足了当前的业务需求,也为未来的功能扩展提供了良好的技术基础。