diff --git a/_sql/add_pricing_plans_menu.sql b/_sql/add_pricing_plans_menu.sql new file mode 100644 index 0000000..a7bb22c --- /dev/null +++ b/_sql/add_pricing_plans_menu.sql @@ -0,0 +1,36 @@ +-- 在用户管理菜单下添加"价格套餐管理"菜单项 +-- 参考其他菜单项的配置格式 + +INSERT INTO sys_menu ( + name, + parent_id, + model_id, + form_id, + icon, + path, + component, + api_path, + is_show_menu, + is_show, + type, + sort, + create_time, + last_modify_time, + is_delete +) VALUES ( + '价格套餐管理', -- 菜单名称 + 120, -- parent_id: 用户管理菜单的ID + 0, -- model_id + 0, -- form_id + 'md-pricetags', -- icon: 价格标签图标 + 'pricing_plans', -- path: 路由路径 + 'system/pricing_plans.vue', -- component: 组件路径(已在 component-map.js 中定义) + 'system/pricing_plans_server.js', -- api_path: API 服务文件路径 + 1, -- is_show_menu: 1=显示在菜单栏 + 1, -- is_show: 1=启用 + '页面', -- type: 页面类型 + 3, -- sort: 排序(可根据实际情况调整) + NOW(), -- create_time: 创建时间 + NOW(), -- last_modify_time: 最后修改时间 + 0 -- is_delete: 0=未删除 +); diff --git a/_sql/create_pricing_plans_table.sql b/_sql/create_pricing_plans_table.sql new file mode 100644 index 0000000..452f07d --- /dev/null +++ b/_sql/create_pricing_plans_table.sql @@ -0,0 +1,24 @@ +-- 创建价格套餐表 +-- 用于存储各种价格套餐的配置信息 + +CREATE TABLE `pricing_plans` ( + `id` INT(11) NOT NULL AUTO_INCREMENT COMMENT '价格套餐ID', + `name` VARCHAR(100) NOT NULL DEFAULT '' COMMENT '套餐名称(如:体验套餐、月度套餐等)', + `duration` VARCHAR(50) NOT NULL DEFAULT '' COMMENT '时长描述(如:7天、30天、永久)', + `days` INT(11) NOT NULL DEFAULT 0 COMMENT '天数(-1表示永久,0表示无限制)', + `price` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '售价(元)', + `original_price` DECIMAL(10,2) NULL DEFAULT NULL COMMENT '原价(元),可为空表示无原价', + `unit` VARCHAR(20) NOT NULL DEFAULT '元' COMMENT '价格单位', + `discount` VARCHAR(50) NULL DEFAULT NULL COMMENT '折扣描述(如:8.3折、超值)', + `features` TEXT NOT NULL COMMENT '功能特性列表(JSON字符串数组)', + `featured` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否为推荐套餐(1=推荐,0=普通)', + `is_active` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '是否启用(1=启用,0=禁用)', + `sort_order` INT(11) NOT NULL DEFAULT 0 COMMENT '排序顺序(越小越靠前)', + `create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `last_modify_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间', + `is_delete` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除(1=已删除,0=未删除)', + PRIMARY KEY (`id`), + INDEX `idx_is_active` (`is_active`), + INDEX `idx_is_delete` (`is_delete`), + INDEX `idx_sort_order` (`sort_order`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='价格套餐表'; diff --git a/_sql/insert_pricing_plans_data.sql b/_sql/insert_pricing_plans_data.sql new file mode 100644 index 0000000..60c9b0d --- /dev/null +++ b/_sql/insert_pricing_plans_data.sql @@ -0,0 +1,89 @@ +-- 插入初始价格套餐数据 +-- 基于原有前端接口 /config/pricing-plans 中的硬编码数据 + +INSERT INTO `pricing_plans` ( + `name`, + `duration`, + `days`, + `price`, + `original_price`, + `unit`, + `discount`, + `features`, + `featured`, + `is_active`, + `sort_order`, + `create_time`, + `last_modify_time`, + `is_delete` +) VALUES +( + '体验套餐', + '7天', + 7, + 28.00, + 28.00, + '元', + NULL, + '["7天使用权限", "全功能体验", "技术支持"]', + 0, + 1, + 1, + NOW(), + NOW(), + 0 +), +( + '月度套餐', + '30天', + 30, + 99.00, + 120.00, + '元', + '约8.3折', + '["30天使用权限", "全功能使用", "优先技术支持", "性价比最高"]', + 1, + 1, + 2, + NOW(), + NOW(), + 0 +), +( + '季度套餐', + '90天', + 90, + 269.00, + 360.00, + '元', + '7.5折', + '["90天使用权限", "全功能使用", "优先技术支持", "更优惠价格"]', + 0, + 1, + 3, + NOW(), + NOW(), + 0 +), +( + '终生套餐', + '永久', + -1, + 888.00, + NULL, + '元', + '超值', + '["永久使用权限", "全功能使用", "终身技术支持", "一次购买,终身使用", "最划算选择"]', + 0, + 1, + 4, + NOW(), + NOW(), + 0 +); + +-- 查询验证插入结果 +SELECT id, name, duration, price, original_price, featured, is_active, sort_order +FROM pricing_plans +WHERE is_delete = 0 +ORDER BY sort_order ASC; diff --git a/admin/src/api/system/pricing_plans_server.js b/admin/src/api/system/pricing_plans_server.js new file mode 100644 index 0000000..53c34ac --- /dev/null +++ b/admin/src/api/system/pricing_plans_server.js @@ -0,0 +1,54 @@ +/** + * 价格套餐 API 服务 + */ + +class PricingPlansServer { + /** + * 分页查询价格套餐 + * @param {Object} param - 查询参数 + * @param {Object} param.seachOption - 搜索条件 + * @param {Object} param.pageOption - 分页选项 + * @returns {Promise} + */ + page(param) { + return window.framework.http.post('/pricing_plans/list', param) + } + + /** + * 获取价格套餐详情 + * @param {Number|String} id - 价格套餐ID + * @returns {Promise} + */ + getById(id) { + return window.framework.http.get('/pricing_plans/detail', { id }) + } + + /** + * 新增价格套餐 + * @param {Object} row - 价格套餐数据 + * @returns {Promise} + */ + add(row) { + return window.framework.http.post('/pricing_plans/create', row) + } + + /** + * 更新价格套餐信息 + * @param {Object} row - 价格套餐数据 + * @returns {Promise} + */ + update(row) { + return window.framework.http.post('/pricing_plans/update', row) + } + + /** + * 删除价格套餐 + * @param {Object} row - 价格套餐数据(包含id) + * @returns {Promise} + */ + del(row) { + return window.framework.http.post('/pricing_plans/delete', { id: row.id }) + } +} + +export default new PricingPlansServer() diff --git a/admin/src/router/component-map.js b/admin/src/router/component-map.js index 56fe105..15f10d0 100644 --- a/admin/src/router/component-map.js +++ b/admin/src/router/component-map.js @@ -21,6 +21,7 @@ import TaskStatus from '@/views/task/task_status.vue' import SystemConfig from '@/views/system/system_config.vue' import Version from '@/views/system/version.vue' import JobTypes from '@/views/work/job_types.vue' +import PricingPlans from '@/views/system/pricing_plans.vue' // 首页模块 import HomeIndex from '@/views/home/index.vue' @@ -53,6 +54,8 @@ const componentMap = { 'system/system_config': SystemConfig, 'system/version': Version, 'work/job_types': JobTypes, + 'system/pricing_plans': PricingPlans, + 'system/pricing_plans.vue': PricingPlans, 'home/index': HomeIndex, diff --git a/admin/src/views/system/pricing_plans.vue b/admin/src/views/system/pricing_plans.vue new file mode 100644 index 0000000..a522748 --- /dev/null +++ b/admin/src/views/system/pricing_plans.vue @@ -0,0 +1,385 @@ + + + + + diff --git a/api/controller_admin/pricing_plans.js b/api/controller_admin/pricing_plans.js new file mode 100644 index 0000000..61cee7c --- /dev/null +++ b/api/controller_admin/pricing_plans.js @@ -0,0 +1,455 @@ +/** + * 价格套餐管理API - 后台管理 + * 提供价格套餐的增删改查功能 + */ + +const Framework = require("../../framework/node-core-framework.js"); + +module.exports = { + /** + * @swagger + * /admin_api/pricing_plans/list: + * post: + * summary: 获取价格套餐列表 + * description: 分页获取所有价格套餐 + * tags: [后台-价格套餐管理] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * pageOption: + * type: object + * properties: + * page: + * type: integer + * description: 页码 + * pageSize: + * type: integer + * description: 每页数量 + * seachOption: + * type: object + * properties: + * key: + * type: string + * description: 搜索字段 + * value: + * type: string + * description: 搜索值 + * is_active: + * type: integer + * description: 状态筛选(1=启用,0=禁用) + * responses: + * 200: + * description: 获取成功 + */ + 'POST /pricing_plans/list': async (ctx) => { + try { + const models = Framework.getModels(); + const { pricing_plans, op } = models; + const body = ctx.getBody(); + + // 获取分页参数 + const { limit, offset } = ctx.getPageSize(); + + // 构建查询条件 + const where = { is_delete: 0 }; + + // 搜索条件 + if (body.seachOption) { + const { key, value, is_active } = body.seachOption; + + if (value && key) { + if (key === 'name') { + where.name = { [op.like]: `%${value}%` }; + } else if (key === 'duration') { + where.duration = { [op.like]: `%${value}%` }; + } + } + + // 状态筛选 + if (is_active !== undefined && is_active !== null && is_active !== '') { + where.is_active = is_active; + } + } + + const result = await pricing_plans.findAndCountAll({ + where, + limit, + offset, + order: [['sort_order', 'ASC'], ['id', 'ASC']] + }); + + return ctx.success(result); + } catch (error) { + console.error('获取价格套餐列表失败:', error); + return ctx.fail('获取价格套餐列表失败: ' + error.message); + } + }, + + /** + * @swagger + * /admin_api/pricing_plans/detail: + * get: + * summary: 获取价格套餐详情 + * description: 根据ID获取价格套餐详细信息 + * tags: [后台-价格套餐管理] + * parameters: + * - in: query + * name: id + * required: true + * schema: + * type: integer + * description: 价格套餐ID + * responses: + * 200: + * description: 获取成功 + */ + 'GET /pricing_plans/detail': async (ctx) => { + try { + const models = Framework.getModels(); + const { pricing_plans } = models; + const { id } = ctx.getQuery(); + + if (!id) { + return ctx.fail('价格套餐ID不能为空'); + } + + const plan = await pricing_plans.findOne({ + where: { id, is_delete: 0 } + }); + + if (!plan) { + return ctx.fail('价格套餐不存在'); + } + + return ctx.success(plan); + } catch (error) { + console.error('获取价格套餐详情失败:', error); + return ctx.fail('获取价格套餐详情失败: ' + error.message); + } + }, + + /** + * @swagger + * /admin_api/pricing_plans/create: + * post: + * summary: 创建价格套餐 + * description: 创建新的价格套餐 + * tags: [后台-价格套餐管理] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - name + * - duration + * - days + * - price + * properties: + * name: + * type: string + * description: 套餐名称 + * duration: + * type: string + * description: 时长描述 + * days: + * type: integer + * description: 天数(-1表示永久) + * price: + * type: number + * description: 售价 + * original_price: + * type: number + * description: 原价 + * unit: + * type: string + * description: 单位 + * discount: + * type: string + * description: 折扣描述 + * features: + * type: array + * description: 功能特性列表 + * featured: + * type: integer + * description: 是否推荐(1=推荐,0=普通) + * is_active: + * type: integer + * description: 是否启用(1=启用,0=禁用) + * sort_order: + * type: integer + * description: 排序顺序 + * responses: + * 200: + * description: 创建成功 + */ + 'POST /pricing_plans/create': async (ctx) => { + try { + const models = Framework.getModels(); + const { pricing_plans } = models; + const body = ctx.getBody(); + const { name, duration, days, price, original_price, unit, discount, features, featured, is_active, sort_order } = body; + + // 验证必填字段 + if (!name) { + return ctx.fail('套餐名称不能为空'); + } + if (!duration) { + return ctx.fail('时长描述不能为空'); + } + if (days === undefined || days === null) { + return ctx.fail('天数不能为空'); + } + if (!price && price !== 0) { + return ctx.fail('价格不能为空'); + } + + // 验证价格 + if (price < 0) { + return ctx.fail('价格不能为负数'); + } + + // 验证天数 + if (days < -1) { + return ctx.fail('天数不能小于-1(-1表示永久)'); + } + + // 处理 features 字段:转换为 JSON 字符串 + let featuresStr = '[]'; + if (features) { + try { + if (Array.isArray(features)) { + featuresStr = JSON.stringify(features); + } else if (typeof features === 'string') { + // 验证是否为有效 JSON + const parsed = JSON.parse(features); + if (!Array.isArray(parsed)) { + return ctx.fail('功能特性必须是数组格式'); + } + featuresStr = features; + } else { + return ctx.fail('功能特性格式错误'); + } + } catch (e) { + return ctx.fail('功能特性JSON格式错误'); + } + } + + // 创建套餐 + const plan = await pricing_plans.create({ + name, + duration, + days, + price, + original_price: original_price !== undefined ? original_price : null, + unit: unit || '元', + discount: discount || null, + features: featuresStr, + featured: featured !== undefined ? featured : 0, + is_active: is_active !== undefined ? is_active : 1, + sort_order: sort_order !== undefined ? sort_order : 0, + is_delete: 0, + create_time: new Date(), + last_modify_time: new Date() + }); + + return ctx.success(plan); + } catch (error) { + console.error('创建价格套餐失败:', error); + return ctx.fail('创建价格套餐失败: ' + error.message); + } + }, + + /** + * @swagger + * /admin_api/pricing_plans/update: + * post: + * summary: 更新价格套餐 + * description: 更新价格套餐信息 + * tags: [后台-价格套餐管理] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - id + * properties: + * id: + * type: integer + * description: 价格套餐ID + * name: + * type: string + * description: 套餐名称 + * duration: + * type: string + * description: 时长描述 + * days: + * type: integer + * description: 天数 + * price: + * type: number + * description: 售价 + * original_price: + * type: number + * description: 原价 + * unit: + * type: string + * description: 单位 + * discount: + * type: string + * description: 折扣描述 + * features: + * type: array + * description: 功能特性列表 + * featured: + * type: integer + * description: 是否推荐 + * is_active: + * type: integer + * description: 是否启用 + * sort_order: + * type: integer + * description: 排序顺序 + * responses: + * 200: + * description: 更新成功 + */ + 'POST /pricing_plans/update': async (ctx) => { + try { + const models = Framework.getModels(); + const { pricing_plans } = models; + const body = ctx.getBody(); + const { id, name, duration, days, price, original_price, unit, discount, features, featured, is_active, sort_order } = body; + + if (!id) { + return ctx.fail('价格套餐ID不能为空'); + } + + const plan = await pricing_plans.findOne({ + where: { id, is_delete: 0 } + }); + + if (!plan) { + return ctx.fail('价格套餐不存在'); + } + + // 构建更新数据 + const updateData = { + last_modify_time: new Date() + }; + + if (name !== undefined) updateData.name = name; + if (duration !== undefined) updateData.duration = duration; + if (days !== undefined) { + if (days < -1) { + return ctx.fail('天数不能小于-1(-1表示永久)'); + } + updateData.days = days; + } + if (price !== undefined) { + if (price < 0) { + return ctx.fail('价格不能为负数'); + } + updateData.price = price; + } + if (original_price !== undefined) updateData.original_price = original_price; + if (unit !== undefined) updateData.unit = unit; + if (discount !== undefined) updateData.discount = discount; + if (featured !== undefined) updateData.featured = featured; + if (is_active !== undefined) updateData.is_active = is_active; + if (sort_order !== undefined) updateData.sort_order = sort_order; + + // 处理 features 字段 + if (features !== undefined) { + try { + if (Array.isArray(features)) { + updateData.features = JSON.stringify(features); + } else if (typeof features === 'string') { + const parsed = JSON.parse(features); + if (!Array.isArray(parsed)) { + return ctx.fail('功能特性必须是数组格式'); + } + updateData.features = features; + } else { + return ctx.fail('功能特性格式错误'); + } + } catch (e) { + return ctx.fail('功能特性JSON格式错误'); + } + } + + await pricing_plans.update(updateData, { + where: { id, is_delete: 0 } + }); + + return ctx.success({ message: '价格套餐更新成功' }); + } catch (error) { + console.error('更新价格套餐失败:', error); + return ctx.fail('更新价格套餐失败: ' + error.message); + } + }, + + /** + * @swagger + * /admin_api/pricing_plans/delete: + * post: + * summary: 删除价格套餐 + * description: 软删除指定的价格套餐 + * tags: [后台-价格套餐管理] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - id + * properties: + * id: + * type: integer + * description: 价格套餐ID + * responses: + * 200: + * description: 删除成功 + */ + 'POST /pricing_plans/delete': async (ctx) => { + try { + const models = Framework.getModels(); + const { pricing_plans } = models; + const { id } = ctx.getBody(); + + if (!id) { + return ctx.fail('价格套餐ID不能为空'); + } + + const plan = await pricing_plans.findOne({ + where: { id, is_delete: 0 } + }); + + if (!plan) { + return ctx.fail('价格套餐不存在'); + } + + // 软删除 + await pricing_plans.update( + { + is_delete: 1, + last_modify_time: new Date() + }, + { where: { id } } + ); + + return ctx.success({ message: '价格套餐删除成功' }); + } catch (error) { + console.error('删除价格套餐失败:', error); + return ctx.fail('删除价格套餐失败: ' + error.message); + } + } +}; diff --git a/api/controller_front/config.js b/api/controller_front/config.js index 7a4eab4..197ad8e 100644 --- a/api/controller_front/config.js +++ b/api/controller_front/config.js @@ -88,84 +88,41 @@ module.exports = { * description: 获取成功 */ 'GET /config/pricing-plans': async (ctx) => { - try { - // 写死4条价格套餐数据 - // 价格计算规则:2小时 = 1天 - const pricingPlans = [ - { - id: 1, - name: '体验套餐', - duration: '7天', - days: 7, - price: 28, - originalPrice: 28, - unit: '元', - features: [ - '7天使用权限', - '全功能体验', - '技术支持' - ], - featured: false - }, - { - id: 2, - name: '月度套餐', - duration: '30天', - days: 30, - price: 99, - originalPrice: 120, - unit: '元', - discount: '约8.3折', - features: [ - '30天使用权限', - '全功能使用', - '优先技术支持', - '性价比最高' - ], - featured: true - }, - { - id: 3, - name: '季度套餐', - duration: '90天', - days: 90, - price: 269, - originalPrice: 360, - unit: '元', - discount: '7.5折', - features: [ - '90天使用权限', - '全功能使用', - '优先技术支持', - '更优惠价格' - ], - featured: false - }, - { - id: 4, - name: '终生套餐', - duration: '永久', - days: -1, - price: 888, - originalPrice: null, - unit: '元', - discount: '超值', - features: [ - '永久使用权限', - '全功能使用', - '终身技术支持', - '一次购买,终身使用', - '最划算选择' - ], - featured: false - } - ]; - return ctx.success(pricingPlans); - } catch (error) { - console.error('获取价格套餐失败:', error); - return ctx.fail('获取价格套餐失败: ' + error.message); - } + const models = Framework.getModels(); + const { pricing_plans } = models; + + // 查询所有启用且未删除的套餐,按排序顺序返回 + const plans = await pricing_plans.findAll({ + where: { + is_active: 1, + is_delete: 0 + }, + order: [['sort_order', 'ASC'], ['id', 'ASC']], + attributes: [ + 'id', 'name', 'duration', 'days', 'price', + 'original_price', 'unit', 'discount', 'features', 'featured' + ] + }); + + // 转换数据格式以匹配前端期望 + const pricingPlans = plans.map(plan => { + const planData = plan.toJSON(); + + // 重命名字段以匹配前端期望(camelCase) + if (planData.original_price !== null) { + planData.originalPrice = planData.original_price; + } + delete planData.original_price; + + // 转换 featured 为布尔值 + planData.featured = planData.featured === 1; + + return planData; + }); + + return ctx.success(pricingPlans); + }, /** diff --git a/api/model/pricing_plans.js b/api/model/pricing_plans.js new file mode 100644 index 0000000..deb6f37 --- /dev/null +++ b/api/model/pricing_plans.js @@ -0,0 +1,97 @@ +const Sequelize = require('sequelize'); + +/** + * 价格套餐表模型 + * 存储各种价格套餐的配置信息,支持管理员在后台配置和管理 + */ +module.exports = (db) => { + const pricing_plans = db.define("pricing_plans", { + name: { + comment: '套餐名称(如:体验套餐、月度套餐等)', + type: Sequelize.STRING(100), + allowNull: false, + defaultValue: '' + }, + duration: { + comment: '时长描述(如:7天、30天、永久)', + type: Sequelize.STRING(50), + allowNull: false, + defaultValue: '' + }, + days: { + comment: '天数(-1表示永久,0表示无限制)', + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + }, + price: { + comment: '售价(元)', + type: Sequelize.DECIMAL(10, 2), + allowNull: false, + defaultValue: 0.00 + }, + original_price: { + comment: '原价(元),可为空表示无原价', + type: Sequelize.DECIMAL(10, 2), + allowNull: true, + defaultValue: null + }, + unit: { + comment: '价格单位', + type: Sequelize.STRING(20), + allowNull: false, + defaultValue: '元' + }, + discount: { + comment: '折扣描述(如:8.3折、超值)', + type: Sequelize.STRING(50), + allowNull: true, + defaultValue: null + }, + features: { + comment: '功能特性列表(JSON字符串数组)', + type: Sequelize.TEXT, + allowNull: false, + defaultValue: '[]' + }, + featured: { + comment: '是否为推荐套餐(1=推荐,0=普通)', + type: Sequelize.TINYINT(1), + allowNull: false, + defaultValue: 0 + }, + is_active: { + comment: '是否启用(1=启用,0=禁用)', + type: Sequelize.TINYINT(1), + allowNull: false, + defaultValue: 1 + }, + sort_order: { + comment: '排序顺序(越小越靠前)', + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + }, + + }, { + timestamps: false, + indexes: [ + { + unique: false, + fields: ['is_active'] + }, + { + unique: false, + fields: ['is_delete'] + }, + { + unique: false, + fields: ['sort_order'] + } + ] + }); + + // pricing_plans.sync({ force: true }); + + return pricing_plans; +} diff --git a/scripts/add_pricing_plans_menu.js b/scripts/add_pricing_plans_menu.js new file mode 100644 index 0000000..7626570 --- /dev/null +++ b/scripts/add_pricing_plans_menu.js @@ -0,0 +1,138 @@ +/** + * 添加"价格套餐管理"菜单项到用户管理菜单下 + * 执行 SQL 插入操作 + */ + +const Framework = require('../framework/node-core-framework.js'); +const frameworkConfig = require('../config/framework.config.js'); + +async function addPricingPlansMenu() { + console.log('🔄 开始添加"价格套餐管理"菜单项...\n'); + + try { + // 初始化框架 + console.log('正在初始化框架...'); + const framework = await Framework.init(frameworkConfig); + const models = Framework.getModels(); + + if (!models) { + throw new Error('无法获取模型列表'); + } + + // 从任意模型获取 sequelize 实例 + const Sequelize = require('sequelize'); + const firstModel = Object.values(models)[0]; + if (!firstModel || !firstModel.sequelize) { + throw new Error('无法获取数据库连接'); + } + const sequelize = firstModel.sequelize; + + // 查找用户管理菜单的ID + const [userMenu] = await sequelize.query( + `SELECT id FROM sys_menu WHERE name = '用户管理' AND parent_id = 0 AND is_delete = 0 LIMIT 1`, + { type: Sequelize.QueryTypes.SELECT } + ); + + let parentId = 120; // 默认 fallback 值 + if (userMenu && userMenu.id) { + parentId = userMenu.id; + console.log(`找到用户管理菜单,ID: ${parentId}`); + } else { + console.log(`未找到用户管理菜单,使用默认 parent_id: ${parentId}`); + } + + // 检查是否已存在 + const [existing] = await sequelize.query( + `SELECT id, name FROM sys_menu WHERE path = 'pricing_plans' AND is_delete = 0`, + { type: Sequelize.QueryTypes.SELECT } + ); + + if (existing) { + console.log(`⚠️ 菜单项已存在 (ID: ${existing.id}, 名称: ${existing.name})`); + console.log('✅ 无需重复添加\n'); + return; + } + + // 获取最大排序值 + const [maxSort] = await sequelize.query( + `SELECT MAX(sort) as maxSort FROM sys_menu WHERE parent_id = ${parentId} AND is_delete = 0`, + { type: Sequelize.QueryTypes.SELECT } + ); + const nextSort = (maxSort && maxSort.maxSort ? maxSort.maxSort : 0) + 1; + + // 执行插入 + await sequelize.query( + `INSERT INTO sys_menu ( + name, + parent_id, + model_id, + form_id, + icon, + path, + component, + api_path, + is_show_menu, + is_show, + type, + sort, + create_time, + last_modify_time, + is_delete + ) VALUES ( + '价格套餐管理', + ${parentId}, + 0, + 0, + 'md-pricetags', + 'pricing_plans', + 'system/pricing_plans.vue', + 'system/pricing_plans_server.js', + 1, + 1, + '页面', + ${nextSort}, + NOW(), + NOW(), + 0 + )`, + { type: Sequelize.QueryTypes.INSERT } + ); + + console.log('✅ "价格套餐管理"菜单项添加成功!\n'); + + // 验证插入结果 + const [menu] = await sequelize.query( + `SELECT id, name, parent_id, path, component, api_path, sort + FROM sys_menu + WHERE path = 'pricing_plans' AND is_delete = 0`, + { type: Sequelize.QueryTypes.SELECT } + ); + + if (menu) { + console.log('📋 菜单项详情:'); + console.log(` ID: ${menu.id}`); + console.log(` 名称: ${menu.name}`); + console.log(` 父菜单ID: ${menu.parent_id}`); + console.log(` 路由路径: ${menu.path}`); + console.log(` 组件路径: ${menu.component}`); + console.log(` API路径: ${menu.api_path}`); + console.log(` 排序: ${menu.sort}\n`); + } + + } catch (error) { + console.error('❌ 添加失败:', error.message); + console.error('\n详细错误:', error); + throw error; + } +} + +// 执行添加 +addPricingPlansMenu() + .then(() => { + console.log('✨ 操作完成!'); + process.exit(0); + }) + .catch(error => { + console.error('\n💥 执行失败:', error); + process.exit(1); + }); diff --git a/scripts/migrate_pricing_plans_data.js b/scripts/migrate_pricing_plans_data.js new file mode 100644 index 0000000..fb91fea --- /dev/null +++ b/scripts/migrate_pricing_plans_data.js @@ -0,0 +1,135 @@ +/** + * 迁移现有价格套餐数据到数据库 + * 将 config.js 中硬编码的 4 个套餐数据导入到 pricing_plans 表 + */ + +const Framework = require('../framework/node-core-framework.js'); +const frameworkConfig = require('../config/framework.config.js'); + +async function migratePricingPlans() { + console.log('🔄 开始迁移价格套餐数据...\n'); + + try { + // 初始化框架 + console.log('正在初始化框架...'); + const framework = await Framework.init(frameworkConfig); + const models = Framework.getModels(); + + if (!models || !models.pricing_plans) { + throw new Error('无法获取 pricing_plans 模型'); + } + + const { pricing_plans } = models; + + // 检查是否已有数据 + const existingCount = await pricing_plans.count({ where: { is_delete: 0 } }); + if (existingCount > 0) { + console.log(`⚠️ 已存在 ${existingCount} 条套餐数据,跳过迁移\n`); + return; + } + + // 现有的4个套餐数据(来自 api/controller_front/config.js) + const plans = [ + { + name: '体验套餐', + duration: '7天', + days: 7, + price: 28.00, + original_price: 28.00, + unit: '元', + discount: null, + features: JSON.stringify(['7天使用权限', '全功能体验', '技术支持']), + featured: 0, + is_active: 1, + sort_order: 1, + is_delete: 0, + create_time: new Date(), + last_modify_time: new Date() + }, + { + name: '月度套餐', + duration: '30天', + days: 30, + price: 99.00, + original_price: 120.00, + unit: '元', + discount: '约8.3折', + features: JSON.stringify(['30天使用权限', '全功能使用', '优先技术支持', '性价比最高']), + featured: 1, + is_active: 1, + sort_order: 2, + is_delete: 0, + create_time: new Date(), + last_modify_time: new Date() + }, + { + name: '季度套餐', + duration: '90天', + days: 90, + price: 269.00, + original_price: 360.00, + unit: '元', + discount: '7.5折', + features: JSON.stringify(['90天使用权限', '全功能使用', '优先技术支持', '更优惠价格']), + featured: 0, + is_active: 1, + sort_order: 3, + is_delete: 0, + create_time: new Date(), + last_modify_time: new Date() + }, + { + name: '终生套餐', + duration: '永久', + days: -1, + price: 888.00, + original_price: null, + unit: '元', + discount: '超值', + features: JSON.stringify(['永久使用权限', '全功能使用', '终身技术支持', '一次购买,终身使用', '最划算选择']), + featured: 0, + is_active: 1, + sort_order: 4, + is_delete: 0, + create_time: new Date(), + last_modify_time: new Date() + } + ]; + + // 批量插入 + await pricing_plans.bulkCreate(plans); + console.log('✅ 成功迁移 4 条价格套餐数据!\n'); + + // 验证插入结果 + const result = await pricing_plans.findAll({ + where: { is_delete: 0 }, + order: [['sort_order', 'ASC']] + }); + + console.log('📋 已迁移的套餐:'); + result.forEach(plan => { + const planData = plan.toJSON(); + console.log(` ${planData.id}. ${planData.name} - ${planData.duration} - ¥${planData.price}`); + if (planData.featured === 1) { + console.log(` [推荐套餐]`); + } + }); + console.log(''); + + } catch (error) { + console.error('❌ 迁移失败:', error.message); + console.error('\n详细错误:', error); + throw error; + } +} + +// 执行迁移 +migratePricingPlans() + .then(() => { + console.log('✨ 迁移完成!'); + process.exit(0); + }) + .catch(error => { + console.error('\n💥 执行失败:', error); + process.exit(1); + });