diff --git a/.gitignore b/.gitignore index 8b433fd..02decea 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,10 @@ server/* server/vendor/ server/thinkphp/ server/runtime/ -server/public/assets/ +server/public/assets/* +!server/public/assets/js/ +server/public/assets/js/* +!server/public/assets/js/backend/ server/public/uploads/ server/.env server/.well-known/ diff --git a/db/schema_phase15_menu_reorganize.sql b/db/schema_phase15_menu_reorganize.sql new file mode 100644 index 0000000..194ea80 --- /dev/null +++ b/db/schema_phase15_menu_reorganize.sql @@ -0,0 +1,191 @@ +-- ============================================================================ +-- 优匠优服 · Phase 15 后台菜单体系重构与数据库表结构对齐 +-- 1. 拆分混杂的「服务调度」,独立设立「订单调度 (order_manage)」与「服务商品 (service_manage)」 +-- 2. 按双边平台核心链路重排一级目录: +-- 数据大盘(200) -> 订单调度(190) -> 服务商品(180) -> 师傅管理(170) -> 客商管理(160) +-- -> 财务资金(150) -> 运营营销(140) -> 微信管理(90) -> 权限管理(80) -> 常规管理(70) -> 插件管理(10) +-- 3. 清理「运营营销」下未使用的框架演示菜单「全站分类 (category)」及「常规管理」下重复的「控制中心 (general/dashboard)」 +-- 4. 修正「权限管理」、「常规管理」内部子菜单排序,补齐缺失的权限子节点 +-- ============================================================================ +SET NAMES utf8mb4; + +-- 1. 创建「服务商品 (service_manage)」顶级目录(若不存在则插入) +INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`) +SELECT 'file', 0, 'service_manage', '服务商品', 'fa fa-cubes', '', '平台服务类目、标准工项与商品规格定价管理', 1, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 180, 'normal' +WHERE NOT EXISTS (SELECT `id` FROM `fa_auth_rule` WHERE `name` = 'service_manage'); + +-- 2. 更新所有一级目录的名称、图标、权重与备注 +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '数据大盘', `icon` = 'fa fa-tachometer', `weigh` = 200, `ismenu` = 1, `status` = 'normal', `remark` = '平台核心业务数据与履约实时监控' WHERE `name` = 'home'; +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '订单调度', `icon` = 'fa fa-tasks', `weigh` = 190, `ismenu` = 1, `status` = 'normal', `remark` = '同城服务订单流转、派单调度与售后评价闭环' WHERE `name` = 'order_manage'; +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '服务商品', `icon` = 'fa fa-cubes', `weigh` = 180, `ismenu` = 1, `status` = 'normal', `remark` = '平台服务类目、标准工项与商品规格定价管理' WHERE `name` = 'service_manage'; +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '师傅管理', `icon` = 'fa fa-id-badge', `weigh` = 170, `ismenu` = 1, `status` = 'normal', `remark` = '入驻维修师傅档案与专业技能体系管理' WHERE `name` = 'master_manage'; +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '客商管理', `icon` = 'fa fa-user-circle',`weigh` = 160, `ismenu` = 1, `status` = 'normal', `remark` = 'C端个人业主与B端合作企业商家管理' WHERE `name` = 'user'; +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '财务资金', `icon` = 'fa fa-money', `weigh` = 150, `ismenu` = 1, `status` = 'normal', `remark` = '平台订单结算、提现审批与发票开具管理' WHERE `name` = 'finance_manage'; +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '运营营销', `icon` = 'fa fa-bullhorn', `weigh` = 140, `ismenu` = 1, `status` = 'normal', `remark` = '轮播展位、优惠卡券与系统消息触达管理' WHERE `name` = 'operation_manage'; +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '微信管理', `icon` = 'fa fa-wechat', `weigh` = 90, `ismenu` = 1, `status` = 'normal', `remark` = '微信生态参数、菜单与自动回复配置' WHERE `name` = 'wechat'; +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '权限管理', `icon` = 'fa fa-group', `weigh` = 80, `ismenu` = 1, `status` = 'normal', `remark` = '后台管理员账号、角色权限与操作审计' WHERE `name` = 'auth'; +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '常规管理', `icon` = 'fa fa-cogs', `weigh` = 70, `ismenu` = 1, `status` = 'normal', `remark` = '系统全局参数、附件资源与服务器监控' WHERE `name` = 'general'; +UPDATE `fa_auth_rule` SET `pid` = 0, `title` = '插件管理', `icon` = 'fa fa-rocket', `weigh` = 10, `ismenu` = 1, `status` = 'normal', `remark` = '系统底层功能插件安装与配置管理' WHERE `name` = 'addon'; + +-- 3. 归集「订单调度 (order_manage)」子菜单:订单管理 -> 申诉工单 -> 评价管理 +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'order_manage' +SET r.pid = p.id, r.title = '订单管理', r.icon = 'fa fa-file-text', r.weigh = 195, r.ismenu = 1, r.status = 'normal', r.remark = '同城安装维修订单派单与履约跟踪' +WHERE r.name = 'order'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'order_manage' +SET r.pid = p.id, r.title = '申诉工单', r.icon = 'fa fa-shield', r.weigh = 193, r.ismenu = 1, r.status = 'normal', r.remark = '订单售后维权与服务争议仲裁处理' +WHERE r.name = 'appeal'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'order_manage' +SET r.pid = p.id, r.title = '评价管理', r.icon = 'fa fa-star', r.weigh = 191, r.ismenu = 1, r.status = 'normal', r.remark = '业主完工服务评价与晒单内容管理' +WHERE r.name = 'review'; + +-- 4. 归集「服务商品 (service_manage)」子菜单:商品管理 -> 服务类目 -> 服务项目 +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'service_manage' +SET r.pid = p.id, r.title = '商品管理', r.icon = 'fa fa-shopping-bag', r.weigh = 185, r.ismenu = 1, r.status = 'normal', r.remark = '施工商品标的物、细分规格与关联服务定价' +WHERE r.name = 'service'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'service_manage' +SET r.pid = p.id, r.title = '服务类目', r.icon = 'fa fa-th-large', r.weigh = 183, r.ismenu = 1, r.status = 'normal', r.remark = '平台家居安装维修一级服务品类管理' +WHERE r.name = 'service/cat'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'service_manage' +SET r.pid = p.id, r.title = '服务项目', r.icon = 'fa fa-sliders', r.weigh = 181, r.ismenu = 1, r.status = 'normal', r.remark = '安装、维修、拆旧等标准服务工项字典' +WHERE r.name = 'service/type'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'service_manage' +SET r.pid = p.id, r.title = '增值服务', r.icon = 'fa fa-plus-square', r.weigh = 179, r.ismenu = 0, r.status = 'hidden' +WHERE r.name = 'service/addon'; + +-- 5. 归集「师傅管理 (master_manage)」子菜单:师傅列表 -> 技能分类 -> 技能列表 +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'master_manage' +SET r.pid = p.id, r.title = '师傅列表', r.icon = 'fa fa-wrench', r.weigh = 175, r.ismenu = 1, r.status = 'normal', r.remark = '入驻师傅资质审核、状态与技能配置' +WHERE r.name = 'master'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'master_manage' +SET r.pid = p.id, r.title = '技能分类', r.icon = 'fa fa-folder-open', r.weigh = 173, r.ismenu = 1, r.status = 'normal', r.remark = '师傅专业服务技能大类管理' +WHERE r.name = 'skill_category'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'master_manage' +SET r.pid = p.id, r.title = '技能列表', r.icon = 'fa fa-tags', r.weigh = 171, r.ismenu = 1, r.status = 'normal', r.remark = '标准技能字典与师傅自定义技能管理' +WHERE r.name = 'skill_dict'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'master_manage' +SET r.pid = p.id, r.ismenu = 0, r.weigh = 169 +WHERE r.name = 'master_skill'; + +-- 6. 归集「客商管理 (user)」子菜单:用户列表 -> 商家档案 +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'user' +SET r.pid = p.id, r.title = '用户列表', r.icon = 'fa fa-user', r.weigh = 165, r.ismenu = 1, r.status = 'normal', r.remark = 'C端个人业主账号与资产信息管理' +WHERE r.name = 'user/user'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'user' +SET r.pid = p.id, r.title = '商家档案', r.icon = 'fa fa-building', r.weigh = 163, r.ismenu = 1, r.status = 'normal', r.remark = 'B端签约合作企业与商家资质管理' +WHERE r.name = 'merchant'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'user' +SET r.pid = p.id, r.ismenu = 0, r.weigh = 161 +WHERE r.name = 'user/group'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'user' +SET r.pid = p.id, r.ismenu = 0, r.weigh = 159 +WHERE r.name = 'user/rule'; + +-- 7. 归集「财务资金 (finance_manage)」子菜单:结算账单 -> 提现申请 -> 开票申请 +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'finance_manage' +SET r.pid = p.id, r.title = '结算账单', r.icon = 'fa fa-calculator', r.weigh = 155, r.ismenu = 1, r.status = 'normal', r.remark = '完工订单佣金核算与师傅结算账单' +WHERE r.name = 'settle'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'finance_manage' +SET r.pid = p.id, r.title = '提现申请', r.icon = 'fa fa-credit-card', r.weigh = 153, r.ismenu = 1, r.status = 'normal', r.remark = '师傅与账户余额提现审核及打款记录' +WHERE r.name = 'withdraw'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'finance_manage' +SET r.pid = p.id, r.title = '开票申请', r.icon = 'fa fa-file-text-o', r.weigh = 151, r.ismenu = 1, r.status = 'normal', r.remark = '业主与商家订单增值税发票开具管理' +WHERE r.name = 'invoice'; + +-- 8. 归集「运营营销 (operation_manage)」子菜单:轮播管理 -> 卡券管理 -> 消息通知 +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'operation_manage' +SET r.pid = p.id, r.title = '轮播管理', r.icon = 'fa fa-picture-o', r.weigh = 145, r.ismenu = 1, r.status = 'normal', r.remark = '小程序首页轮播图与金刚区视觉配置' +WHERE r.name = 'banner'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'operation_manage' +SET r.pid = p.id, r.title = '卡券管理', r.icon = 'fa fa-ticket', r.weigh = 143, r.ismenu = 1, r.status = 'normal', r.remark = '平台优惠券模板配置与发放核销管理' +WHERE r.name = 'coupon'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'operation_manage' +SET r.pid = p.id, r.title = '消息通知', r.icon = 'fa fa-bell', r.weigh = 141, r.ismenu = 1, r.status = 'normal', r.remark = '系统公告、派单提醒与交易消息记录' +WHERE r.name = 'message'; + +-- 9. 归集「微信管理 (wechat)」子菜单:配置管理 -> 菜单管理 -> 自动回复 -> 资源管理 +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'wechat' +SET r.pid = p.id, r.title = '配置管理', r.icon = 'fa fa-cog', r.weigh = 95, r.ismenu = 1, r.status = 'normal' +WHERE r.name = 'wechat/config'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'wechat' +SET r.pid = p.id, r.title = '菜单管理', r.icon = 'fa fa-list', r.weigh = 93, r.ismenu = 1, r.status = 'normal' +WHERE r.name = 'wechat/menu'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'wechat' +SET r.pid = p.id, r.title = '自动回复', r.icon = 'fa fa-reply-all', r.weigh = 91, r.ismenu = 1, r.status = 'normal' +WHERE r.name = 'wechat/autoreply'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'wechat' +SET r.pid = p.id, r.title = '资源管理', r.icon = 'fa fa-list-alt', r.weigh = 89, r.ismenu = 1, r.status = 'normal' +WHERE r.name = 'wechat/response'; + +-- 10. 归集「权限管理 (auth)」子菜单:管理账号 -> 角色管理 -> 菜单规则 -> 操作日志 +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'auth' +SET r.pid = p.id, r.title = '管理账号', r.icon = 'fa fa-user', r.weigh = 85, r.ismenu = 1, r.status = 'normal' +WHERE r.name = 'auth/admin'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'auth' +SET r.pid = p.id, r.title = '角色管理', r.icon = 'fa fa-group', r.weigh = 83, r.ismenu = 1, r.status = 'normal' +WHERE r.name = 'auth/group'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'auth' +SET r.pid = p.id, r.title = '菜单规则', r.icon = 'fa fa-bars', r.weigh = 81, r.ismenu = 1, r.status = 'normal' +WHERE r.name = 'auth/rule'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'auth' +SET r.pid = p.id, r.title = '操作日志', r.icon = 'fa fa-list-alt', r.weigh = 79, r.ismenu = 1, r.status = 'normal' +WHERE r.name = 'auth/adminlog'; + +-- 11. 归集「常规管理 (general)」子菜单:系统配置 -> 附件管理 -> 系统监控 -> 个人资料 +-- 同时将未使用的演示分类表 category 与重复的旧版控制台 general/dashboard 收纳并隐藏 +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'general' +SET r.pid = p.id, r.title = '系统配置', r.icon = 'fa fa-cog', r.weigh = 75, r.ismenu = 1, r.status = 'normal' +WHERE r.name = 'general/config'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'general' +SET r.pid = p.id, r.title = '附件管理', r.icon = 'fa fa-file-image-o', r.weigh = 73, r.ismenu = 1, r.status = 'normal' +WHERE r.name = 'general/attachment'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'general' +SET r.pid = p.id, r.title = '系统监控', r.icon = 'fa fa-server', r.weigh = 71, r.ismenu = 1, r.status = 'normal', r.remark = '服务器软硬件环境与数据库资源监控' +WHERE r.name = 'general/system'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'general' +SET r.pid = p.id, r.title = '个人资料', r.icon = 'fa fa-user', r.weigh = 69, r.ismenu = 1, r.status = 'normal', r.remark = '当前登录管理员个人资料与密码修改' +WHERE r.name = 'general/profile'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'general' +SET r.pid = p.id, r.title = '控制中心', r.icon = 'fa fa-dashboard', r.weigh = 65, r.ismenu = 0, r.status = 'hidden' +WHERE r.name = 'general/dashboard'; + +UPDATE `fa_auth_rule` r JOIN `fa_auth_rule` p ON p.name = 'general' +SET r.pid = p.id, r.title = '全站分类', r.icon = 'fa fa-leaf', r.weigh = 63, r.ismenu = 0, r.status = 'hidden' +WHERE r.name = 'category'; + +-- 12. 补齐数据大盘 (home) 与系统监控 (general/system) 的查看权限子节点 +INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`) +SELECT 'file', p.id, 'home/index', '查看', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal' +FROM `fa_auth_rule` p +WHERE p.name = 'home' + AND NOT EXISTS (SELECT `id` FROM `fa_auth_rule` WHERE `name` = 'home/index'); + +INSERT INTO `fa_auth_rule` (`type`, `pid`, `name`, `title`, `icon`, `condition`, `remark`, `ismenu`, `createtime`, `updatetime`, `weigh`, `status`) +SELECT 'file', p.id, 'general/system/index', '查看', 'fa fa-circle-o', '', '', 0, UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), 0, 'normal' +FROM `fa_auth_rule` p +WHERE p.name = 'general/system' + AND NOT EXISTS (SELECT `id` FROM `fa_auth_rule` WHERE `name` = 'general/system/index'); diff --git a/db/schema_phase15_service_pricing.sql b/db/schema_phase15_service_pricing.sql new file mode 100644 index 0000000..d26a87c --- /dev/null +++ b/db/schema_phase15_service_pricing.sql @@ -0,0 +1,39 @@ +-- ============================================================================ +-- Phase 15: 商品管理、细分规格与全局服务字典(方案 2 定价架构)结构升级与索引优化 +-- 1. fa_service_cat : 扩容 icon 字段长度至 varchar(255),规范表注释 +-- 2. fa_service : 规范商品管理表及字段注释,新增复合索引 idx_cat_status_weigh +-- 3. fa_service_spec : 明确 base_price 注释(0.00=沿用商品默认价,>0=特殊规格专属一口价),新增复合索引 idx_service_status_weigh +-- 4. fa_service_type : 统一 support_fixed 默认值为 1,规范表注释 +-- 5. fa_service_addon: 移除已废弃的冗余表及权限菜单节点(增项服务已统一并入 fa_service_type) +-- ============================================================================ + +ALTER TABLE `fa_service_cat` + MODIFY COLUMN `icon` varchar(255) NOT NULL DEFAULT '' COMMENT '类目图标URL', + COMMENT='服务类目表(一级垂直行业分类)'; + +ALTER TABLE `fa_service` + MODIFY COLUMN `name` varchar(100) NOT NULL DEFAULT '' COMMENT '商品名称(施工标的物)', + MODIFY COLUMN `price_type` enum('fixed','quote','range') NOT NULL DEFAULT 'fixed' COMMENT '默认计价模式:fixed=一口价', + MODIFY COLUMN `service_types` text COMMENT '关联主服务及默认一口价JSON', + MODIFY COLUMN `addons` text COMMENT '关联增项服务及加价JSON', + MODIFY COLUMN `base_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '商品默认首选服务一口价', + ADD INDEX `idx_cat_status_weigh` (`cat_id`, `status`, `weigh`), + COMMENT='商品管理表(施工标的物与商品级默认服务定价)'; + +ALTER TABLE `fa_service_spec` + MODIFY COLUMN `service_id` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '所属商品ID(关联fa_service.id)', + MODIFY COLUMN `name` varchar(100) NOT NULL DEFAULT '' COMMENT '细分规格/型号名称', + MODIFY COLUMN `description` varchar(255) NOT NULL DEFAULT '' COMMENT '规格特征说明', + MODIFY COLUMN `base_price` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '特殊规格专属一口价(0.00=沿用商品默认服务价)', + ADD INDEX `idx_service_status_weigh` (`service_id`, `status`, `weigh`), + COMMENT='商品细分规格表(支持特殊规格单独设价覆盖)'; + +ALTER TABLE `fa_service_type` + MODIFY COLUMN `support_fixed` tinyint(1) unsigned NOT NULL DEFAULT '1' COMMENT '支持一口价:1=是,0=否(仅师傅报价)', + COMMENT='全局服务字典表(主服务项目与增项服务统一字典)'; + +UPDATE `fa_service_type` SET `support_fixed` = 1 WHERE `code` IN ('install', 'repair', 'remove', 'filter', 'glue', 'drill', 'open_hole', 'cut') OR `can_be_addon` = 1; +UPDATE `fa_service_type` SET `support_fixed` = 0, `default_price` = 0.00 WHERE `code` IN ('paint_repair', 'measure', 'deliver', 'deliver_install', 'deliver_downstairs', 'return_goods', 'maintain', 'grout', 'clean', 'film', 'move', 'other') AND `can_be_addon` = 0; + +DROP TABLE IF EXISTS `fa_service_addon`; +DELETE FROM `fa_auth_rule` WHERE `name` LIKE 'service/addon%'; diff --git a/scripts/reorganize_menus.php b/scripts/reorganize_menus.php index 101b90f..ff81ee7 100644 --- a/scripts/reorganize_menus.php +++ b/scripts/reorganize_menus.php @@ -1,106 +1,16 @@ setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); -// 1. 定义 4 个新父级分类目录 -$parentCategories = [ - [ - 'name' => 'order_manage', - 'title' => '服务与调度', - 'icon' => 'fa fa-tasks', - 'weigh' => 190, - ], - [ - 'name' => 'capacity_manage', - 'title' => '运力与商户', - 'icon' => 'fa fa-id-badge', - 'weigh' => 170, - ], - [ - 'name' => 'finance_manage', - 'title' => '财务与资金', - 'icon' => 'fa fa-money', - 'weigh' => 150, - ], - [ - 'name' => 'operation_manage', - 'title' => '运营与营销', - 'icon' => 'fa fa-bullhorn', - 'weigh' => 130, - ], -]; - -$catIds = []; -$now = time(); - -foreach ($parentCategories as $cat) { - // 检查是否已存在 - $stmt = $pdo->prepare("SELECT id FROM fa_auth_rule WHERE name = ?"); - $stmt->execute([$cat['name']]); - $id = $stmt->fetchColumn(); - if (!$id) { - $insertStmt = $pdo->prepare("INSERT INTO fa_auth_rule (type, pid, name, title, icon, `condition`, remark, ismenu, createtime, updatetime, weigh, status) VALUES ('file', 0, ?, ?, ?, '', '', 1, ?, ?, ?, 'normal')"); - $insertStmt->execute([$cat['name'], $cat['title'], $cat['icon'], $now, $now, $cat['weigh']]); - $id = $pdo->lastInsertId(); - echo "Created parent menu [{$cat['title']}] with ID: $id\n"; - } else { - $updateStmt = $pdo->prepare("UPDATE fa_auth_rule SET title = ?, icon = ?, weigh = ?, ismenu = 1, status = 'normal' WHERE id = ?"); - $updateStmt->execute([$cat['title'], $cat['icon'], $cat['weigh'], $id]); - echo "Updated parent menu [{$cat['title']}] with ID: $id\n"; - } - $catIds[$cat['name']] = $id; +$sqlFile = __DIR__ . '/../db/schema_phase15_menu_reorganize.sql'; +if (!file_exists($sqlFile)) { + die("SQL file not found: $sqlFile\n"); } -// 2. 将子菜单调整到对应父分类下,并分配专属高辨识度图标和权重 -$menuUpdates = [ - // 业务与调度 (order_manage) - 119 => ['pid' => $catIds['order_manage'], 'title' => '订单管理', 'icon' => 'fa fa-shopping-cart', 'weigh' => 195], - 155 => ['pid' => $catIds['order_manage'], 'title' => '服务项目', 'icon' => 'fa fa-th-list', 'weigh' => 194], - 185 => ['pid' => $catIds['order_manage'], 'title' => '服务类目', 'icon' => 'fa fa-cubes', 'weigh' => 193], - 143 => ['pid' => $catIds['order_manage'], 'title' => '评价管理', 'icon' => 'fa fa-star', 'weigh' => 192], - 161 => ['pid' => $catIds['order_manage'], 'title' => '申诉工单', 'icon' => 'fa fa-shield', 'weigh' => 191], +$sql = file_get_contents($sqlFile); +$pdo->exec($sql); - // 运力与商户 (capacity_manage) - 125 => ['pid' => $catIds['capacity_manage'], 'title' => '师傅档案', 'icon' => 'fa fa-wrench', 'weigh' => 175], - 131 => ['pid' => $catIds['capacity_manage'], 'title' => '商家档案', 'icon' => 'fa fa-building', 'weigh' => 174], - - // 财务与资金 (finance_manage) - 179 => ['pid' => $catIds['finance_manage'], 'title' => '结算账单', 'icon' => 'fa fa-calculator', 'weigh' => 155], - 173 => ['pid' => $catIds['finance_manage'], 'title' => '提现申请', 'icon' => 'fa fa-credit-card', 'weigh' => 154], - 167 => ['pid' => $catIds['finance_manage'], 'title' => '开票申请', 'icon' => 'fa fa-file-text-o', 'weigh' => 153], - - // 运营与营销 (operation_manage) - 137 => ['pid' => $catIds['operation_manage'], 'title' => '优惠券管理', 'icon' => 'fa fa-ticket', 'weigh' => 135], - 113 => ['pid' => $catIds['operation_manage'], 'title' => '轮播管理', 'icon' => 'fa fa-picture-o', 'weigh' => 134], - 149 => ['pid' => $catIds['operation_manage'], 'title' => '消息通知', 'icon' => 'fa fa-bell', 'weigh' => 133], - - // 现有一级菜单权重与图标强化 - 1 => ['pid' => 0, 'title' => '控制台', 'icon' => 'fa fa-dashboard', 'weigh' => 200], - 66 => ['pid' => 0, 'title' => '会员管理', 'icon' => 'fa fa-user-circle', 'weigh' => 110], - 85 => ['pid' => 0, 'title' => '微信管理', 'icon' => 'fa fa-wechat', 'weigh' => 90], - 2 => ['pid' => 0, 'title' => '常规管理', 'icon' => 'fa fa-cogs', 'weigh' => 70], - 5 => ['pid' => 0, 'title' => '权限管理', 'icon' => 'fa fa-group', 'weigh' => 50], -]; - -foreach ($menuUpdates as $id => $u) { - $stmt = $pdo->prepare("UPDATE fa_auth_rule SET pid = ?, title = ?, icon = ?, weigh = ? WHERE id = ?"); - $stmt->execute([$u['pid'], $u['title'], $u['icon'], $u['weigh'], $id]); - echo "Updated menu [$id] -> {$u['title']}\n"; -} - -// 3. 在 常规管理 (general, id: 2) 下添加原控制台的迁移目标:系统监控 (general/system) -$stmt = $pdo->prepare("SELECT id FROM fa_auth_rule WHERE name = 'general/system'"); -$stmt->execute(); -$systemId = $stmt->fetchColumn(); -if (!$systemId) { - $stmt = $pdo->prepare("INSERT INTO fa_auth_rule (type, pid, name, title, icon, `condition`, remark, ismenu, createtime, updatetime, weigh, status) VALUES ('file', 2, 'general/system', '系统监控', 'fa fa-server', '', '系统运维与服务器状态监控', 1, ?, ?, 65, 'normal')"); - $stmt->execute([$now, $now]); - $systemId = $pdo->lastInsertId(); - echo "Created [系统监控] menu under general with ID: $systemId\n"; -} else { - $stmt = $pdo->prepare("UPDATE fa_auth_rule SET pid = 2, title = '系统监控', icon = 'fa fa-server', weigh = 65, ismenu = 1, status = 'normal' WHERE id = ?"); - $stmt->execute([$systemId]); - echo "Updated [系统监控] menu with ID: $systemId\n"; -} - -echo "\nMenu reorganization completed successfully!\n"; +echo "Menu reorganization (Phase 15) completed successfully!\n"; diff --git a/server/application/admin/controller/Service.php b/server/application/admin/controller/Service.php index db932cc..fc44d78 100644 --- a/server/application/admin/controller/Service.php +++ b/server/application/admin/controller/Service.php @@ -26,7 +26,6 @@ class Service extends Backend { parent::_initialize(); $this->model = new \app\admin\model\Service; - $this->view->assign("priceTypeList", $this->model->getPriceTypeList()); $this->view->assign("statusList", $this->model->getStatusList()); // 获取统一服务字典表(fa_service_type,不绑定单一类目,主服务与增项服务同源) @@ -66,7 +65,7 @@ class Service extends Backend } // 一次性批量查询当前页商品的细分规格,避免 N+1 查询 $allSpecs = Db::name('service_spec') - ->field('id,service_id,name,description') + ->field('id,service_id,name,base_price,description') ->where('service_id', 'in', $serviceIds) ->where('status', 'normal') ->order('weigh desc, id asc') @@ -77,7 +76,8 @@ class Service extends Backend if (!isset($specMap[$sid])) { $specMap[$sid] = []; } - $specMap[$sid][] = $sp['name']; + $spPrice = (float)$sp['base_price']; + $specMap[$sid][] = $spPrice > 0 ? ($sp['name'] . '(¥' . round($spPrice) . ')') : $sp['name']; } foreach ($rows as &$r) { $sid = (int)$r['id']; @@ -125,7 +125,7 @@ class Service extends Backend $serviceId = $this->model->id; // 处理商品规格列表 - $this->syncSpecs($serviceId, $this->request->post('specs/a', []), isset($params['base_price']) ? (float)$params['base_price'] : 0); + $this->syncSpecs($serviceId, $this->request->post('specs/a', [])); Db::commit(); } catch (ValidateException|PDOException|Exception $e) { @@ -168,7 +168,7 @@ class Service extends Backend $result = $row->allowField(true)->save($params); // 同步处理商品规格列表 - $this->syncSpecs($row->id, $this->request->post('specs/a', []), isset($params['base_price']) ? (float)$params['base_price'] : (float)$row->base_price); + $this->syncSpecs($row->id, $this->request->post('specs/a', [])); Db::commit(); } catch (ValidateException|PDOException|Exception $e) { @@ -220,20 +220,75 @@ class Service extends Backend if (isset($params['service_types_raw'])) { if (is_string($params['service_types_raw'])) { $parsedTypes = json_decode($params['service_types_raw'], true) ?: []; - $params['service_types'] = $params['service_types_raw']; } else { $parsedTypes = is_array($params['service_types_raw']) ? $params['service_types_raw'] : []; - $params['service_types'] = json_encode($parsedTypes, JSON_UNESCAPED_UNICODE); } unset($params['service_types_raw']); + + if (empty($parsedTypes)) { + throw new ValidateException('请至少开启 1 项主服务'); + } + + $dictSupportMap = Db::name('service_type')->column('support_fixed', 'code'); + $hasDefault = false; + foreach ($parsedTypes as &$pt) { + $code = isset($pt['code']) ? (string)$pt['code'] : ''; + $supportFixed = isset($dictSupportMap[$code]) + ? (int)$dictSupportMap[$code] + : (isset($pt['support_fixed']) ? (int)$pt['support_fixed'] : 1); + $price = ($supportFixed === 1 && isset($pt['price'])) ? round((float)$pt['price'], 2) : 0.00; + $pt['price'] = $price; + $pt['support_fixed'] = $supportFixed; + if ($supportFixed === 1 && !empty($pt['is_default']) && !$hasDefault) { + $pt['is_default'] = 1; + $hasDefault = true; + } else { + $pt['is_default'] = 0; + } + } + unset($pt); + + if (!$hasDefault && !empty($parsedTypes)) { + foreach ($parsedTypes as &$ptDef) { + if ((int)$ptDef['support_fixed'] === 1) { + $ptDef['is_default'] = 1; + $hasDefault = true; + break; + } + } + unset($ptDef); + if (!$hasDefault) { + $parsedTypes[0]['is_default'] = 1; + } + } + + $params['service_types'] = json_encode(array_values($parsedTypes), JSON_UNESCAPED_UNICODE); } if (isset($params['addons_raw'])) { - $params['addons'] = is_string($params['addons_raw']) ? $params['addons_raw'] : json_encode($params['addons_raw'], JSON_UNESCAPED_UNICODE); + $parsedAddons = is_string($params['addons_raw']) + ? (json_decode($params['addons_raw'], true) ?: []) + : (is_array($params['addons_raw']) ? $params['addons_raw'] : []); unset($params['addons_raw']); + $cleanAddons = []; + foreach ($parsedAddons as $idx => $ad) { + $adName = isset($ad['name']) ? trim((string)$ad['name']) : ''; + $adPrice = isset($ad['price']) ? round((float)$ad['price'], 2) : 0.00; + if ($adName === '' || $adPrice <= 0) { + continue; + } + $cleanAddons[] = [ + 'id' => isset($ad['id']) ? (int)$ad['id'] : ($idx + 1), + 'code' => isset($ad['code']) ? (string)$ad['code'] : ('addon_' . ($idx + 1)), + 'name' => $adName, + 'unit' => !empty($ad['unit']) ? (string)$ad['unit'] : '份', + 'price' => $adPrice, + ]; + } + $params['addons'] = json_encode($cleanAddons, JSON_UNESCAPED_UNICODE); } - // 自动从关联的主服务(优先默认主服务或“安装”)同步底层 base_price 与 price_type,商品无需单独填价格 + // 自动从关联的主服务(优先默认首选主服务)同步底层 base_price 与 price_type if (!empty($parsedTypes)) { $defItem = $parsedTypes[0]; foreach ($parsedTypes as $pt) { @@ -251,9 +306,9 @@ class Service extends Backend } /** - * 同步商品规格型号表(规格不设独立商品价格,仅标识具体施工标的物型号) + * 同步商品规格型号表(支持特殊规格单独设价覆盖,留空或0则沿用商品默认服务价) */ - protected function syncSpecs($serviceId, $specsInput, $defaultBasePrice = 0.00) + protected function syncSpecs($serviceId, $specsInput) { if (empty($serviceId)) return; $now = time(); @@ -264,6 +319,9 @@ class Service extends Backend $name = trim(isset($item['name']) ? $item['name'] : ''); if ($name === '') continue; $specDesc = isset($item['description']) ? trim($item['description']) : ''; + $specPrice = (isset($item['base_price']) && is_numeric($item['base_price']) && (float)$item['base_price'] > 0) + ? round((float)$item['base_price'], 2) + : 0.00; $specId = isset($item['id']) ? (int)$item['id'] : 0; $weigh = max(0, 100 - $idx * 5); $idx++; @@ -271,7 +329,7 @@ class Service extends Backend if ($specId > 0) { Db::name('service_spec')->where('id', $specId)->where('service_id', $serviceId)->update([ 'name' => $name, - 'base_price' => $defaultBasePrice, + 'base_price' => $specPrice, 'description' => $specDesc, 'weigh' => $weigh, 'updatetime' => $now, @@ -281,7 +339,7 @@ class Service extends Backend $newId = Db::name('service_spec')->insertGetId([ 'service_id' => $serviceId, 'name' => $name, - 'base_price' => $defaultBasePrice, + 'base_price' => $specPrice, 'description' => $specDesc, 'weigh' => $weigh, 'status' => 'normal', diff --git a/server/application/admin/controller/service/Addon.php b/server/application/admin/controller/service/Addon.php deleted file mode 100644 index c1692d5..0000000 --- a/server/application/admin/controller/service/Addon.php +++ /dev/null @@ -1,51 +0,0 @@ -model = new \app\admin\model\service\Addon; - $this->view->assign("statusList", $this->model->getStatusList()); - - // 获取分类列表 - $cats = Db::name('service_cat')->where('status', 'normal')->column('id,name'); - $this->view->assign("catList", [0 => '通用全部类目'] + $cats); - } - - public function index() - { - $this->request->filter(['strip_tags', 'trim']); - if (false === $this->request->isAjax()) { - return $this->view->fetch(); - } - if ($this->request->request('keyField')) { - return $this->selectpage(); - } - [$where, $sort, $order, $offset, $limit] = $this->buildparams(); - $list = $this->model - ->with(['cat']) - ->where($where) - ->order($sort, $order) - ->paginate($limit); - $result = ['total' => $list->total(), 'rows' => $list->items()]; - return json($result); - } -} diff --git a/server/application/admin/lang/zh-cn/service/addon.php b/server/application/admin/lang/zh-cn/service/addon.php deleted file mode 100644 index 49dafce..0000000 --- a/server/application/admin/lang/zh-cn/service/addon.php +++ /dev/null @@ -1,12 +0,0 @@ - '所属服务类目', - 'Name' => '增值服务名称', - 'Unit' => '计价单位', - 'Default_price' => '平台指导价(元)', - 'Weigh' => '排序权重', - 'Status' => '服务状态', - 'Normal' => '正常启用', - 'Hidden' => '隐藏下架', -]; diff --git a/server/application/admin/model/Service.php b/server/application/admin/model/Service.php index 968c7b1..7b64979 100644 --- a/server/application/admin/model/Service.php +++ b/server/application/admin/model/Service.php @@ -2,19 +2,14 @@ namespace app\admin\model; +use think\Db; use think\Model; - class Service extends Model { - - - - - // 表名 protected $name = 'service'; - + // 自动写入时间戳字段 protected $autoWriteTimestamp = 'integer'; @@ -28,7 +23,6 @@ class Service extends Model 'price_type_text', 'status_text' ]; - protected static function init() { @@ -38,9 +32,13 @@ class Service extends Model $row->getQuery()->where($pk, $row[$pk])->update(['weigh' => $row[$pk]]); } }); + + self::afterDelete(function ($row) { + $pk = $row->getPk(); + Db::name('service_spec')->where('service_id', $row[$pk])->delete(); + }); } - public function getPriceTypeList() { return ['fixed' => __('Fixed'), 'quote' => __('Quote'), 'range' => __('Range')]; @@ -51,7 +49,6 @@ class Service extends Model return ['normal' => __('Normal'), 'hidden' => __('Hidden')]; } - public function getPriceTypeTextAttr($value, $data) { $value = $value ?: ($data['price_type'] ?? ''); @@ -59,7 +56,6 @@ class Service extends Model return $list[$value] ?? ''; } - public function getStatusTextAttr($value, $data) { $value = $value ?: ($data['status'] ?? ''); @@ -67,10 +63,6 @@ class Service extends Model return $list[$value] ?? ''; } - - - - public function cat() { return $this->belongsTo('app\\admin\\model\\service\\Cat', 'cat_id', 'id', [], 'LEFT')->setEagerlyType(0); diff --git a/server/application/admin/model/service/Addon.php b/server/application/admin/model/service/Addon.php deleted file mode 100644 index 22063f6..0000000 --- a/server/application/admin/model/service/Addon.php +++ /dev/null @@ -1,41 +0,0 @@ - __('Normal'), 'hidden' => __('Hidden')]; - } - - public function getStatusTextAttr($value, $data) - { - $value = $value ? $value : (isset($data['status']) ? $data['status'] : ''); - $list = $this->getStatusList(); - return isset($list[$value]) ? $list[$value] : ''; - } - - public function cat() - { - return $this->belongsTo('app\admin\model\service\Cat', 'cat_id', 'id', [], 'LEFT')->setEagerlyType(0); - } -} diff --git a/server/application/admin/view/service/add.html b/server/application/admin/view/service/add.html index c4f03d2..9ba0356 100644 --- a/server/application/admin/view/service/add.html +++ b/server/application/admin/view/service/add.html @@ -195,14 +195,18 @@
| 序号 | -细分规格/款式名称 | +细分规格/款式名称 | +特殊规格一口价(选填) | 规格说明或施工提示(选填) | 操作 | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
{$name}
@@ -249,6 +253,7 @@
+ {if $mainSupportFixed == 1}
@@ -323,18 +331,6 @@
$row.toggleClass('is-row-enabled', on).toggleClass('is-row-disabled', !on);
});
- $(document).on('change', '.st-support-fixed', function () {
- var $row = $(this).closest('tr');
- var isFixed = parseInt($(this).val(), 10) === 1;
- if (isFixed) {
- $row.find('.st-price-wrap').css('display', 'inline-table');
- $row.find('.st-quote-hint').hide();
- } else {
- $row.find('.st-price-wrap').hide();
- $row.find('.st-quote-hint').css('display', 'inline-flex');
- }
- });
-
function collectServiceConfig() {
var serviceTypes = [];
var addons = [];
@@ -345,15 +341,16 @@
var code = $row.data('code');
var name = $row.data('name');
var unit = $row.data('unit') || '份';
+ var supportFixed = parseInt($row.data('support-fixed'), 10) === 1 ? 1 : 0;
if ($row.find('.st-enable').is(':checked')) {
- var mainPrice = parseFloat($row.find('.st-price').val()) || 0;
- var isDef = $row.find('.st-default').is(':checked') ? 1 : 0;
+ var mainPrice = supportFixed === 1 ? (parseFloat($row.find('.st-price').val()) || 0) : 0;
+ var isDef = (supportFixed === 1 && $row.find('.st-default').is(':checked')) ? 1 : 0;
serviceTypes.push({
code: code,
name: name,
price: mainPrice,
- support_fixed: 1,
+ support_fixed: supportFixed,
is_default: isDef
});
}
@@ -386,6 +383,7 @@
var html = '
¥
@@ -257,6 +262,9 @@
+ {else/}
+ 不支持一口价(下单时自动转师傅报价)
+ {/if}
未启用主服务
' + seq + ' | ' +
'' +
+ ' | | ' +
'¥ ' +
' | ' +
' |
- {:build_heading()}
-
-
diff --git a/server/application/admin/view/service/edit.html b/server/application/admin/view/service/edit.html
index 1b7f965..7f89be0 100644
--- a/server/application/admin/view/service/edit.html
+++ b/server/application/admin/view/service/edit.html
@@ -429,7 +429,7 @@
-
-
-
-
-
-
-
-
商品细分型号与款式规格
- (用户下单选择该商品时,二级联动展示的具体款式,无单独商品价格)
+ (常规规格留空价格即可,自动沿用商品默认服务价;仅施工难度特殊的规格需填专属一口价)
序号 |
- 细分规格/款式名称 |
+ 细分规格/款式名称 |
+ 特殊规格一口价(选填) |
规格说明或施工提示(选填) |
操作 |
+ |
+ ¥
+
+
+
|
@@ -492,9 +499,9 @@
$unit = $tp['unit'] ?: ($row['unit'] ?: '份');
$isMainChecked = !empty($savedMainMap) ? isset($savedMainMap[$code]) : ($tp['can_be_main'] == 1 && in_array($code, ['install','repair','remove']));
- $mainSupportFixed = isset($savedMainMap[$code]['support_fixed']) ? (int)$savedMainMap[$code]['support_fixed'] : (int)$tp['support_fixed'];
- $mainPrice = isset($savedMainMap[$code]['price']) ? (float)$savedMainMap[$code]['price'] : (float)$tp['default_price'];
- $isMainDefault = isset($savedMainMap[$code]['is_default']) ? (int)$savedMainMap[$code]['is_default'] : (int)$tp['is_default'];
+ $mainSupportFixed = (int)$tp['support_fixed'];
+ $mainPrice = isset($savedMainMap[$code]['price']) && (float)$savedMainMap[$code]['price'] > 0 ? (float)$savedMainMap[$code]['price'] : (float)$tp['default_price'];
+ $isMainDefault = ($mainSupportFixed === 1 && isset($savedMainMap[$code]['is_default'])) ? (int)$savedMainMap[$code]['is_default'] : ($mainSupportFixed === 1 ? (int)$tp['is_default'] : 0);
$matchedAddon = isset($savedAddonMap[$code]) ? $savedAddonMap[$code] : (isset($savedAddonMap[$name]) ? $savedAddonMap[$name] : null);
$isAddonChecked = !empty($savedAddonMap) ? ($matchedAddon !== null) : ($code === 'remove');
@@ -502,7 +509,7 @@
$addonUnit = $matchedAddon !== null && !empty($matchedAddon['unit']) ? $matchedAddon['unit'] : $unit;
$rowEnabled = ($isMainChecked || $isAddonChecked);
?>
-
|
@@ -520,6 +527,7 @@
+ {if $mainSupportFixed == 1}
¥
@@ -529,6 +537,9 @@
+ {else/}
+ 不支持一口价(下单时自动转师傅报价)
+ {/if}
未启用主服务
@@ -617,19 +628,6 @@
refreshRowAndCounters($row);
});
- // 切换一口价/仅报价模式
- $(document).on('change', '.st-support-fixed', function () {
- var $row = $(this).closest('tr');
- var isFixed = parseInt($(this).val(), 10) === 1;
- if (isFixed) {
- $row.find('.st-price-wrap').css('display', 'inline-table');
- $row.find('.st-quote-hint').hide();
- } else {
- $row.find('.st-price-wrap').hide();
- $row.find('.st-quote-hint').css('display', 'inline-flex');
- }
- });
-
// 首选单选高亮
$(document).on('change', '.st-default', function () {
$('.srv-default-label').removeClass('is-def-active');
@@ -673,15 +671,16 @@
var code = $row.data('code');
var name = $row.data('name');
var unit = $row.data('unit') || '份';
+ var supportFixed = parseInt($row.data('support-fixed'), 10) === 1 ? 1 : 0;
if ($row.find('.st-enable').is(':checked')) {
- var mainPrice = parseFloat($row.find('.st-price').val()) || 0;
- var isDef = $row.find('.st-default').is(':checked') ? 1 : 0;
+ var mainPrice = supportFixed === 1 ? (parseFloat($row.find('.st-price').val()) || 0) : 0;
+ var isDef = (supportFixed === 1 && $row.find('.st-default').is(':checked')) ? 1 : 0;
serviceTypes.push({
code: code,
name: name,
price: mainPrice,
- support_fixed: 1,
+ support_fixed: supportFixed,
is_default: isDef
});
}
@@ -714,6 +713,7 @@
var html = '' + seq + ' | ' +
'' +
+ ' | | ' +
'¥ ' +
' | ' +
' |
-
diff --git a/server/application/admin/view/service/type/edit.html b/server/application/admin/view/service/type/edit.html
index b527271..073b0e6 100644
--- a/server/application/admin/view/service/type/edit.html
+++ b/server/application/admin/view/service/type/edit.html
@@ -31,20 +31,27 @@
+
+
-
+
+
+
+
+
+
+
-
diff --git a/server/application/api/controller/Home.php b/server/application/api/controller/Home.php
index 4715bb0..52963d2 100644
--- a/server/application/api/controller/Home.php
+++ b/server/application/api/controller/Home.php
@@ -415,9 +415,6 @@ class Home extends Base
$types = $dec;
}
}
- if (empty($types)) {
- $types = $this->resolveServiceTypes($csRow);
- }
foreach ($types as $st) {
$code = isset($st['code']) ? (string)$st['code'] : '';
if ($code === $serviceType) {
@@ -728,9 +725,11 @@ class Home extends Base
$allowTypes = ['fixed', 'quote', 'range', 'custom'];
$type = in_array($priceType, $allowTypes, true) ? $priceType : ($service ? $service['price_type'] : 'fixed');
- // 支持多商品组合与增项计算
+ // 支持多商品组合与增项计算(后端结合 fa_service 与 fa_service_spec 权威核验单价)
$base = 0;
if (!empty($goodsList) && is_array($goodsList)) {
+ $serviceCache = [];
+ $specCache = [];
foreach ($goodsList as $g) {
if (is_string($g)) {
$rawG = htmlspecialchars_decode($g);
@@ -742,16 +741,88 @@ class Home extends Base
if (!is_array($g)) {
continue;
}
- $itemPrice = isset($g['base_price']) ? (float)$g['base_price'] : 0;
- if ($itemPrice <= 0 && !empty($g['service_id'])) {
- $itemPrice = (float)Db::name('service')->where('id', (int)$g['service_id'])->value('base_price');
+ $sid = isset($g['service_id']) ? (int)$g['service_id'] : 0;
+ $specId = isset($g['spec_id']) ? (int)$g['spec_id'] : 0;
+ $reqServiceType = isset($g['service_type']) ? trim((string)$g['service_type']) : '';
+ $isCustom = !empty($g['is_custom']) || (isset($g['cat_id']) && (int)$g['cat_id'] === 15);
+
+ $dbSvc = null;
+ if ($sid > 0 && !$isCustom) {
+ if (!array_key_exists($sid, $serviceCache)) {
+ $serviceCache[$sid] = Db::name('service')->where('id', $sid)->find() ?: null;
+ }
+ $dbSvc = $serviceCache[$sid];
}
+
+ $specPrice = 0.0;
+ if ($specId > 0 && !$isCustom) {
+ if (!array_key_exists($specId, $specCache)) {
+ $spQuery = Db::name('service_spec')->where('id', $specId);
+ if ($sid > 0) {
+ $spQuery->where('service_id', $sid);
+ }
+ $specCache[$specId] = $spQuery->find() ?: null;
+ }
+ if (!empty($specCache[$specId])) {
+ $specPrice = (float)$specCache[$specId]['base_price'];
+ }
+ }
+
+ $itemPrice = isset($g['base_price']) ? max(0, (float)$g['base_price']) : 0.0;
+ $dbAddonsMap = [];
+
+ if ($dbSvc) {
+ $svcTypes = !empty($dbSvc['service_types']) ? (json_decode((string)$dbSvc['service_types'], true) ?: []) : [];
+ $defaultCode = 'install';
+ $matchedTypePrice = 0.0;
+ foreach ($svcTypes as $st) {
+ if (!empty($st['is_default']) && !empty($st['code'])) {
+ $defaultCode = (string)$st['code'];
+ }
+ if ($reqServiceType !== '' && isset($st['code']) && (string)$st['code'] === $reqServiceType) {
+ $matchedTypePrice = isset($st['price']) ? (float)$st['price'] : 0.0;
+ }
+ }
+
+ // 方案2计价优先级:若规格设置了专属一口价(>0)且当前为默认主服务,则优先采用规格专属价;否则采用所选主服务定价或商品默认价
+ $isDefaultService = ($reqServiceType === '' || $reqServiceType === $defaultCode || $reqServiceType === 'install');
+ if ($specPrice > 0 && $isDefaultService) {
+ $itemPrice = $specPrice;
+ } elseif ($matchedTypePrice > 0) {
+ $itemPrice = $matchedTypePrice;
+ } elseif ($specPrice > 0) {
+ $itemPrice = $specPrice;
+ } elseif ((float)$dbSvc['base_price'] > 0) {
+ $itemPrice = (float)$dbSvc['base_price'];
+ }
+
+ $svcAddons = !empty($dbSvc['addons']) ? (json_decode((string)$dbSvc['addons'], true) ?: []) : [];
+ foreach ($svcAddons as $ad) {
+ if (!empty($ad['name']) && isset($ad['price']) && (float)$ad['price'] > 0) {
+ $dbAddonsMap[(string)$ad['name']] = (float)$ad['price'];
+ }
+ if (!empty($ad['code']) && isset($ad['price']) && (float)$ad['price'] > 0) {
+ $dbAddonsMap[(string)$ad['code']] = (float)$ad['price'];
+ }
+ }
+ }
+
$itemNum = isset($g['num']) ? max(1, (int)$g['num']) : 1;
$base += $itemPrice * $itemNum;
if (!empty($g['extras']) && is_array($g['extras'])) {
foreach ($g['extras'] as $ex) {
- $exPrice = isset($ex['price']) ? (float)$ex['price'] : 0;
$exNum = isset($ex['num']) ? max(0, (int)$ex['num']) : 0;
+ if ($exNum <= 0) {
+ continue;
+ }
+ $exPrice = isset($ex['price']) ? max(0, (float)$ex['price']) : 0.0;
+ $exName = isset($ex['name']) ? (string)$ex['name'] : '';
+ $exCode = isset($ex['code']) ? (string)$ex['code'] : '';
+ if ($exName !== '' && isset($dbAddonsMap[$exName])) {
+ $exPrice = $dbAddonsMap[$exName];
+ } elseif ($exCode !== '' && isset($dbAddonsMap[$exCode])) {
+ $exPrice = $dbAddonsMap[$exCode];
+ }
$base += $exPrice * $exNum;
}
}
@@ -842,9 +913,6 @@ class Home extends Base
foreach ($rows as $r) {
$theme = isset($themeText[$r['theme']]) ? $r['theme'] : 'promo';
$stats = $this->parseBannerStats(isset($r['stats']) ? $r['stats'] : '');
- if ($theme === 'brand' && empty($stats)) {
- $stats = $this->defaultBrandStats();
- }
$actionText = trim((string)$r['action_text']);
if ($actionText === '' && $theme !== 'brand') {
$actionText = '立即查看';
@@ -892,47 +960,6 @@ class Home extends Base
return $out;
}
- /**
- * 平台实力默认指标
- */
- protected function defaultBrandStats()
- {
- return [
- ['value' => '460万+', 'label' => '专业师傅'],
- ['value' => '99.9%', 'label' => '区域覆盖到村镇'],
- ['value' => '1762万', 'label' => '成功订单'],
- ];
- }
-
- /**
- * 无后台数据时的兜底轮播
- */
- protected function defaultBanners()
- {
- return [
- [
- 'id' => 0, 'badge' => '家装季', 'title' => '双11 狂欢大促',
- 'subtitle' => '下单立减 · 满额再返券', 'date_text' => '10.23 - 11.15',
- 'theme' => 'promo', 'theme_text' => '活动促销', 'action_text' => '立即抢购',
- 'stats' => [], 'image' => '',
- 'link' => '/pages/service/list?cat_id=1&cat_name=%E5%AE%B6%E5%85%B7',
- ],
- [
- 'id' => -1, 'badge' => '平台实力', 'title' => '平台实力',
- 'subtitle' => '已有 176276994 个订单找到师傅', 'date_text' => '下单越多 · 越优惠',
- 'theme' => 'brand', 'theme_text' => '平台实力', 'action_text' => '',
- 'stats' => $this->defaultBrandStats(), 'image' => '', 'link' => '',
- ],
- [
- 'id' => -2, 'badge' => '服务主题', 'title' => '家电清洗季',
- 'subtitle' => '空调 / 洗衣机 / 油烟机深度清洗', 'date_text' => '9.9 折起',
- 'theme' => 'service', 'theme_text' => '服务主题', 'action_text' => '立即预约',
- 'stats' => [], 'image' => '',
- 'link' => '/pages/service/list?cat_id=10&cat_name=%E5%AE%B6%E7%94%B5',
- ],
- ];
- }
-
/**
* 读取 fa_config 中的逗号分隔列表
*/
@@ -1054,44 +1081,42 @@ class Home extends Base
$serviceTypes = [];
if (!empty($row['service_types'])) {
- $serviceTypes = is_array($row['service_types']) ? $row['service_types'] : json_decode($row['service_types'], true);
+ $serviceTypes = is_array($row['service_types']) ? $row['service_types'] : (json_decode($row['service_types'], true) ?: []);
}
- if (empty($serviceTypes)) {
- $serviceTypes = $this->resolveServiceTypes($row);
- }
- $FixedCodes = ['install', 'repair', 'remove'];
+ $isOtherCat = ((int)$row['cat_id'] === 15);
if (!empty($activeTypeCodes)) {
$filteredTypes = [];
foreach ($serviceTypes as $st) {
$c = isset($st['code']) ? $st['code'] : '';
if ($c !== '' && isset($activeTypeCodes[$c])) {
- if (!isset($st['support_fixed'])) {
- $st['support_fixed'] = in_array($c, $FixedCodes, true) ? 1 : 0;
+ $dictSupport = isset($activeTypeCodes[$c]['support_fixed']) ? (int)$activeTypeCodes[$c]['support_fixed'] : 1;
+ $stPrice = isset($st['price']) ? (float)$st['price'] : 0.0;
+ if ($isOtherCat || $dictSupport === 0) {
+ $st['support_fixed'] = 0;
+ $st['price'] = 0;
+ } else {
+ $st['support_fixed'] = $stPrice > 0 ? 1 : 0;
}
$filteredTypes[] = $st;
}
}
- if (!empty($filteredTypes)) {
- $serviceTypes = $filteredTypes;
- }
+ $serviceTypes = $filteredTypes;
} else {
foreach ($serviceTypes as &$stItem) {
- $c = isset($stItem['code']) ? $stItem['code'] : '';
+ $stPrice = isset($stItem['price']) ? (float)$stItem['price'] : 0.0;
if (!isset($stItem['support_fixed'])) {
- $stItem['support_fixed'] = in_array($c, $FixedCodes, true) ? 1 : 0;
+ $stItem['support_fixed'] = (!$isOtherCat && $stPrice > 0) ? 1 : 0;
}
}
unset($stItem);
}
- // 解析增项服务(优先读取商品绑定的 addons JSON,并与统一服务表 fa_service_type 联动过滤)
+ // 解析增项服务(读取商品绑定的 addons JSON,并与统一服务表 fa_service_type 联动过滤)
$addons = [];
if (!empty($row['addons'])) {
- $addons = is_array($row['addons']) ? $row['addons'] : json_decode($row['addons'], true);
+ $addons = is_array($row['addons']) ? $row['addons'] : (json_decode($row['addons'], true) ?: []);
}
- if (empty($addons)) {
- $addons = $this->resolveServiceAddons($row);
- } elseif (!empty($activeTypeCodes)) {
+ if (!empty($addons) && !empty($activeTypeCodes)) {
$addons = array_values(array_filter($addons, function ($ad) use ($activeTypeCodes, $activeTypeNames) {
$code = isset($ad['code']) ? $ad['code'] : '';
$name = isset($ad['name']) ? $ad['name'] : '';
@@ -1126,450 +1151,6 @@ class Home extends Base
];
}
- /**
- * 根据具体商品名称与所属类目返回精准匹配的服务项目列表(1:1 对标奇兵到家图2 & 图3)
- */
- protected function resolveServiceTypes($row)
- {
- $catId = (int)$row['cat_id'];
- $name = (string)$row['name'];
- $bp = max(30, (float)$row['base_price']);
-
- $make = function ($items) use ($bp) {
- $res = [];
- $fixedCodes = ['install', 'repair', 'remove'];
- foreach ($items as $idx => $it) {
- $ratio = isset($it[2]) ? (float)$it[2] : 1.0;
- $fixed = isset($it[3]) && $it[3] !== null ? (float)$it[3] : round($bp * $ratio, 2);
- $supportFixed = isset($it[4]) ? (int)$it[4] : (in_array($it[0], $fixedCodes, true) ? 1 : 0);
- $res[] = [
- 'code' => $it[0],
- 'name' => $it[1],
- 'price' => $fixed,
- 'is_default' => $idx === 0 ? 1 : 0,
- 'support_fixed' => $supportFixed,
- ];
- }
- return $res;
- };
-
- // 类目 15:其它(1:1 复刻图3)
- if ($catId === 15) {
- return $make([
- ['install', '安装', 1.0, null, 0],
- ['repair', '维修', 0.8, null, 0],
- ['grout', '美缝', 1.0, null, 0],
- ['glue', '打胶', 0.6, null, 0],
- ['remove', '拆旧', 0.5, null, 0],
- ['deliver', '送货到家', 0.7, 50, 0],
- ['deliver_install', '送货到家并安装', 1.3, null, 0],
- ['deliver_downstairs', '送货到楼下(不含上楼)', 0.5, 35, 0],
- ['clean', '保洁', 1.0, null, 0],
- ['drill', '打孔', 0.5, 30, 0],
- ['film', '贴膜', 0.8, null, 0],
- ['move', '移机', 1.1, null, 0],
- ['other', '其他', 1.0, null, 0],
- ]);
- }
-
- // 类目 6:墙纸/壁画(1:1 复刻图2)
- if ($catId === 6) {
- if (strpos($name, '画') !== false && strpos($name, '壁画') === false) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 1.15],
- ['remove', '拆旧', 0.5, 30],
- ['drill', '打孔', 0.6, 30, 0],
- ['deliver_install', '送货到家并安装', 1.3, null, 0],
- ]);
- }
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 1.15],
- ['deliver', '送货到家', 0.7, 50, 0],
- ['deliver_install', '送货到家并安装', 1.3, null, 0],
- ['deliver_downstairs', '送货到楼下(不含上楼)', 0.5, 35, 0],
- ]);
- }
-
- // 类目 1:家具(1:1 复刻视频 00:02 的 11 项服务项目,且安装/维修/拆旧支持一口价,其余自动转报价)
- if ($catId === 1) {
- $repairPrice = round($bp + 10, 2);
- return $make([
- ['install', '安装', 1.0, $bp, 1],
- ['repair', '维修', 1.18, $repairPrice, 1],
- ['remove', '拆旧', 0.9, 50, 1],
- ['paint_repair', '补漆修复', 1.0, 80, 0],
- ['measure', '测量', 0.8, 50, 0],
- ['deliver', '送货到家', 0.7, 60, 0],
- ['deliver_install', '送货到家并安装', 1.35, round($bp * 1.35, 2), 0],
- ['deliver_downstairs', '送货到楼下(不含上楼)', 0.5, 40, 0],
- ['return_goods', '返货', 0.8, 50, 0],
- ['maintain', '保养', 0.8, 60, 0],
- ['open_hole', '开孔', 0.5, 30, 0],
- ]);
- }
-
- // 类目 2:卫浴(区分马桶、花洒龙头、浴室柜/浴缸、浴霸)
- if ($catId === 2) {
- if (preg_match('/(马桶|便器)/u', $name)) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['remove', '拆旧', 0.5],
- ['glue', '打胶', 0.4, 30],
- ['grout', '美缝', 0.6, 50],
- ['deliver_install', '送货到家并安装', 1.3],
- ]);
- }
- if (preg_match('/(花洒|龙头|扶手|镜)/u', $name)) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['remove', '拆旧', 0.4],
- ['drill', '打孔', 0.4, 25],
- ]);
- }
- if (preg_match('/(浴霸|水泵)/u', $name)) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.75],
- ['remove', '拆旧', 0.45],
- ['deliver_install', '送货到家并安装', 1.25],
- ]);
- }
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['remove', '拆旧', 0.5],
- ['glue', '打胶', 0.4, 30],
- ['deliver', '送货到家', 0.6, 50],
- ['deliver_install', '送货到家并安装', 1.3],
- ]);
- }
-
- // 类目 3:灯具
- if ($catId === 3) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['remove', '拆旧', 0.4],
- ['deliver_install', '送货到家并安装', 1.25],
- ]);
- }
-
- // 类目 4:锁具
- if ($catId === 4) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['remove', '拆旧', 0.4],
- ['drill', '打孔', 0.5, 40],
- ]);
- }
-
- // 类目 5:窗帘
- if ($catId === 5) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['measure', '测量', 0.5, 40],
- ['remove', '拆旧', 0.4],
- ['deliver_install', '送货到家并安装', 1.25],
- ]);
- }
-
- // 类目 7:净水器
- if ($catId === 7) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['filter', '换滤芯', 0.5, 40],
- ['remove', '拆旧', 0.4],
- ['move', '移机', 1.1],
- ]);
- }
-
- // 类目 8:晾衣架(区分电动、手摇、户外推拉/落地)
- if ($catId === 8) {
- if (strpos($name, '电动') !== false) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['remove', '拆旧', 0.4],
- ['deliver', '送货到家', 0.6, 45],
- ['deliver_install', '送货到家并安装', 1.25],
- ['move', '移机', 1.1],
- ]);
- }
- if (strpos($name, '户外') !== false || strpos($name, '推拉') !== false) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['remove', '拆旧', 0.4],
- ['drill', '打孔', 0.5, 30],
- ]);
- }
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['remove', '拆旧', 0.4],
- ['deliver', '送货到家', 0.6, 45],
- ['deliver_install', '送货到家并安装', 1.25],
- ]);
- }
-
- // 类目 9:门窗
- if ($catId === 9) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['measure', '测量', 0.4, 50],
- ['remove', '拆旧', 0.5],
- ['glue', '打胶', 0.4, 40],
- ['deliver_install', '送货到家并安装', 1.3],
- ]);
- }
-
- // 类目 10:家电(区分空调、厨电、洗衣机冰箱)
- if ($catId === 10) {
- if (strpos($name, '空调') !== false) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['move', '移机', 1.2],
- ['remove', '拆旧', 0.5],
- ['clean', '保洁清洗', 0.6, 80],
- ['deliver_install', '送货到家并安装', 1.3],
- ]);
- }
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['remove', '拆旧', 0.45],
- ['clean', '保洁清洗', 0.6, 70],
- ['drill', '打孔', 0.4, 40],
- ['deliver_install', '送货到家并安装', 1.25],
- ]);
- }
-
- // 类目 11:电视/投影
- if ($catId === 11) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['move', '移机', 1.0],
- ['remove', '拆旧', 0.4],
- ['deliver_install', '送货到家并安装', 1.25],
- ]);
- }
-
- // 类目 12:淋浴房
- if ($catId === 12) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['measure', '测量', 0.3, 50],
- ['remove', '拆旧', 0.45],
- ['glue', '打胶', 0.3, 40],
- ['film', '贴膜', 0.4, 80],
- ['deliver_install', '送货到家并安装', 1.3],
- ]);
- }
-
- // 类目 13:商用厨电
- if ($catId === 13) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['clean', '保洁', 0.8],
- ['remove', '拆旧', 0.5],
- ['deliver_install', '送货到家并安装', 1.25],
- ]);
- }
-
- // 类目 14:健身器材
- if ($catId === 14) {
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['move', '移机', 1.1],
- ['remove', '拆旧', 0.5],
- ['deliver', '送货到家', 0.6, 60],
- ['deliver_install', '送货到家并安装', 1.3],
- ]);
- }
-
- return $make([
- ['install', '安装', 1.0],
- ['repair', '维修', 0.7],
- ['remove', '拆旧', 0.5],
- ['deliver', '送货到家', 0.6, 50],
- ['deliver_install', '送货到家并安装', 1.3],
- ]);
- }
-
- /**
- * 根据具体商品名称与所属类目返回极简增值服务(增项)列表(1:1 对标奇兵到家:仅保留“拆旧”、“切割”等 1~2 项极简短词)
- */
- protected function resolveServiceAddons($row)
- {
- $catId = (int)$row['cat_id'];
- $name = (string)$row['name'];
-
- $codeMap = [
- '拆旧' => 'remove',
- '切割' => 'cut',
- '打孔' => 'drill',
- '开孔' => 'open_hole',
- ];
- $build = function ($items) use ($codeMap) {
- $res = [];
- foreach ($items as $idx => $it) {
- $res[] = [
- 'id' => $idx + 1,
- 'code' => isset($codeMap[$it[0]]) ? $codeMap[$it[0]] : ('addon_' . ($idx + 1)),
- 'name' => $it[0],
- 'unit' => $it[1],
- 'price' => (float)$it[2],
- ];
- }
- return $res;
- };
-
- // 类目 1:家具(对标视频 00:06 / 00:17:电脑桌仅“拆旧 50元/份”,柜类多一项“切割 20元/份”)
- if ($catId === 1) {
- if (preg_match('/(柜)/u', $name)) {
- return $build([
- ['拆旧', '份', 50],
- ['切割', '份', 20],
- ]);
- }
- return $build([
- ['拆旧', '份', 50],
- ]);
- }
-
- // 类目 2:卫浴(对标奇兵到家截图 frame_010:洗衣柜/浴室柜为“拆旧 50元/份”、“切割 20元/份”,马桶/花洒为“拆旧”)
- if ($catId === 2) {
- if (preg_match('/(浴室柜|洗衣柜|面盆|洗面盆|水槽|拖把池)/u', $name)) {
- return $build([
- ['拆旧', '份', 50],
- ['切割', '份', 20],
- ]);
- }
- if (preg_match('/(马桶|便器)/u', $name)) {
- return $build([
- ['拆旧', '份', 50],
- ]);
- }
- if (preg_match('/(浴缸)/u', $name)) {
- return $build([
- ['拆旧', '份', 80],
- ]);
- }
- return $build([
- ['拆旧', '份', 30],
- ]);
- }
-
- // 类目 3:灯具
- if ($catId === 3) {
- return $build([
- ['拆旧', '份', 20],
- ]);
- }
-
- // 类目 4:锁具
- if ($catId === 4) {
- return $build([
- ['拆旧', '份', 30],
- ['开孔', '份', 30],
- ]);
- }
-
- // 类目 5:窗帘
- if ($catId === 5) {
- return $build([
- ['拆旧', '份', 30],
- ]);
- }
-
- // 类目 6:墙纸/壁画
- if ($catId === 6) {
- return $build([
- ['拆旧', '份', 30],
- ]);
- }
-
- // 类目 7:净水器
- if ($catId === 7) {
- return $build([
- ['拆旧', '份', 30],
- ['打孔', '份', 30],
- ]);
- }
-
- // 类目 8:晾衣架
- if ($catId === 8) {
- return $build([
- ['拆旧', '份', 30],
- ]);
- }
-
- // 类目 9:门窗
- if ($catId === 9) {
- return $build([
- ['拆旧', '份', 50],
- ]);
- }
-
- // 类目 10:家电
- if ($catId === 10) {
- if (strpos($name, '空调') !== false) {
- return $build([
- ['拆旧', '份', 80],
- ['打孔', '份', 50],
- ]);
- }
- return $build([
- ['拆旧', '份', 40],
- ]);
- }
-
- // 类目 11:电视/投影
- if ($catId === 11) {
- return $build([
- ['拆旧', '份', 30],
- ]);
- }
-
- // 类目 12:淋浴房
- if ($catId === 12) {
- return $build([
- ['拆旧', '份', 80],
- ]);
- }
-
- // 类目 13:商用厨电
- if ($catId === 13) {
- return $build([
- ['拆旧', '份', 80],
- ]);
- }
-
- // 类目 14:健身器材
- if ($catId === 14) {
- return $build([
- ['拆旧', '份', 60],
- ]);
- }
-
- return $build([
- ['拆旧', '份', 30],
- ]);
- }
-
/**
* 相对时间计算(更具真实动态感)
*/
diff --git a/server/application/extra/site.php b/server/application/extra/site.php
index 7201395..10f41b8 100755
--- a/server/application/extra/site.php
+++ b/server/application/extra/site.php
@@ -4,7 +4,7 @@ return array (
'name' => '优匠优服',
'beian' => '',
'cdnurl' => '',
- 'version' => '1.0.43',
+ 'version' => '1.0.44',
'timezone' => 'Asia/Shanghai',
'forbiddenip' => '',
'languages' =>
diff --git a/server/public/assets/js/backend/addon.js b/server/public/assets/js/backend/addon.js
new file mode 100644
index 0000000..defe5b5
--- /dev/null
+++ b/server/public/assets/js/backend/addon.js
@@ -0,0 +1,847 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template', 'cookie'], function ($, undefined, Backend, Table, Form, Template, undefined) {
+ $.cookie.prototype.defaults = {path: Config.moduleurl};
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: Config.api_url ? Config.api_url + '/addon/index' : "addon/downloaded",
+ add_url: '',
+ edit_url: '',
+ del_url: '',
+ multi_url: ''
+ }
+ });
+
+ var table = $("#table");
+
+ // 弹窗自适应宽高
+ var area = Fast.config.openArea != undefined ? Fast.config.openArea : [$(window).width() > 800 ? '800px' : '95%', $(window).height() > 600 ? '600px' : '95%'];
+
+ var switch_local = function () {
+ if ($(".btn-switch.active").data("type") != "local") {
+ Layer.confirm(__('Store not available tips'), {
+ title: __('Warmtips'),
+ btn: [__('Switch to the local'), __('Try to reload')]
+ }, function (index) {
+ layer.close(index);
+ $(".panel .nav-tabs").hide();
+ $(".toolbar > *:not(:first)").hide();
+ $(".btn-switch[data-type='local']").trigger("click");
+ }, function (index) {
+ layer.close(index);
+ table.bootstrapTable('refresh');
+ });
+ return false;
+ }
+ };
+ table.on('load-success.bs.table', function (e, json) {
+ if (json && typeof json.category != 'undefined' && $(".nav-category li").length == 2) {
+ $.each(json.category, function (i, j) {
+ $("
+
+
-
+
+
+
+
+
+
+ ' + __('The following data tables will be deleted') + ': '
+ : '' + tables.join(" ") + ' ' + __('The Addon did not create a data table') + ' ';
+ $(html).insertAfter($(this).closest("p"));
+ } else {
+ $(".droptablestips").remove();
+ }
+ $(window).resize();
+ });
+
+ // 会员信息
+ $(document).on("click", ".btn-userinfo", function (e, name, version) {
+ var that = this;
+ var area = [$(window).width() > 800 ? '500px' : '95%', $(window).height() > 600 ? '400px' : '95%'];
+ var userinfo = Controller.api.userinfo.get();
+ if (!userinfo) {
+ Fast.api.ajax({
+ url: Config.api_url + '/user/logintpl',
+ type: 'post',
+ loading: false,
+ data: {
+ version: Config.faversion,
+ sid: Controller.api.sid()
+ }
+ }, function (tpldata, ret) {
+ Layer.open({
+ content: Template.render(tpldata, {}),
+ zIndex: 99,
+ area: area,
+ title: __('Login'),
+ resize: false,
+ btn: [__('Login')],
+ yes: function (index, layero) {
+ var data = $("form", layero).serializeArray();
+ data.push({name: "faversion", value: Config.faversion});
+ data.push({name: "sid", value: Controller.api.sid()});
+ Fast.api.ajax({
+ url: Config.api_url + '/user/login',
+ type: 'post',
+ data: data
+ }, function (data, ret) {
+ Controller.api.userinfo.set(data);
+ Layer.closeAll();
+ Layer.alert(ret.msg, {title: __('Warning'), icon: 1});
+ return false;
+ }, function (data, ret) {
+ });
+ },
+ success: function (layero, index) {
+ this.checkEnterKey = function (event) {
+ if (event.keyCode === 13) {
+ $(".layui-layer-btn0").trigger("click");
+ return false;
+ }
+ };
+ $(document).on('keydown', this.checkEnterKey);
+ },
+ end: function () {
+ $(document).off('keydown', this.checkEnterKey);
+ }
+ });
+ return false;
+ });
+ } else {
+ Fast.api.ajax({
+ url: Config.api_url + '/user/userinfotpl',
+ type: 'post',
+ data: {
+ uid: userinfo.id,
+ token: userinfo.token,
+ version: Config.faversion,
+ sid: Controller.api.sid()
+ }
+ }, function (tpldata, ret) {
+ Layer.open({
+ content: Template.render(tpldata, userinfo),
+ area: area,
+ title: __('Userinfo'),
+ resize: false,
+ btn: [__('Logout'), __('Close')],
+ yes: function () {
+ Fast.api.ajax({
+ url: Config.api_url + '/user/logout',
+ data: {
+ uid: userinfo.id,
+ token: userinfo.token,
+ version: Config.faversion,
+ sid: Controller.api.sid()
+ }
+ }, function (data, ret) {
+ Controller.api.userinfo.set(null);
+ Layer.closeAll();
+ Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
+ }, function (data, ret) {
+ Controller.api.userinfo.set(null);
+ Layer.closeAll();
+ Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
+ });
+ }
+ });
+ return false;
+ }, function (data) {
+ Controller.api.userinfo.set(null);
+ $(that).trigger('click');
+ return false;
+ });
+ }
+ });
+
+ //刷新授权
+ $(document).on("click", ".btn-authorization", function () {
+ var userinfo = Controller.api.userinfo.get();
+ if (!userinfo) {
+ $(".btn-userinfo").trigger("click");
+ return false;
+ }
+ Layer.confirm(__('Are you sure you want to refresh authorization?'), {icon: 3, title: __('Warmtips')}, function () {
+ Fast.api.ajax({
+ url: 'addon/authorization',
+ data: {
+ uid: userinfo.id,
+ token: userinfo.token
+ }
+ }, function (data, ret) {
+ $(".btn-refresh").trigger("click");
+ Layer.closeAll();
+ });
+ });
+ return false;
+ });
+
+ var install = function (name, version, force) {
+ var userinfo = Controller.api.userinfo.get();
+ var uid = userinfo ? userinfo.id : 0;
+ var token = userinfo ? userinfo.token : '';
+ Fast.api.ajax({
+ url: 'addon/install',
+ data: {
+ name: name,
+ force: force ? 1 : 0,
+ uid: uid,
+ token: token,
+ version: version,
+ faversion: Config.faversion
+ }
+ }, function (data, ret) {
+ Layer.closeAll();
+ Config['addons'][data.addon.name] = ret.data.addon;
+ operate(data.addon.name, 'enable', false, function () {
+ Layer.alert(__('Online installed tips') + (data.addon.testdata ? __('Testdata tips') : ""), {
+ btn: data.addon.testdata ? [__('Import testdata'), __('Skip testdata')] : [__('OK')],
+ title: __('Warning'),
+ yes: function (index) {
+ if (data.addon.testdata) {
+ Fast.api.ajax({
+ url: 'addon/testdata',
+ data: {
+ name: name,
+ uid: uid,
+ token: token,
+ version: version,
+ faversion: Config.faversion
+ }
+ }, function (data, ret) {
+ Layer.close(index);
+ });
+ } else {
+ Layer.close(index);
+ }
+ },
+ icon: 1
+ });
+ Controller.api.refresh(table, name);
+ });
+ }, function (data, ret) {
+ var area = Fast.config.openArea != undefined ? Fast.config.openArea : [$(window).width() > 650 ? '650px' : '95%', $(window).height() > 710 ? '710px' : '95%'];
+ if (ret && ret.code === -2) {
+ //如果登录已经超时,重新提醒登录
+ if (uid && uid != ret.data.uid) {
+ Controller.api.userinfo.set(null);
+ $(".operate[data-name='" + name + "'] .btn-install:first").trigger("click");
+ return;
+ }
+ top.Fast.api.open(ret.data.payurl, __('Pay now'), {
+ area: area,
+ end: function () {
+ Fast.api.ajax({
+ url: 'addon/isbuy',
+ data: {
+ name: name,
+ force: force ? 1 : 0,
+ uid: uid,
+ token: token,
+ version: version,
+ faversion: Config.faversion
+ }
+ }, function () {
+ top.Layer.alert(__('Pay successful tips'), {
+ btn: [__('Continue installation')],
+ title: __('Warning'),
+ icon: 1,
+ yes: function (index) {
+ top.Layer.close(index);
+ install(name, version);
+ }
+ });
+ return false;
+ }, function () {
+ console.log(__('Canceled'));
+ return false;
+ });
+ }
+ });
+ } else if (ret && ret.code === -3) {
+ //插件目录发现影响全局的文件
+ Layer.open({
+ content: Template("conflicttpl", ret.data),
+ shade: 0.8,
+ area: area,
+ title: __('Warning'),
+ btn: [__('Continue install'), __('Cancel')],
+ end: function () {
+
+ },
+ yes: function () {
+ install(name, version, true);
+ }
+ });
+
+ } else {
+ Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
+ }
+ return false;
+ });
+ };
+
+ var uninstall = function (name, force, droptables) {
+ Fast.api.ajax({
+ url: 'addon/uninstall',
+ data: {name: name, force: force ? 1 : 0, droptables: droptables ? 1 : 0}
+ }, function (data, ret) {
+ delete Config['addons'][name];
+ Layer.closeAll();
+ Controller.api.refresh(table, name);
+ }, function (data, ret) {
+ if (ret && ret.code === -3) {
+ //插件目录发现影响全局的文件
+ Layer.open({
+ content: Template("conflicttpl", ret.data),
+ shade: 0.8,
+ area: area,
+ title: __('Warning'),
+ btn: [__('Continue uninstall'), __('Cancel')],
+ end: function () {
+
+ },
+ yes: function () {
+ uninstall(name, true, droptables);
+ }
+ });
+
+ } else {
+ Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
+ }
+ return false;
+ });
+ };
+
+ var operate = function (name, action, force, success) {
+ Fast.api.ajax({
+ url: 'addon/state',
+ data: {name: name, action: action, force: force ? 1 : 0}
+ }, function (data, ret) {
+ var addon = Config['addons'][name];
+ addon.state = action === 'enable' ? 1 : 0;
+ Layer.closeAll();
+ if (typeof success === 'function') {
+ success(data, ret);
+ }
+ Controller.api.refresh(table, name);
+ }, function (data, ret) {
+ if (ret && ret.code === -3) {
+ //插件目录发现影响全局的文件
+ Layer.open({
+ content: Template("conflicttpl", ret.data),
+ shade: 0.8,
+ area: area,
+ title: __('Warning'),
+ btn: [__('Continue operate'), __('Cancel')],
+ end: function () {
+
+ },
+ yes: function () {
+ operate(name, action, true, success);
+ }
+ });
+
+ } else {
+ Layer.alert(ret.msg, {title: __('Warning'), icon: 0});
+ }
+ return false;
+ });
+ };
+
+ var upgrade = function (name, version) {
+ var userinfo = Controller.api.userinfo.get();
+ var uid = userinfo ? userinfo.id : 0;
+ var token = userinfo ? userinfo.token : '';
+ Fast.api.ajax({
+ url: 'addon/upgrade',
+ data: {name: name, uid: uid, token: token, version: version, faversion: Config.faversion}
+ }, function (data, ret) {
+ Config['addons'][name] = data.addon;
+ Layer.closeAll();
+ Controller.api.refresh(table, name);
+ }, function (data, ret) {
+ Layer.alert(ret.msg, {title: __('Warning')});
+ return false;
+ });
+ };
+
+ // 点击安装
+ $(document).on("click", ".btn-install", function () {
+ var that = this;
+ var name = $(this).closest(".operate").data("name");
+ var version = $(this).data("version");
+
+ var userinfo = Controller.api.userinfo.get();
+ var uid = userinfo ? userinfo.id : 0;
+
+ if (parseInt(uid) === 0) {
+ $(".btn-userinfo").trigger("click", name, version);
+ return false;
+ }
+ install(name, version, false);
+ });
+
+ // 点击卸载
+ $(document).on("click", ".btn-uninstall", function () {
+ var name = $(this).closest(".operate").data('name');
+ if (Config['addons'][name].state == 1) {
+ Layer.alert(__('Please disable the add before trying to uninstall'), {icon: 7});
+ return false;
+ }
+ Template.helper("__", __);
+ tables = [];
+ Layer.confirm(Template("uninstalltpl", {addon: Config['addons'][name]}), {focusBtn: false, title: __("Warning")}, function (index, layero) {
+ uninstall(name, false, $("input[name='droptables']", layero).prop("checked"));
+ });
+ });
+
+ // 点击配置
+ $(document).on("click", ".btn-config", function () {
+ var name = $(this).closest(".operate").data("name");
+ Fast.api.open("addon/config?name=" + name, __('Setting'));
+ });
+
+ // 点击启用/禁用
+ $(document).on("click", ".btn-enable,.btn-disable", function () {
+ var name = $(this).data("name");
+ var action = $(this).data("action");
+ operate(name, action, false);
+ });
+
+ // 点击升级
+ $(document).on("click", ".btn-upgrade", function () {
+ var name = $(this).closest(".operate").data('name');
+ if (Config['addons'][name].state == 1) {
+ Layer.alert(__('Please disable the add before trying to upgrade'), {icon: 7});
+ return false;
+ }
+ var version = $(this).data("version");
+
+ Layer.confirm(__('Upgrade tips', Config['addons'][name].title), {title: __('Warmtips')}, function (index, layero) {
+ upgrade(name, version);
+ });
+ });
+
+ $(document).on("click", ".operate .btn-group .dropdown-toggle", function () {
+ $(this).closest(".btn-group").toggleClass("dropup", $(document).height() - $(this).offset().top <= 200);
+ });
+
+ $(document).on("click", ".view-screenshots", function () {
+ var row = Table.api.getrowbyindex(table, parseInt($(this).data("index")));
+ var data = [];
+ $.each(row.screenshots, function (i, j) {
+ data.push({
+ "src": j
+ });
+ });
+ var json = {
+ "title": row.title,
+ "data": data
+ };
+ top.Layer.photos(top.JSON.parse(JSON.stringify({photos: json})));
+ });
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ config: function () {
+ $(document).on("click", ".nav-group li a[data-toggle='tab']", function () {
+ if ($(this).attr("href") == "#all") {
+ $(".tab-pane").addClass("active in");
+ }
+ return;
+ var type = $(this).attr("href").substring(1);
+ if (type == 'all') {
+ $(".table-config tr").show();
+ } else {
+ $(".table-config tr").hide();
+ $(".table-config tr[data-group='" + type + "']").show();
+ }
+ });
+
+ Controller.api.bindevent();
+ },
+ api: {
+ formatter: {
+ title: function (value, row, index) {
+ if ($(".btn-switch.active").data("type") == "local") {
+ // return value;
+ }
+ var title = '' + value + '';
+ if (row.screenshots && row.screenshots.length > 0) {
+ title += ' ';
+ }
+ return title;
+ },
+ intro: function (value, row, index) {
+ return row.intro + (row.extend ? "" + Fast.api.escape(row.extend[0]) + "" : "");
+ },
+ operate: function (value, row, index) {
+ return Template("operatetpl", {item: row, index: index});
+ },
+ toggle: function (value, row, index) {
+ if (!row.addon) {
+ return '';
+ }
+ return '';
+ },
+ author: function (value, row, index) {
+ var url = 'javascript:';
+ if (typeof row.homepage !== 'undefined') {
+ url = row.homepage;
+ } else if (typeof row.qq !== 'undefined' && row.qq) {
+ url = 'https://wpa.qq.com/msgrd?v=3&uin=' + row.qq + '&site=&menu=yes';
+ }
+ return '' + value + '';
+ },
+ price: function (value, row, index) {
+ if (isNaN(value)) {
+ return value;
+ }
+ return parseFloat(value) == 0 ? '' + __('Free') + '' : '¥' + value + '';
+ },
+ downloads: function (value, row, index) {
+ return value;
+ },
+ version: function (value, row, index) {
+ return row.addon && row.addon.version != row.version ? '' + row.addon.version + '' : row.version;
+ },
+ home: function (value, row, index) {
+ return row.addon && parseInt(row.addon.state) > 0 ? '' : '';
+ },
+ },
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ },
+ userinfo: {
+ get: function () {
+ if (typeof $.cookie !== 'undefined') {
+ var userinfo = $.cookie('fastadmin_userinfo');
+ } else {
+ var userinfo = sessionStorage.getItem("fastadmin_userinfo");
+ }
+ return userinfo ? JSON.parse(userinfo) : null;
+ },
+ set: function (data) {
+ if (typeof $.cookie !== 'undefined') {
+ if (data) {
+ $.cookie("fastadmin_userinfo", JSON.stringify(data));
+ } else {
+ $.removeCookie("fastadmin_userinfo");
+ }
+ } else {
+ if (data) {
+ sessionStorage.setItem("fastadmin_userinfo", JSON.stringify(data));
+ } else {
+ sessionStorage.removeItem("fastadmin_userinfo");
+ }
+ }
+ }
+ },
+ sid: function () {
+ var sid = $.cookie('fastadmin_sid');
+ if (!sid) {
+ sid = Math.random().toString(20).substr(2, 12);
+ $.cookie('fastadmin_sid', sid);
+ }
+ return sid;
+ },
+ refresh: function (table, name) {
+ //刷新左侧边栏
+ Fast.api.refreshmenu();
+
+ //刷新行数据
+ if ($(".operate[data-name='" + name + "']").length > 0) {
+ var tr = $(".operate[data-name='" + name + "']").closest("tr[data-index]");
+ var index = tr.data("index");
+ var row = Table.api.getrowbyindex(table, index);
+ row.addon = typeof Config['addons'][name] !== 'undefined' ? Config['addons'][name] : undefined;
+ table.bootstrapTable("updateRow", {index: index, row: row});
+ } else if ($(".btn-switch.active").data("type") == "local") {
+ $(".btn-refresh").trigger("click");
+ }
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/appeal.js b/server/public/assets/js/backend/appeal.js
new file mode 100644
index 0000000..7bd91f2
--- /dev/null
+++ b/server/public/assets/js/backend/appeal.js
@@ -0,0 +1,59 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'appeal/index' + location.search,
+ add_url: 'appeal/add',
+ edit_url: 'appeal/edit',
+ del_url: 'appeal/del',
+ multi_url: 'appeal/multi',
+ import_url: 'appeal/import',
+ table: 'appeal',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'id',
+ fixedColumns: true,
+ fixedRightNumber: 1,
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'order.order_no', title: __('关联订单号'), operate: 'LIKE'},
+ {field: 'master.realname', title: __('申诉师傅'), operate: 'LIKE'},
+ {field: 'type', title: __('Type'), searchList: {"misjudge":__('Misjudge'),"review":__('Review'),"deduct":__('Deduct'),"other":__('Other')}, formatter: Table.api.formatter.normal},
+ {field: 'content', title: __('Content'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+ {field: 'status', title: __('Status'), searchList: {"wait":__('Wait'),"handling":__('Handling'),"done":__('Done'),"reject":__('Reject')}, formatter: Table.api.formatter.status},
+ {field: 'reply', title: __('Reply'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+ {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/auth/admin.js b/server/public/assets/js/backend/auth/admin.js
new file mode 100644
index 0000000..84a7e1b
--- /dev/null
+++ b/server/public/assets/js/backend/auth/admin.js
@@ -0,0 +1,62 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'auth/admin/index',
+ add_url: 'auth/admin/add',
+ edit_url: 'auth/admin/edit',
+ del_url: 'auth/admin/del',
+ multi_url: 'auth/admin/multi',
+ }
+ });
+
+ var table = $("#table");
+
+ //在表格内容渲染完成后回调的事件
+ table.on('post-body.bs.table', function (e, json) {
+ $("tbody tr[data-index]", this).each(function () {
+ if (parseInt($("td:eq(1)", this).text()) == Config.admin.id) {
+ $("input[type=checkbox]", this).prop("disabled", true);
+ }
+ });
+ });
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ columns: [
+ [
+ {field: 'state', checkbox: true, },
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'username', title: __('Username')},
+ {field: 'nickname', title: __('Nickname')},
+ {field: 'groups_text', title: __('Group'), operate:false, formatter: Table.api.formatter.label},
+ {field: 'email', title: __('Email')},
+ {field: 'mobile', title: __('Mobile')},
+ {field: 'status', title: __("Status"), searchList: {"normal":__('Normal'),"hidden":__('Hidden')}, formatter: Table.api.formatter.status},
+ {field: 'logintime', title: __('Login time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: function (value, row, index) {
+ if(row.id == Config.admin.id){
+ return '';
+ }
+ return Table.api.formatter.operate.call(this, value, row, index);
+ }}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Form.api.bindevent($("form[role=form]"));
+ },
+ edit: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/auth/adminlog.js b/server/public/assets/js/backend/auth/adminlog.js
new file mode 100644
index 0000000..77556a7
--- /dev/null
+++ b/server/public/assets/js/backend/auth/adminlog.js
@@ -0,0 +1,64 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'auth/adminlog/index',
+ add_url: '',
+ edit_url: '',
+ del_url: 'auth/adminlog/del',
+ multi_url: 'auth/adminlog/multi',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ fixedColumns: true,
+ fixedRightNumber: 1,
+ columns: [
+ [
+ {field: 'state', checkbox: true,},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'admin_id', title: __('Admin_id'), formatter: Table.api.formatter.search, visible: false},
+ {field: 'username', title: __('Username'), formatter: Table.api.formatter.search},
+ {field: 'title', title: __('Title'), operate: 'LIKE %...%', placeholder: '模糊搜索'},
+ {field: 'url', title: __('Url'), formatter: Table.api.formatter.url},
+ {field: 'ip', title: __('IP'), events: Table.api.events.ip, formatter: Table.api.formatter.search},
+ {field: 'createtime', title: __('Create time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {
+ field: 'operate', title: __('Operate'), table: table,
+ events: Table.api.events.operate,
+ buttons: [{
+ name: 'detail',
+ text: __('Detail'),
+ icon: '',
+ classname: 'btn btn-info btn-xs btn-detail btn-dialog',
+ url: 'auth/adminlog/detail'
+ }],
+ formatter: Table.api.formatter.operate
+ }
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ },
+ formatter: {
+ browser: function (value, row, index) {
+ return '' + row.useragent.split(" ")[0] + '';
+ },
+ },
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/auth/group.js b/server/public/assets/js/backend/auth/group.js
new file mode 100644
index 0000000..1a98ea2
--- /dev/null
+++ b/server/public/assets/js/backend/auth/group.js
@@ -0,0 +1,160 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function ($, undefined, Backend, Table, Form, undefined) {
+ //读取选中的条目
+ $.jstree.core.prototype.get_all_checked = function (full) {
+ var obj = this.get_selected(), i, j;
+ for (i = 0, j = obj.length; i < j; i++) {
+ obj = obj.concat(this.get_node(obj[i]).parents);
+ }
+ obj = $.grep(obj, function (v, i, a) {
+ return v != '#';
+ });
+ obj = obj.filter(function (itm, i, a) {
+ return i == a.indexOf(itm);
+ });
+ return full ? $.map(obj, $.proxy(function (i) {
+ return this.get_node(i);
+ }, this)) : obj;
+ };
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ "index_url": "auth/group/index",
+ "add_url": "auth/group/add",
+ "edit_url": "auth/group/edit",
+ "del_url": "auth/group/del",
+ "multi_url": "auth/group/multi",
+ }
+ });
+
+ var table = $("#table");
+
+ //在表格内容渲染完成后回调的事件
+ table.on('post-body.bs.table', function (e, json) {
+ $("tbody tr[data-index]", this).each(function () {
+ if (Config.admin.group_ids.indexOf(parseInt(parseInt($("td:eq(1)", this).text()))) > -1) {
+ $("input[type=checkbox]", this).prop("disabled", true);
+ }
+ });
+ });
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ columns: [
+ [
+ {field: 'state', checkbox: true,},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'pid', title: __('Parent')},
+ {field: 'name', title: __('Name'), align: 'left', formatter:function (value, row, index) {
+ return value.toString().replace(/(&|&)nbsp;/g, ' ');
+ }
+ },
+ {field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
+ {
+ field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: function (value, row, index) {
+ if (Config.admin.group_ids.indexOf(parseInt(row.id)) > -1) {
+ return '';
+ }
+ return Table.api.formatter.operate.call(this, value, row, index);
+ }
+ }
+ ]
+ ],
+ pagination: false,
+ search: false,
+ commonSearch: false,
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);//当内容渲染完成后
+
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"), null, null, function () {
+ if ($("#treeview").length > 0) {
+ var r = $("#treeview").jstree("get_all_checked");
+ $("input[name='row[rules]']").val(r.join(','));
+ }
+ return true;
+ });
+ //渲染权限节点树
+ //变更级别后需要重建节点树
+ $(document).on("change", "select[name='row[pid]']", function () {
+ var pid = $(this).data("pid");
+ var id = $(this).data("id");
+ if ($(this).val() == id) {
+ $("option[value='" + pid + "']", this).prop("selected", true).change();
+ Backend.api.toastr.error(__('Can not change the parent to self'));
+ return false;
+ }
+ $.ajax({
+ url: "auth/group/roletree",
+ type: 'post',
+ dataType: 'json',
+ data: {id: id, pid: $(this).val()},
+ success: function (ret) {
+ if (ret.hasOwnProperty("code")) {
+ var data = ret.hasOwnProperty("data") && ret.data != "" ? ret.data : "";
+ if (ret.code === 1) {
+ //销毁已有的节点树
+ $("#treeview").jstree("destroy");
+ Controller.api.rendertree(data);
+ } else {
+ Backend.api.toastr.error(ret.msg);
+ }
+ }
+ }, error: function (e) {
+ Backend.api.toastr.error(e.message);
+ }
+ });
+ });
+ //全选和展开
+ $(document).on("click", "#checkall", function () {
+ $("#treeview").jstree($(this).prop("checked") ? "check_all" : "uncheck_all");
+ });
+ $(document).on("click", "#expandall", function () {
+ $("#treeview").jstree($(this).prop("checked") ? "open_all" : "close_all");
+ });
+ $("select[name='row[pid]']").trigger("change");
+ },
+ rendertree: function (content) {
+ $("#treeview")
+ .on('redraw.jstree', function (e) {
+ $(".layer-footer").attr("domrefresh", Math.random());
+ })
+ .jstree({
+ "themes": {"stripes": true},
+ "checkbox": {
+ "keep_selected_style": false,
+ },
+ "types": {
+ "root": {
+ "icon": "fa fa-folder-open",
+ },
+ "menu": {
+ "icon": "fa fa-folder-open",
+ },
+ "file": {
+ "icon": "fa fa-file-o",
+ }
+ },
+ "plugins": ["checkbox", "types"],
+ "core": {
+ 'check_callback': true,
+ "data": content
+ }
+ });
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/auth/rule.js b/server/public/assets/js/backend/auth/rule.js
new file mode 100644
index 0000000..f342b82
--- /dev/null
+++ b/server/public/assets/js/backend/auth/rule.js
@@ -0,0 +1,221 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'template'], function ($, undefined, Backend, Table, Form, Template) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ "index_url": "auth/rule/index",
+ "add_url": "auth/rule/add",
+ "edit_url": "auth/rule/edit",
+ "del_url": "auth/rule/del",
+ "multi_url": "auth/rule/multi",
+ "table": "auth_rule"
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ sortName: '',
+ escape: true,
+ columns: [
+ [
+ {field: 'state', checkbox: true,},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'title', title: __('Title'), align: 'left', formatter: Controller.api.formatter.title, clickToSelect: !false},
+ {field: 'icon', title: __('Icon'), formatter: Controller.api.formatter.icon},
+ {field: 'name', title: __('Name'), align: 'left', formatter: Controller.api.formatter.name},
+ {field: 'weigh', title: __('Weigh')},
+ {field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
+ {
+ field: 'ismenu',
+ title: __('Ismenu'),
+ align: 'center',
+ table: table,
+ formatter: Table.api.formatter.toggle
+ },
+ {
+ field: 'operate',
+ title: __('Operate'),
+ table: table,
+ events: Table.api.events.operate,
+ formatter: Table.api.formatter.operate
+ }
+ ]
+ ],
+ pagination: false,
+ search: false,
+ commonSearch: false,
+ rowAttributes: function (row, index) {
+ return row.pid == 0 ? {} : {style: "display:none"};
+ }
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+
+ var btnSuccessEvent = function (data, ret) {
+ if ($(this).hasClass("btn-change")) {
+ var index = $(this).data("index");
+ var row = Table.api.getrowbyindex(table, index);
+ row.ismenu = $("i.fa.text-gray", this).length > 0 ? 1 : 0;
+ table.bootstrapTable("updateRow", {index: index, row: row});
+ } else if ($(this).hasClass("btn-delone")) {
+ if ($(this).closest("tr[data-index]").find("a.btn-node-sub.disabled").length > 0) {
+ $(this).closest("tr[data-index]").remove();
+ } else {
+ table.bootstrapTable('refresh');
+ }
+ } else if ($(this).hasClass("btn-dragsort")) {
+ table.bootstrapTable('refresh');
+ }
+ Fast.api.refreshmenu();
+ return false;
+ };
+
+ //表格内容渲染前
+ table.on('pre-body.bs.table', function (e, data) {
+ var options = table.bootstrapTable("getOptions");
+ options.escape = true;
+ });
+
+ //当内容渲染完成后
+ table.on('post-body.bs.table', function (e, data) {
+ var options = table.bootstrapTable("getOptions");
+ options.escape = false;
+
+ //点击切换/排序/删除操作后刷新左侧菜单
+ $(".btn-change[data-id],.btn-delone,.btn-dragsort").data("success", btnSuccessEvent);
+
+ });
+
+ table.on('post-body.bs.table', function (e, settings, json, xhr) {
+ //显示隐藏子节点
+ $(">tbody>tr[data-index] > td", this).on('click', "a.btn-node-sub", function () {
+ var status = $(this).data("shown") ? true : false;
+ $("a[data-pid='" + $(this).data("id") + "']").each(function () {
+ $(this).closest("tr").toggle(!status);
+ });
+ if (status) {
+ $("a[data-pid='" + $(this).data("id") + "']").trigger("collapse");
+ }
+ $(this).data("shown", !status);
+ $("i", this).toggleClass("fa-caret-down").toggleClass("fa-caret-right");
+ return false;
+ });
+ });
+
+ //隐藏子节点
+ $(document).on("collapse", ".btn-node-sub", function () {
+ if ($("i", this).length > 0) {
+ $("a[data-pid='" + $(this).data("id") + "']").trigger("collapse");
+ }
+ $("i", this).removeClass("fa-caret-down").addClass("fa-caret-right");
+ $(this).data("shown", false);
+ $(this).closest("tr").toggle(false);
+ });
+
+ //批量删除后的回调
+ $(".toolbar > .btn-del,.toolbar .btn-more~ul>li>a").data("success", function (e) {
+ Fast.api.refreshmenu();
+ });
+
+ //展开隐藏一级
+ $(document.body).on("click", ".btn-toggle", function (e) {
+ $("a[data-id][data-pid][data-pid!=0].disabled").closest("tr").hide();
+ var that = this;
+ var show = $("i", that).hasClass("fa-chevron-down");
+ $("i", that).toggleClass("fa-chevron-down", !show).toggleClass("fa-chevron-up", show);
+ $("a[data-id][data-pid][data-pid!=0]").not('.disabled').closest("tr").toggle(show);
+ $(".btn-node-sub[data-pid=0]").data("shown", show);
+ });
+
+ //展开隐藏全部
+ $(document.body).on("click", ".btn-toggle-all", function (e) {
+ var that = this;
+ var show = $("i", that).hasClass("fa-plus");
+ $("i", that).toggleClass("fa-plus", !show).toggleClass("fa-minus", show);
+ $(".btn-node-sub:not([data-pid=0])").closest("tr").toggle(show);
+ $(".btn-node-sub").data("shown", show);
+ $(".btn-node-sub > i").toggleClass("fa-caret-down", show).toggleClass("fa-caret-right", !show);
+ });
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ formatter: {
+ title: function (value, row, index) {
+ value = value.toString().replace(/(&|&)nbsp;/g, ' ');
+ var caret = row.haschild == 1 || row.ismenu == 1 ? '' : '';
+ value = value.indexOf(" ") > -1 ? value.replace(/(.*) /, "$1" + caret) : caret + value;
+
+ value = !row.ismenu || row.status == 'hidden' ? "" + value + "" : value;
+ return '' + value + '';
+ },
+ name: function (value, row, index) {
+ return !row.ismenu || row.status == 'hidden' ? "" + value + "" : value;
+ },
+ icon: function (value, row, index) {
+ return '';
+ }
+ },
+ bindevent: function () {
+ $(document).on('click', "input[name='row[ismenu]']", function () {
+ var name = $("input[name='row[name]']");
+ var ismenu = $(this).val() == 1;
+ name.prop("placeholder", ismenu ? name.data("placeholder-menu") : name.data("placeholder-node"));
+ $('div[data-type="menu"]').toggleClass("hidden", !ismenu);
+ });
+ $("input[name='row[ismenu]']:checked").trigger("click");
+
+ var iconlist = [];
+ var iconfunc = function () {
+ Layer.open({
+ type: 1,
+ area: ['80%', '80%'], //宽高
+ content: Template('chooseicontpl', {iconlist: iconlist})
+ });
+ };
+ Form.api.bindevent($("form[role=form]"), function (data) {
+ Fast.api.refreshmenu();
+ });
+ $(document).on('change keyup', "#icon", function () {
+ $(this).prev().find("i").prop("class", $(this).val());
+ });
+ $(document).on('click', ".btn-search-icon", function () {
+ if (iconlist.length == 0) {
+ $.get(Config.site.cdnurl + "/assets/libs/font-awesome/css/font-awesome.css", function (ret) {
+ var exp = /fa-(.*):before/ig;
+ var result;
+ while ((result = exp.exec(ret)) != null) {
+ iconlist.push(result[1]);
+ }
+ iconfunc();
+ });
+ } else {
+ iconfunc();
+ }
+ });
+ $(document).on('click', '#chooseicon ul li', function () {
+ $("input[name='row[icon]']").val('fa fa-' + $(this).data("font")).trigger("change");
+ Layer.closeAll();
+ });
+ $(document).on('keyup', 'input.js-icon-search', function () {
+ $("#chooseicon ul li").show();
+ if ($(this).val() != '') {
+ $("#chooseicon ul li:not([data-font*='" + $(this).val() + "'])").hide();
+ }
+ });
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/banner.js b/server/public/assets/js/backend/banner.js
new file mode 100644
index 0000000..5e8da2f
--- /dev/null
+++ b/server/public/assets/js/backend/banner.js
@@ -0,0 +1,59 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'banner/index' + location.search,
+ add_url: 'banner/add',
+ edit_url: 'banner/edit',
+ del_url: 'banner/del',
+ multi_url: 'banner/multi',
+ import_url: 'banner/import',
+ table: 'banner',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'weigh',
+ fixedColumns: true,
+ fixedRightNumber: 1,
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'title', title: __('Title'), operate: 'LIKE'},
+ {field: 'badge', title: __('Badge'), operate: 'LIKE'},
+ {field: 'image', title: __('Image'), operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image},
+ {field: 'link', title: __('Link'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content},
+ {field: 'weigh', title: __('Weigh'), operate: false},
+ {field: 'status', title: __('Status'), searchList: {"normal":__('Normal'),"hidden":__('Hidden')}, formatter: Table.api.formatter.status},
+ {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/category.js b/server/public/assets/js/backend/category.js
new file mode 100644
index 0000000..0977d1f
--- /dev/null
+++ b/server/public/assets/js/backend/category.js
@@ -0,0 +1,93 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'category/index',
+ add_url: 'category/add',
+ edit_url: 'category/edit',
+ del_url: 'category/del',
+ multi_url: 'category/multi',
+ dragsort_url: 'ajax/weigh',
+ table: 'category',
+ }
+ });
+
+ var table = $("#table");
+ var tableOptions = {
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'weigh',
+ pagination: false,
+ commonSearch: false,
+ search: false,
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'type', title: __('Type'), operate: false, searchList: Config.searchList, formatter: Table.api.formatter.label},
+ {field: 'name', title: __('Name'), align: 'left', formatter:function (value, row, index) {
+ return value.toString().replace(/(&|&)nbsp;/g, ' ');
+ }
+ },
+ {field: 'nickname', title: __('Nickname')},
+ {field: 'flag', title: __('Flag'), formatter: Table.api.formatter.flag},
+ {field: 'image', title: __('Image'), operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image},
+ {field: 'weigh', title: __('Weigh')},
+ {field: 'status', title: __('Status'), operate: false, formatter: Table.api.formatter.status},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ };
+ // 初始化表格
+ table.bootstrapTable(tableOptions);
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+
+ //绑定TAB事件
+ $('a[data-toggle="tab"]').on('shown.bs.tab', function (e) {
+ // var options = table.bootstrapTable(tableOptions);
+ var typeStr = $(this).attr("href").replace('#', '');
+ var options = table.bootstrapTable('getOptions');
+ options.pageNumber = 1;
+ options.queryParams = function (params) {
+ // params.filter = JSON.stringify({type: typeStr});
+ params.type = typeStr;
+
+ return params;
+ };
+ table.bootstrapTable('refresh', {});
+ return false;
+
+ });
+
+ //必须默认触发shown.bs.tab事件
+ // $('ul.nav-tabs li.active a[data-toggle="tab"]').trigger("shown.bs.tab");
+
+ },
+ add: function () {
+ Controller.api.bindevent();
+ setTimeout(function () {
+ $("#c-type").trigger("change");
+ }, 100);
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ $(document).on("change", "#c-type", function () {
+ $("#c-pid option[data-type='all']").prop("selected", true);
+ $("#c-pid option").removeClass("hide");
+ $("#c-pid option[data-type!='" + $(this).val() + "'][data-type!='all']").addClass("hide");
+ $("#c-pid").data("selectpicker") && $("#c-pid").selectpicker("refresh");
+ });
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/coupon.js b/server/public/assets/js/backend/coupon.js
new file mode 100644
index 0000000..18be08a
--- /dev/null
+++ b/server/public/assets/js/backend/coupon.js
@@ -0,0 +1,60 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'coupon/index' + location.search,
+ add_url: 'coupon/add',
+ edit_url: 'coupon/edit',
+ del_url: 'coupon/del',
+ multi_url: 'coupon/multi',
+ import_url: 'coupon/import',
+ table: 'coupon',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'id',
+ fixedColumns: true,
+ fixedRightNumber: 1,
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'name', title: __('Name'), operate: 'LIKE'},
+ {field: 'type', title: __('Type'), searchList: {"cash":__('Cash'),"discount":__('Discount')}, formatter: Table.api.formatter.normal},
+ {field: 'amount', title: __('Amount'), operate: 'BETWEEN'},
+ {field: 'min_amount', title: __('Min_amount'), operate: 'BETWEEN'},
+ {field: 'total', title: __('Total')},
+ {field: 'received', title: __('Received')},
+ {field: 'status', title: __('Status'), searchList: {"normal":__('Normal'),"hidden":__('Hidden')}, formatter: Table.api.formatter.status},
+ {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/dashboard.js b/server/public/assets/js/backend/dashboard.js
new file mode 100644
index 0000000..9c02e81
--- /dev/null
+++ b/server/public/assets/js/backend/dashboard.js
@@ -0,0 +1,212 @@
+define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echarts-theme'], function ($, undefined, Backend, Datatable, Table, Echarts) {
+
+ var Controller = {
+ index: function () {
+ // 1. 初始化近 7 天订单与交易金额趋势图 (双轴图表)
+ var trendDom = document.getElementById('chart-order-trend');
+ if (trendDom) {
+ var trendChart = Echarts.init(trendDom, 'walden');
+ var trendOption = {
+ tooltip: {
+ trigger: 'axis',
+ axisPointer: {
+ type: 'cross',
+ crossStyle: {
+ color: '#999'
+ }
+ }
+ },
+ legend: {
+ data: ['订单量 (单)', '交易额 (元)'],
+ top: 0,
+ right: 10,
+ textStyle: {
+ color: '#64748b'
+ }
+ },
+ grid: {
+ left: '3%',
+ right: '4%',
+ bottom: '3%',
+ top: '15%',
+ containLabel: true
+ },
+ xAxis: [{
+ type: 'category',
+ data: Config.dateColumns || [],
+ axisPointer: {
+ type: 'shadow'
+ },
+ axisLine: {
+ lineStyle: {
+ color: '#cbd5e1'
+ }
+ },
+ axisLabel: {
+ color: '#64748b'
+ }
+ }],
+ yAxis: [
+ {
+ type: 'value',
+ name: '订单量 (单)',
+ minInterval: 1,
+ splitLine: {
+ lineStyle: {
+ type: 'dashed',
+ color: '#f1f5f9'
+ }
+ },
+ axisLabel: {
+ color: '#64748b'
+ }
+ },
+ {
+ type: 'value',
+ name: '交易额 (元)',
+ splitLine: {
+ show: false
+ },
+ axisLabel: {
+ color: '#64748b',
+ formatter: '¥{value}'
+ }
+ }
+ ],
+ series: [
+ {
+ name: '订单量 (单)',
+ type: 'bar',
+ barMaxWidth: 26,
+ itemStyle: {
+ barBorderRadius: [4, 4, 0, 0],
+ color: new Echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: '#38bdf8' },
+ { offset: 1, color: '#0284c7' }
+ ])
+ },
+ data: Config.orderCounts || []
+ },
+ {
+ name: '交易额 (元)',
+ type: 'line',
+ yAxisIndex: 1,
+ smooth: true,
+ showSymbol: true,
+ symbolSize: 6,
+ itemStyle: {
+ color: '#10b981'
+ },
+ lineStyle: {
+ width: 3,
+ color: '#10b981'
+ },
+ areaStyle: {
+ color: new Echarts.graphic.LinearGradient(0, 0, 0, 1, [
+ { offset: 0, color: 'rgba(16, 185, 129, 0.28)' },
+ { offset: 1, color: 'rgba(16, 185, 129, 0.02)' }
+ ])
+ },
+ data: Config.orderAmounts || []
+ }
+ ]
+ };
+ trendChart.setOption(trendOption);
+ }
+
+ // 2. 初始化订单履约状态环形分布图
+ var statusDom = document.getElementById('chart-order-status');
+ if (statusDom) {
+ var statusChart = Echarts.init(statusDom, 'walden');
+
+ // 语义色板
+ var colorMap = {
+ '已完成': '#10b981',
+ '待接单': '#f59e0b',
+ '已接单': '#3b82f6',
+ '服务中': '#06b6d4',
+ '待支付': '#94a3b8',
+ '报价中': '#8b5cf6',
+ '已取消': '#cbd5e1',
+ '售后中': '#ef4444'
+ };
+
+ var rawData = Config.statusData || [];
+ var formattedData = rawData.map(function (item) {
+ return {
+ name: item.name,
+ value: item.value,
+ itemStyle: {
+ color: colorMap[item.name] || undefined
+ }
+ };
+ });
+
+ var statusOption = {
+ tooltip: {
+ trigger: 'item',
+ formatter: '{b}: {c} 单 ({d}%)'
+ },
+ legend: {
+ orient: 'horizontal',
+ bottom: '0',
+ left: 'center',
+ textStyle: {
+ color: '#64748b',
+ fontSize: 12
+ },
+ itemWidth: 10,
+ itemHeight: 10
+ },
+ series: [
+ {
+ name: '订单状态',
+ type: 'pie',
+ radius: ['45%', '72%'],
+ center: ['50%', '45%'],
+ avoidLabelOverlap: false,
+ itemStyle: {
+ borderRadius: 6,
+ borderColor: '#fff',
+ borderWidth: 2
+ },
+ label: {
+ show: false,
+ position: 'center'
+ },
+ emphasis: {
+ label: {
+ show: true,
+ fontSize: 16,
+ fontWeight: 'bold',
+ formatter: '{b}\n{c} 单'
+ }
+ },
+ labelLine: {
+ show: false
+ },
+ data: formattedData
+ }
+ ]
+ };
+ statusChart.setOption(statusOption);
+ }
+
+ // 3. 统一监听视口变化
+ $(window).resize(function () {
+ if (trendChart) trendChart.resize();
+ if (statusChart) statusChart.resize();
+ });
+
+ // 点击刷新支持
+ $(document).on("click", ".btn-refresh", function () {
+ setTimeout(function () {
+ if (trendChart) trendChart.resize();
+ if (statusChart) statusChart.resize();
+ }, 100);
+ });
+ }
+ };
+
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/general/attachment.js b/server/public/assets/js/backend/general/attachment.js
new file mode 100644
index 0000000..9aa67bf
--- /dev/null
+++ b/server/public/assets/js/backend/general/attachment.js
@@ -0,0 +1,323 @@
+define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefined, Backend, Form, Table) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'general/attachment/index',
+ add_url: 'general/attachment/add',
+ edit_url: 'general/attachment/edit',
+ del_url: 'general/attachment/del',
+ multi_url: 'general/attachment/multi',
+ table: 'attachment'
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ sortName: 'id',
+ columns: [
+ [
+ {field: 'state', checkbox: true},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'category', title: __('Category'), operate: 'in', formatter: Table.api.formatter.label, searchList: Config.categoryList},
+ {field: 'admin_id', title: __('Admin_id'), visible: false, addClass: "selectpage", extend: "data-source='auth/admin/index' data-field='nickname'"},
+ {field: 'user_id', title: __('User_id'), visible: false, addClass: "selectpage", extend: "data-source='user/user/index' data-field='nickname'"},
+ {field: 'preview', title: __('Preview'), events: Controller.api.events.thumb, formatter: Controller.api.formatter.thumb, operate: false},
+ {field: 'url', title: __('Url'), formatter: Controller.api.formatter.url, visible: false},
+ {field: 'filename', title: __('Filename'), sortable: true, formatter: Controller.api.formatter.filename, operate: 'like'},
+ {
+ field: 'filesize', title: __('Filesize'), operate: 'BETWEEN', sortable: true, formatter: function (value, row, index) {
+ var size = parseFloat(value);
+ var i = Math.floor(Math.log(size) / Math.log(1024));
+ return (size / Math.pow(1024, i)).toFixed(i < 2 ? 0 : 2) * 1 + ' ' + ['B', 'KB', 'MB', 'GB', 'TB'][i];
+ }
+ },
+ {field: 'imagewidth', title: __('Imagewidth'), sortable: true, formatter: Controller.api.formatter.text},
+ {field: 'imageheight', title: __('Imageheight'), sortable: true, formatter: Controller.api.formatter.text},
+ {field: 'imagetype', title: __('Imagetype'), sortable: true, formatter: Controller.api.formatter.search, operate: 'like'},
+ {field: 'storage', title: __('Storage'), formatter: Controller.api.formatter.search, operate: 'like'},
+ {field: 'mimetype', title: __('Mimetype'), formatter: Controller.api.formatter.mimetype},
+ {
+ field: 'createtime',
+ title: __('Createtime'),
+ formatter: Table.api.formatter.datetime,
+ operate: 'RANGE',
+ addclass: 'datetimerange',
+ sortable: true,
+ width: 150
+ },
+ {
+ field: 'operate',
+ title: __('Operate'),
+ table: table,
+ events: Table.api.events.operate,
+ formatter: Table.api.formatter.operate
+ }
+ ]
+ ],
+ });
+
+ // 绑定过滤事件
+ $('.filter-type ul li a', table.closest(".panel-intro")).on('click', function (e) {
+ $(this).closest("ul").find("li").removeClass("active");
+ $(this).closest("li").addClass("active");
+ var field = 'mimetype';
+ var value = $(this).data("value") || '';
+ var object = $("[name='" + field + "']", table.closest(".bootstrap-table").find(".commonsearch-table"));
+ if (object.prop('tagName') == "SELECT") {
+ $("option[value='" + value + "']", object).prop("selected", true);
+ } else {
+ object.val(value);
+ }
+ table.trigger("uncheckbox");
+ table.bootstrapTable('refresh', {pageNumber: 1});
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+
+ // 附件归类
+ $(document).on('click', '.btn-classify', function () {
+ var ids = Table.api.selectedids(table);
+ Layer.open({
+ title: __('Classify'),
+ content: Template("typetpl", {}),
+ btn: [__('OK')],
+ yes: function (index, layero) {
+ var category = $("select[name='category']", layero).val();
+ Fast.api.ajax({
+ url: "general/attachment/classify",
+ type: "post",
+ data: {category: category, ids: ids.join(',')},
+ }, function () {
+ table.bootstrapTable('refresh', {});
+ Layer.close(index);
+ });
+ },
+ success: function (layero, index) {
+ }
+ });
+ });
+
+ },
+ select: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'general/attachment/select',
+ }
+ });
+ var urlArr = [];
+ var multiple = Backend.api.query('multiple');
+ multiple = multiple == 'true' ? true : false;
+
+ var table = $("#table");
+
+ table.on('check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table', function (e, row) {
+ if (e.type == 'check' || e.type == 'uncheck') {
+ row = [row];
+ } else {
+ urlArr = [];
+ }
+ $.each(row, function (i, j) {
+ if (e.type.indexOf("uncheck") > -1) {
+ var index = urlArr.indexOf(j.url);
+ if (index > -1) {
+ urlArr.splice(index, 1);
+ }
+ } else {
+ urlArr.indexOf(j.url) == -1 && urlArr.push(j.url);
+ }
+ });
+ });
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ sortName: 'id',
+ showToggle: false,
+ showExport: false,
+ maintainSelected: true,
+ fixedColumns: true,
+ fixedRightNumber: 1,
+ columns: [
+ [
+ {field: 'state', checkbox: multiple, visible: multiple, operate: false},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'category', title: __('Category'), operate: 'in', formatter: Table.api.formatter.label, searchList: Config.categoryList},
+ {field: 'admin_id', title: __('Admin_id'), formatter: Table.api.formatter.search, visible: false},
+ {field: 'user_id', title: __('User_id'), formatter: Table.api.formatter.search, visible: false},
+ {field: 'url', title: __('Preview'), events: Controller.api.events.thumb, formatter: Controller.api.formatter.thumb, operate: false},
+ {field: 'filename', title: __('Filename'), sortable: true, formatter: Controller.api.formatter.filename, operate: 'like'},
+ {field: 'imagewidth', title: __('Imagewidth'), operate: false, sortable: true},
+ {field: 'imageheight', title: __('Imageheight'), operate: false, sortable: true},
+ {
+ field: 'mimetype', title: __('Mimetype'), sortable: true, operate: 'LIKE %...%',
+ process: function (value, arg) {
+ return value.replace(/\*/g, '%');
+ },
+ formatter: Controller.api.formatter.mimetype
+ },
+ {field: 'createtime', title: __('Createtime'), width: 120, formatter: Table.api.formatter.datetime, datetimeFormat: 'YYYY-MM-DD', operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {
+ field: 'operate', title: __('Operate'), width: 85, events: {
+ 'click .btn-chooseone': function (e, value, row, index) {
+ Fast.api.close($.extend({multiple: multiple}, row));
+ },
+ }, formatter: function () {
+ return ' ' + __('Choose') + '';
+ }
+ }
+ ]
+ ]
+ });
+
+ // 绑定过滤事件
+ $('.filter-type ul li a', table.closest(".panel-intro")).on('click', function (e) {
+ $(this).closest("ul").find("li").removeClass("active");
+ $(this).closest("li").addClass("active");
+ var field = 'mimetype';
+ var value = $(this).data("value") || '';
+ var object = $("[name='" + field + "']", table.closest(".bootstrap-table").find(".commonsearch-table"));
+ if (object.prop('tagName') == "SELECT") {
+ $("option[value='" + value + "']", object).prop("selected", true);
+ } else {
+ object.val(value);
+ }
+ table.trigger("uncheckbox");
+ table.bootstrapTable('refresh', {pageNumber: 1});
+ });
+
+ // 选中多个
+ $(document).on("click", ".btn-choose-multi", function () {
+ Fast.api.close({url: urlArr.join(","), multiple: multiple});
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ require(['upload'], function (Upload) {
+ $("#toolbar .faupload").data("category", function (file) {
+ var category = $("ul.nav-tabs[data-field='category'] li.active a").data("value");
+ return category;
+ });
+ Upload.api.upload($("#toolbar .faupload"), function () {
+ $(".btn-refresh").trigger("click");
+ });
+ });
+ },
+ add: function () {
+ //上传完成后刷新父窗口
+ $(".faupload").data("upload-complete", function (files) {
+ setTimeout(function () {
+ window.parent.$(".btn-refresh").trigger("click");
+ }, 100);
+ });
+ // 获取上传类别
+ $("#faupload-third,#faupload-third-chunking").data("category", function (file) {
+ return $("#category-third").val();
+ });
+ // 获取上传类别
+ $("#faupload-local,#faupload-local-chunking").data("category", function (file) {
+ return $("#category-local").val();
+ });
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ },
+ events: {
+ thumb: {
+ 'click .img-preview-photos': function (e, value, row, index) {
+ e.preventDefault();
+ e.stopPropagation();
+ var previewUrl = row.previewurl || row.fullurl;
+ Layer.photos({
+ photos: {
+ title: row.filename || __('Preview'),
+ start: 0,
+ data: [{
+ alt: row.filename || '',
+ pid: row.id,
+ src: previewUrl,
+ thumb: previewUrl
+ }]
+ },
+ anim: 5
+ });
+ return false;
+ }
+ }
+ },
+ safeurl: function (value) {
+ value = value == null ? '' : String(value);
+ if (/[\x00-\x1f\x7f]/.test(value)) {
+ return '';
+ }
+ value = value.trim();
+ if (/^[a-z][a-z0-9+.-]*:/i.test(value) && !/^https?:\/\//i.test(value)) {
+ return '';
+ }
+ return Fast.api.escape(value);
+ },
+ formatter: {
+ text: function (value) {
+ return Fast.api.escape(value == null ? '' : String(value));
+ },
+ legacytext: function (value) {
+ value = value == null ? '' : String(value);
+ value = value.replace(/&(amp|lt|gt|quot|#0*39|#x0*27);/gi, function (match, entity) {
+ var entities = {
+ amp: '&', lt: '<', gt: '>', quot: '"',
+ '#39': "'", '#x27': "'"
+ };
+ entity = entity.toLowerCase().replace(/^#0+/, '#').replace(/^#x0+/, '#x');
+ return entities[entity] || match;
+ });
+ return Controller.api.formatter.text(value);
+ },
+ search: function (value, row, index) {
+ if (row && this.field && typeof row[this.field] !== 'undefined') {
+ value = row[this.field];
+ }
+ return Table.api.formatter.search.call(this, Controller.api.formatter.text(value), row, index);
+ },
+ thumb: function (value, row, index) {
+ var html = '';
+ var fullurl = Controller.api.safeurl(row.previewurl || row.fullurl);
+ if (row.mimetype && row.mimetype.indexOf("image") > -1) {
+ var thumburl = Controller.api.safeurl((row.previewurl || row.fullurl || '') + (row.thumb_style || ''));
+ html = '注册用户数: {c} 人' + }, + legend: { + data: ['注册用户数'], + right: 10, + top: 0 + }, + xAxis: { + type: 'category', + boundaryGap: false, + data: Config.column, + axisLine: { lineStyle: { color: '#e2e8f0' } }, + axisLabel: { color: '#64748b' } + }, + yAxis: { + minInterval: 1, + splitLine: { lineStyle: { type: 'dashed', color: '#f1f5f9' } }, + axisLabel: { color: '#64748b' } + }, + grid: [{ left: '2%', top: '15%', right: '2%', bottom: 30, containLabel: true }], + series: [{ + name: '注册用户数', + type: 'line', + smooth: true, + showSymbol: true, + symbolSize: 6, + areaStyle: { + normal: { + color: new Echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: 'rgba(255, 106, 0, 0.3)' }, + { offset: 1, color: 'rgba(255, 106, 0, 0.02)' } + ]) + } + }, + lineStyle: { normal: { width: 3, color: '#ff6a00' } }, + itemStyle: { color: '#ff6a00', borderColor: '#fff', borderWidth: 2 }, + data: Config.userdata + }] + }; + myChart.setOption(option); + + $(window).resize(function () { + myChart.resize(); + }); + + $(document).on("click", ".btn-refresh", function () { + setTimeout(function () { + myChart.resize(); + }, 0); + }); + } + }; + + return Controller; +}); diff --git a/server/public/assets/js/backend/general/profile.js b/server/public/assets/js/backend/general/profile.js new file mode 100644 index 0000000..e4550d0 --- /dev/null +++ b/server/public/assets/js/backend/general/profile.js @@ -0,0 +1,57 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'upload'], function ($, undefined, Backend, Table, Form, Upload) { + + var Controller = { + index: function () { + + // 初始化表格参数配置 + Table.api.init({ + search: true, + advancedSearch: true, + pagination: true, + extend: { + "index_url": "general/profile/index", + "add_url": "", + "edit_url": "", + "del_url": "", + "multi_url": "", + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + columns: [ + [ + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'title', title: __('Title')}, + {field: 'url', title: __('Url'), align: 'left', formatter: Table.api.formatter.url}, + {field: 'ip', title: __('ip'), formatter:Table.api.formatter.search}, + {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true}, + ] + ], + commonSearch: false + }); + + // 为表格绑定事件 + Table.api.bindevent(table);//当内容渲染完成后 + + // 给上传按钮添加上传成功事件 + $("#faupload-avatar").data("upload-success", function (data) { + var url = Backend.api.cdnurl(data.url); + $(".profile-user-img").prop("src", url); + Toastr.success("上传成功!"); + }); + + // 给表单绑定事件 + Form.api.bindevent($("#update-form"), function () { + $("input[name='row[password]']").val(''); + var url = Backend.api.cdnurl($("#c-avatar").val()); + top.window.$(".user-panel .image img,.user-menu > a > img,.user-header > img").prop("src", url); + return true; + }); + }, + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/general/system.js b/server/public/assets/js/backend/general/system.js new file mode 100644 index 0000000..dea5ee4 --- /dev/null +++ b/server/public/assets/js/backend/general/system.js @@ -0,0 +1,9 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + // 系统监控页面初始化 + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/home.js b/server/public/assets/js/backend/home.js new file mode 100644 index 0000000..681f2f3 --- /dev/null +++ b/server/public/assets/js/backend/home.js @@ -0,0 +1,224 @@ +define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echarts-theme'], function ($, undefined, Backend, Datatable, Table, Echarts) { + + var Controller = { + index: function () { + // 1. 初始化近 7 天家居售后订单与资金流水复合双轴图表 (奇兵到家橙白旗舰配色) + var trendDom = document.getElementById('home-chart-trend'); + if (trendDom) { + var trendChart = Echarts.init(trendDom, 'walden'); + var trendOption = { + tooltip: { + trigger: 'axis', + backgroundColor: 'rgba(15, 23, 42, 0.92)', + borderColor: '#ff6a00', + borderWidth: 1, + padding: [10, 14], + textStyle: { + color: '#ffffff', + fontSize: 12 + }, + axisPointer: { + type: 'cross', + crossStyle: { + color: '#fdba74' + } + } + }, + legend: { + data: ['服务订单量 (单)', 'GMV 资金流水 (元)'], + top: 0, + right: 15, + textStyle: { + color: '#475569', + fontWeight: 500 + } + }, + grid: { + left: '2%', + right: '3%', + bottom: '3%', + top: '14%', + containLabel: true + }, + xAxis: [{ + type: 'category', + data: Config.dateColumns || [], + axisPointer: { + type: 'shadow' + }, + axisLine: { + lineStyle: { + color: '#e2e8f0' + } + }, + axisLabel: { + color: '#64748b', + fontSize: 12 + } + }], + yAxis: [ + { + type: 'value', + name: '订单量 (单)', + minInterval: 1, + splitLine: { + lineStyle: { + type: 'dashed', + color: '#f8fafc' + } + }, + axisLabel: { + color: '#64748b' + } + }, + { + type: 'value', + name: 'GMV (元)', + splitLine: { + show: false + }, + axisLabel: { + color: '#ff6a00', + formatter: '¥{value}' + } + } + ], + series: [ + { + name: '服务订单量 (单)', + type: 'bar', + barMaxWidth: 24, + itemStyle: { + barBorderRadius: [6, 6, 0, 0], + color: new Echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: '#ff9800' }, + { offset: 1, color: '#ff6a00' } + ]), + shadowColor: 'rgba(255, 106, 0, 0.25)', + shadowBlur: 6 + }, + data: Config.orderCounts || [] + }, + { + name: 'GMV 资金流水 (元)', + type: 'line', + yAxisIndex: 1, + smooth: true, + showSymbol: true, + symbolSize: 7, + itemStyle: { + color: '#ea580c', + borderColor: '#ffffff', + borderWidth: 2 + }, + lineStyle: { + width: 3, + color: '#ea580c' + }, + areaStyle: { + color: new Echarts.graphic.LinearGradient(0, 0, 0, 1, [ + { offset: 0, color: 'rgba(255, 106, 0, 0.32)' }, + { offset: 1, color: 'rgba(255, 106, 0, 0.02)' } + ]) + }, + data: Config.orderAmounts || [] + } + ] + }; + trendChart.setOption(trendOption); + } + + // 2. 初始化订单生命周期环形甜甜圈图 (奇兵到家活力色系) + var pieDom = document.getElementById('home-chart-pie'); + if (pieDom) { + var pieChart = Echarts.init(pieDom, 'walden'); + + var colorPalette = { + '已完成': '#10b981', + '待接单': '#ff6a00', // 核心主橙 + '已接单': '#0284c7', + '服务中': '#0ea5e9', + '待支付': '#94a3b8', + '报价中': '#d97706', + '已取消': '#cbd5e1', + '售后中': '#dc2626' + }; + + var statusList = Config.statusChartData || []; + var pieData = statusList.map(function (item) { + return { + name: item.name, + value: item.value, + itemStyle: { + color: colorPalette[item.name] || undefined + } + }; + }); + + var pieOption = { + tooltip: { + trigger: 'item', + formatter: '{b}: {c} 单 ({d}%)' + }, + legend: { + orient: 'horizontal', + bottom: '0', + left: 'center', + textStyle: { + color: '#64748b', + fontSize: 12 + }, + itemWidth: 10, + itemHeight: 10 + }, + series: [ + { + name: '订单履约状态', + type: 'pie', + radius: ['45%', '70%'], + center: ['50%', '45%'], + avoidLabelOverlap: false, + itemStyle: { + borderRadius: 8, + borderColor: '#fff', + borderWidth: 2 + }, + label: { + show: false, + position: 'center' + }, + emphasis: { + label: { + show: true, + fontSize: 16, + fontWeight: 'bold', + formatter: '{b}\n{c} 单' + } + }, + labelLine: { + show: false + }, + data: pieData + } + ] + }; + pieChart.setOption(pieOption); + } + + // 3. 视口与刷新联动 + $(window).resize(function () { + if (trendChart) trendChart.resize(); + if (pieChart) pieChart.resize(); + }); + + $(document).on("click", ".btn-refresh", function () { + setTimeout(function () { + if (trendChart) trendChart.resize(); + if (pieChart) pieChart.resize(); + }, 100); + }); + } + }; + + return Controller; +}); diff --git a/server/public/assets/js/backend/index.js b/server/public/assets/js/backend/index.js new file mode 100644 index 0000000..3c3f69f --- /dev/null +++ b/server/public/assets/js/backend/index.js @@ -0,0 +1,441 @@ +define(['jquery', 'bootstrap', 'backend', 'addtabs', 'adminlte', 'form'], function ($, undefined, Backend, undefined, AdminLTE, Form) { + var Controller = { + index: function () { + //双击重新加载页面 + $(document).on("dblclick", ".sidebar-menu li > a", function (e) { + $("#con_" + $(this).attr("addtabs") + " iframe").attr('src', function (i, val) { + return val; + }); + e.stopPropagation(); + }); + + //修复在移除窗口时下拉框不隐藏的BUG + $(window).on("blur", function () { + $("[data-toggle='dropdown']").parent().removeClass("open"); + if ($("body").hasClass("sidebar-open")) { + $(".sidebar-toggle").trigger("click"); + } + }); + + //快捷搜索 + $(".menuresult").width($("form.sidebar-form > .input-group").width()); + var searchResult = $(".menuresult"); + $("form.sidebar-form").on("blur", "input[name=q]", function () { + searchResult.addClass("hide"); + }).on("focus", "input[name=q]", function () { + if ($("a", searchResult).length > 0) { + searchResult.removeClass("hide"); + } + }).on("keyup", "input[name=q]", function () { + searchResult.html(''); + var val = $(this).val(); + var html = []; + if (val != '') { + $("ul.sidebar-menu li a[addtabs]:not([href^='javascript:;'])").each(function () { + if ($("span:first", this).text().indexOf(val) > -1 || $(this).attr("py").indexOf(val) > -1 || $(this).attr("pinyin").indexOf(val) > -1) { + html.push('' + $("span:first", this).text() + ''); + if (html.length >= 100) { + return false; + } + } + }); + } + $(searchResult).append(html.join("")); + if (html.length > 0) { + searchResult.removeClass("hide"); + } else { + searchResult.addClass("hide"); + } + }); + //快捷搜索点击事件 + $("form.sidebar-form").on('mousedown click', '.menuresult a[data-url]', function () { + Backend.api.addtabs($(this).data("url")); + }); + + //切换左侧sidebar显示隐藏 + $(document).on("click fa.event.toggleitem", ".sidebar-menu li > a", function (e) { + var nextul = $(this).next("ul"); + if (nextul.length == 0 && (!$(this).parent("li").hasClass("treeview") || ($("body").hasClass("multiplenav") && $(this).parent().parent().hasClass("sidebar-menu")))) { + $(".sidebar-menu li").not($(this).parents("li")).removeClass("active"); + } + //当外部触发隐藏的a时,触发父辈a的事件 + if (!$(this).closest("ul").is(":visible")) { + //如果不需要左侧的菜单栏联动可以注释下面一行即可 + $(this).closest("ul").prev().trigger("click"); + } + + var visible = nextul.is(":visible"); + if (nextul.length == 0) { + $(this).parents("li").addClass("active"); + $(this).closest(".treeview").addClass("treeview-open"); + } else { + } + e.stopPropagation(); + }); + + //清除缓存 + $(document).on('click', "ul.wipecache li a,a.wipecache", function () { + $.ajax({ + url: 'ajax/wipecache', + dataType: 'json', + data: {type: $(this).data("type")}, + cache: false, + success: function (ret) { + if (ret.hasOwnProperty("code")) { + var msg = ret.hasOwnProperty("msg") && ret.msg != "" ? ret.msg : ""; + if (ret.code === 1) { + Toastr.success(msg ? msg : __('Wipe cache completed')); + } else { + Toastr.error(msg ? msg : __('Wipe cache failed')); + } + } else { + Toastr.error(__('Unknown data format')); + } + }, error: function () { + Toastr.error(__('Network error')); + } + }); + }); + + //全屏事件 + $(document).on('click', "[data-toggle='fullscreen']", function () { + var doc = document.documentElement; + if ($(document.body).hasClass("full-screen")) { + $(document.body).removeClass("full-screen"); + document.exitFullscreen ? document.exitFullscreen() : document.mozCancelFullScreen ? document.mozCancelFullScreen() : document.webkitExitFullscreen && document.webkitExitFullscreen(); + } else { + $(document.body).addClass("full-screen"); + doc.requestFullscreen ? doc.requestFullscreen() : doc.mozRequestFullScreen ? doc.mozRequestFullScreen() : doc.webkitRequestFullscreen ? doc.webkitRequestFullscreen() : doc.msRequestFullscreen && doc.msRequestFullscreen(); + } + }); + + var multiplenav = $("body").hasClass("multiplenav") > 0 ? true : false; + var firstnav = $("#firstnav .nav-addtabs"); + var nav = multiplenav ? $("#secondnav .nav-addtabs") : firstnav; + + //刷新菜单事件 + $(document).on('refresh', '.sidebar-menu', function () { + Fast.api.ajax({ + url: 'index/index', + data: {action: 'refreshmenu'}, + loading: false + }, function (data) { + $(".sidebar-menu li:not([data-rel='external'])").remove(); + $(".sidebar-menu").prepend(data.menulist); + if (multiplenav) { + firstnav.html(data.navlist); + } + $("li[role='presentation'].active a", nav).trigger('click'); + $(window).trigger("resize"); + return false; + }, function () { + return false; + }); + }); + + if (multiplenav) { + firstnav.css("overflow", "inherit"); + //一级菜单自适应 + $(window).resize(function () { + var siblingsWidth = 0; + firstnav.siblings().each(function () { + siblingsWidth += $(this).outerWidth(); + }); + firstnav.width(firstnav.parent().width() - siblingsWidth); + firstnav.refreshAddtabs(); + }); + + //点击顶部第一级菜单栏 + firstnav.on("click", "li a", function () { + $("li", firstnav).removeClass("active"); + $(this).closest("li").addClass("active"); + $(".sidebar-menu > li[pid]").addClass("hidden"); + if ($(this).attr("url") == "javascript:;") { + var sonlist = $(".sidebar-menu > li[pid='" + $(this).attr("addtabs") + "']"); + sonlist.removeClass("hidden"); + var sidenav; + var last_id = $(this).attr("last-id"); + if (last_id) { + sidenav = $(".sidebar-menu > li[pid='" + $(this).attr("addtabs") + "'] a[addtabs='" + last_id + "']"); + } else { + sidenav = $(".sidebar-menu > li[pid='" + $(this).attr("addtabs") + "']:first > a"); + } + if (sidenav) { + sidenav.attr("href") != "javascript:;" && sidenav.trigger('click'); + } + } else { + + } + }); + + var mobilenav = $(".mobilenav"); + $("#firstnav .nav-addtabs li a").each(function () { + mobilenav.append($(this).clone().addClass("btn btn-app")); + }); + + //点击移动端一级菜单 + mobilenav.on("click", "a", function () { + $("a", mobilenav).removeClass("active"); + $(this).addClass("active"); + $(".sidebar-menu > li[pid]").addClass("hidden"); + if ($(this).attr("url") == "javascript:;") { + var sonlist = $(".sidebar-menu > li[pid='" + $(this).attr("addtabs") + "']"); + sonlist.removeClass("hidden"); + } + }); + + //点击左侧菜单栏 + $(document).on('click', '.sidebar-menu li a[addtabs]', function (e) { + var parents = $(this).parentsUntil("ul.sidebar-menu", "li"); + var top = parents[parents.length - 1]; + var pid = $(top).attr("pid"); + if (pid) { + var obj = $("li a[addtabs=" + pid + "]", firstnav); + var last_id = obj.attr("last-id"); + if (!last_id || last_id != pid) { + obj.attr("last-id", $(this).attr("addtabs")); + if (!obj.closest("li").hasClass("active")) { + obj.trigger("click"); + } + } + mobilenav.find("a").removeClass("active"); + mobilenav.find("a[addtabs='" + pid + "']").addClass("active"); + } + }); + } + + //这一行需要放在点击左侧链接事件之前 + var addtabs = Config.referer ? sessionStorage.getItem("addtabs") : null; + + //绑定tabs事件,如果需要点击强制刷新iframe,则请将iframeForceRefresh置为true,iframeForceRefreshTable只强制刷新表格 + nav.addtabs({iframeHeight: "100%", iframeForceRefresh: false, iframeForceRefreshTable: true, nav: nav}); + + if ($("ul.sidebar-menu li.active a").length > 0) { + $("ul.sidebar-menu li.active a").trigger("click"); + } else { + if (multiplenav) { + $("li:first > a", firstnav).trigger("click"); + } else { + $("ul.sidebar-menu li a[url!='javascript:;']:first").trigger("click"); + } + } + + //如果是刷新操作则直接返回刷新前的页面 + if (Config.referer) { + if (Config.referer === $(addtabs).attr("url")) { + var active = $("ul.sidebar-menu li a[addtabs=" + $(addtabs).attr("addtabs") + "]"); + if (multiplenav && active.length == 0) { + active = $("ul li a[addtabs='" + $(addtabs).attr("addtabs") + "']"); + } + if (active.length > 0) { + active.trigger("click"); + } else { + $(addtabs).appendTo(document.body).addClass("hide").trigger("click"); + } + } else { + //刷新页面后跳到到刷新前的页面 + Backend.api.addtabs(Config.referer); + } + } + + var createCookie = function (name, value) { + var date = new Date(); + date.setTime(date.getTime() + (365 * 24 * 60 * 60 * 1000)); + var path = Config.moduleurl; + document.cookie = encodeURIComponent(Config.cookie.prefix + name) + "=" + encodeURIComponent(value) + "; path=" + path + "; expires=" + date.toGMTString(); + }; + + var my_skins = [ + "skin-blue", + "skin-black", + "skin-red", + "skin-yellow", + "skin-purple", + "skin-green", + "skin-blue-light", + "skin-black-light", + "skin-red-light", + "skin-yellow-light", + "skin-purple-light", + "skin-green-light", + "skin-black-blue", + "skin-black-purple", + "skin-black-red", + "skin-black-green", + "skin-black-yellow", + "skin-black-pink", + ]; + + // 皮肤切换 + $("[data-skin]").on('click', function (e) { + var skin = $(this).data('skin'); + if (!$("body").hasClass(skin)) { + $("body").removeClass(my_skins.join(' ')).addClass(skin); + var cssfile = Config.site.cdnurl + "/assets/css/skins/" + skin + ".css"; + $('head').append(''); + $(".skin-list li.active").removeClass("active"); + $(".skin-list li a[data-skin='" + skin + "']").parent().addClass("active"); + createCookie('adminskin', skin); + } + return false; + }); + + // 收起菜单栏切换 + $("[data-layout='sidebar-collapse']").on('click', function () { + $(".sidebar-toggle").trigger("click"); + }); + + // 切换子菜单显示和菜单小图标的显示 + $("[data-menu='show-submenu']").on('click', function () { + createCookie('show_submenu', $(this).prop("checked") ? 1 : 0); + location.reload(); + }); + + // 右侧控制栏切换 + $("[data-controlsidebar]").on('click', function () { + var cls = $(this).data('controlsidebar'); + $("body").toggleClass(cls); + AdminLTE.layout.fixSidebar(); + //Fix the problem with right sidebar and layout boxed + if (cls == "layout-boxed") + AdminLTE.controlSidebar._fix($(".control-sidebar-bg")); + if ($('body').hasClass('fixed') && cls == 'fixed') { + AdminLTE.pushMenu.expandOnHover(); + AdminLTE.layout.activate(); + } + AdminLTE.controlSidebar._fix($(".control-sidebar-bg")); + AdminLTE.controlSidebar._fix($(".control-sidebar")); + var slide = !AdminLTE.options.controlSidebarOptions.slide; + AdminLTE.options.controlSidebarOptions.slide = slide; + if (!slide) + $('.control-sidebar').removeClass('control-sidebar-open'); + }); + + // 右侧控制栏背景切换 + $("[data-sidebarskin='toggle']").on('click', function () { + var sidebar = $(".control-sidebar"); + if (sidebar.hasClass("control-sidebar-dark")) { + sidebar.removeClass("control-sidebar-dark") + sidebar.addClass("control-sidebar-light") + } else { + sidebar.removeClass("control-sidebar-light") + sidebar.addClass("control-sidebar-dark") + } + }); + + // 菜单栏展开或收起 + $("[data-enable='expandOnHover']").on('click', function () { + $.AdminLTE.options.sidebarExpandOnHover = $(this).prop("checked") ? 1 : 0; + localStorage.setItem('sidebarExpandOnHover', $.AdminLTE.options.sidebarExpandOnHover); + AdminLTE.pushMenu.expandOnHover(); + $.AdminLTE.layout.fixSidebar(); + }); + + // 切换菜单栏 + $(document).on("click", ".sidebar-toggle", function () { + setTimeout(function () { + var value = $("body").hasClass("sidebar-collapse") ? 1 : 0; + setTimeout(function () { + $(window).trigger("resize"); + }, 300); + createCookie('sidebar_collapse', value); + }, 0); + }); + + // 切换多级菜单 + $(document).on("click", "[data-config='multiplenav']", function () { + var value = $(this).prop("checked") ? 1 : 0; + createCookie('multiplenav', value); + location.reload(); + }); + + // 切换多选项卡 + $(document).on("click", "[data-config='multipletab']", function () { + var value = $(this).prop("checked") ? 1 : 0; + $("body").toggleClass("multipletab", value); + createCookie('multipletab', value); + }); + + // 重设选项 + if ($('body').hasClass('fixed')) { + $("[data-layout='fixed']").attr('checked', 'checked'); + } + if ($('body').hasClass('layout-boxed')) { + $("[data-layout='layout-boxed']").attr('checked', 'checked'); + } + if ($('body').hasClass('sidebar-collapse')) { + $("[data-layout='sidebar-collapse']").attr('checked', 'checked'); + } + if ($('ul.sidebar-menu').hasClass('show-submenu')) { + $("[data-menu='show-submenu']").attr('checked', 'checked'); + } + + var sidebarExpandOnHover = localStorage.getItem('sidebarExpandOnHover'); + if (sidebarExpandOnHover == '1') { + $("[data-enable='expandOnHover']").trigger("click"); + } + + $.each(my_skins, function (i, j) { + if ($("body").hasClass(j)) { + $(".skin-list li a[data-skin='" + j + "']").parent().addClass("active"); + } + }); + + $(document).on("click", "a[href*='index/logout']", function () { + var that = this; + $.ajax({ + type: 'GET', dataType: 'html', url: $(that).attr("href"), + success: function (data, status, xhr) { + Fast.api.ajax({url: $(that).attr("href"), loading: false, data: {__token__: xhr.getResponseHeader('__token__')}}, function (data, ret) { + Layer.msg(ret.msg, {icon: 1, time: 1500}, function () { + location.reload(); + }); + return false; + }); + }, error: function (xhr, type) { + Layer.msg(__('Network error'), {icon: 2}); + } + }); + return false; + }); + + $(window).resize(); + + }, + login: function () { + var lastlogin = localStorage.getItem("lastlogin"); + if (lastlogin) { + lastlogin = JSON.parse(lastlogin); + $("#profile-img").attr("src", Backend.api.cdnurl(lastlogin.avatar)); + $("#profile-name").val(lastlogin.username); + } + + //让错误提示框居中 + Fast.config.toastr.positionClass = "toast-top-center"; + + //本地验证未通过时提示 + $("#login-form").data("validator-options", { + invalid: function (form, errors) { + $.each(errors, function (i, j) { + Toastr.error(j); + }); + }, + target: '#errtips' + }); + + //为表单绑定事件 + Form.api.bindevent($("#login-form"), function (data) { + localStorage.setItem("lastlogin", JSON.stringify({ + id: data.id, + username: data.username, + avatar: data.avatar + })); + location.href = Backend.api.fixurl(data.url); + }, function (data) { + $("input[name=captcha]").next(".input-group-addon").find("img").trigger("click"); + }); + } + }; + + return Controller; +}); diff --git a/server/public/assets/js/backend/invoice.js b/server/public/assets/js/backend/invoice.js new file mode 100644 index 0000000..577895a --- /dev/null +++ b/server/public/assets/js/backend/invoice.js @@ -0,0 +1,60 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + // 初始化表格参数配置(开票申请仅用户/商户端发起,后台只提供开票处理) + Table.api.init({ + extend: { + index_url: 'invoice/index' + location.search, + edit_url: 'invoice/edit', + table: 'invoice', + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'id', + sortOrder: 'desc', + fixedColumns: true, + fixedRightNumber: 1, + columns: [ + [ + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'title', title: '开票抬头', operate: 'LIKE'}, + {field: 'type', title: '发票类型', searchList: {"normal":__('Ordinary'),"special":__('Special')}, formatter: Table.api.formatter.normal}, + {field: 'amount', title: '开票金额', operate: 'BETWEEN'}, + {field: 'merchant.name', title: '开票主体', operate: 'LIKE', formatter: function (value) { return value ? value : '个人用户'; }}, + {field: 'user.username', title: '申请用户', operate: 'LIKE'}, + {field: 'tax_no', title: '税号', operate: 'LIKE'}, + {field: 'email', title: '接收邮箱', operate: 'LIKE'}, + {field: 'status', title: '处理状态', searchList: {"wait":__('Wait'),"issued":__('Issued'),"reject":__('Reject')}, formatter: Table.api.formatter.status}, + {field: 'invoice_no', title: '发票号码', operate: 'LIKE'}, + {field: 'audit_msg', title: '处理说明', operate: 'LIKE', class: 'autocontent', formatter: Table.api.formatter.content}, + {field: 'createtime', title: '申请时间', operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime, width: '150px'}, + {field: 'operate', title: '开票处理', table: table, events: Table.api.events.operate, + buttons: [ + {name: 'edit', text: '处理', title: '财务开票处理', icon: '', classname: 'btn btn-xs btn-success btn-editone'} + ], + formatter: Table.api.formatter.operate} + ] + ] + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + }, + edit: function () { + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + Form.api.bindevent($("form[role=form]")); + } + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/master.js b/server/public/assets/js/backend/master.js new file mode 100644 index 0000000..08b196a --- /dev/null +++ b/server/public/assets/js/backend/master.js @@ -0,0 +1,107 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + // 初始化表格参数配置 + Table.api.init({ + extend: { + index_url: 'master/index' + location.search, + add_url: 'master/add', + edit_url: 'master/edit', + del_url: 'master/del', + detail_url: 'master/detail', + skills_url: 'master/skills', + multi_url: 'master/multi', + import_url: 'master/import', + table: 'master', + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'id', + fixedColumns: true, + fixedRightNumber: 1, + columns: [ + [ + {checkbox: true}, + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'realname', title: __('Realname'), operate: 'LIKE'}, + {field: 'mobile', title: __('Mobile'), operate: 'LIKE'}, + {field: 'user.username', title: __('关联用户'), operate: 'LIKE'}, + {field: 'city', title: __('City'), operate: 'LIKE'}, + { + field: 'skills_list', + title: '服务技能', + operate: false, + formatter: function (value, row, index) { + if (!value || value.length === 0) { + return '未配置'; + } + var tags = value.map(function (s) { + return '' + s + ''; + }); + return tags.join(''); + } + }, + {field: 'level', title: __('Level')}, + {field: 'score', title: __('Score'), operate: 'BETWEEN'}, + {field: 'order_count', title: __('Order_count')}, + {field: 'work_status', title: __('Work_status'), searchList: {"off":__('Off'),"on":__('On')}, formatter: Table.api.formatter.status}, + {field: 'verify_status', title: __('Verify_status'), searchList: {"pending":__('Pending'),"pass":__('Pass'),"reject":__('Reject')}, formatter: Table.api.formatter.status}, + {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime}, + { + field: 'operate', + title: __('Operate'), + table: table, + events: Table.api.events.operate, + formatter: Table.api.formatter.operate, + buttons: [ + { + name: 'skills', + text: '技能', + title: function (row) { + return row.realname + ' - 服务技能配置'; + }, + icon: 'fa fa-certificate', + classname: 'btn btn-xs btn-success btn-dialog', + url: 'master/skills' + }, + { + name: 'detail', + text: '详情', + title: '师傅全景档案画像', + icon: 'fa fa-user', + classname: 'btn btn-xs btn-warning btn-dialog', + url: 'master/detail' + } + ] + } + ] + ] + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + }, + add: function () { + Controller.api.bindevent(); + }, + edit: function () { + Controller.api.bindevent(); + }, + skills: function () { + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + Form.api.bindevent($("form[role=form]")); + } + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/master_skill.js b/server/public/assets/js/backend/master_skill.js new file mode 100644 index 0000000..4865f48 --- /dev/null +++ b/server/public/assets/js/backend/master_skill.js @@ -0,0 +1,79 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + // 初始化表格参数配置 + Table.api.init({ + extend: { + index_url: 'master_skill/index' + location.search, + add_url: 'master_skill/add', + edit_url: 'master_skill/edit', + del_url: 'master_skill/del', + multi_url: 'master_skill/multi', + table: 'master_skill', + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'id', + columns: [ + [ + {checkbox: true}, + {field: 'id', title: 'ID', sortable: true}, + {field: 'master.realname', title: '师傅姓名', operate: 'LIKE'}, + {field: 'master.mobile', title: '师傅电话', operate: 'LIKE'}, + {field: 'cat.name', title: '服务技能/类目', operate: 'LIKE'}, + { + field: 'cert_image', + title: '资质证书', + operate: false, + events: Table.api.events.image, + formatter: Table.api.formatter.image + }, + { + field: 'status', + title: '生效状态', + searchList: {"normal": '正常', "hidden": '隐藏'}, + formatter: Table.api.formatter.status + }, + { + field: 'createtime', + title: '认证时间', + operate: 'RANGE', + addclass: 'datetimerange', + autocomplete: false, + formatter: Table.api.formatter.datetime + }, + { + field: 'operate', + title: __('Operate'), + table: table, + events: Table.api.events.operate, + formatter: Table.api.formatter.operate + } + ] + ] + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + }, + add: function () { + Controller.api.bindevent(); + }, + edit: function () { + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + Form.api.bindevent($("form[role=form]")); + } + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/merchant.js b/server/public/assets/js/backend/merchant.js new file mode 100644 index 0000000..3285c7b --- /dev/null +++ b/server/public/assets/js/backend/merchant.js @@ -0,0 +1,79 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + // 初始化表格参数配置 + Table.api.init({ + extend: { + index_url: 'merchant/index' + location.search, + add_url: 'merchant/add', + edit_url: 'merchant/edit', + del_url: 'merchant/del', + detail_url: 'merchant/detail', + multi_url: 'merchant/multi', + import_url: 'merchant/import', + table: 'merchant', + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'id', + fixedColumns: true, + fixedRightNumber: 1, + columns: [ + [ + {checkbox: true}, + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'name', title: __('Name'), operate: 'LIKE'}, + {field: 'license_no', title: __('License_no'), operate: 'LIKE'}, + {field: 'legal_person', title: __('Legal_person'), operate: 'LIKE'}, + {field: 'contact', title: __('Contact'), operate: 'LIKE'}, + {field: 'mobile', title: __('Mobile'), operate: 'LIKE'}, + {field: 'user.username', title: __('关联用户'), operate: 'LIKE'}, + {field: 'settle_type', title: __('Settle_type'), searchList: {"prepay":__('Prepay'),"monthly":__('Monthly')}, formatter: Table.api.formatter.normal}, + {field: 'verify_status', title: __('Verify_status'), searchList: {"pending":__('Pending'),"pass":__('Pass'),"reject":__('Reject')}, formatter: Table.api.formatter.status}, + {field: 'order_count', title: __('Order_count')}, + {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime}, + { + field: 'operate', + title: __('Operate'), + table: table, + events: Table.api.events.operate, + formatter: Table.api.formatter.operate, + buttons: [ + { + name: 'detail', + text: '详情', + title: '商户企业资质看板', + icon: 'fa fa-building', + classname: 'btn btn-xs btn-primary btn-dialog', + url: 'merchant/detail' + } + ] + } + ] + ] + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + }, + add: function () { + Controller.api.bindevent(); + }, + edit: function () { + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + Form.api.bindevent($("form[role=form]")); + } + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/message.js b/server/public/assets/js/backend/message.js new file mode 100644 index 0000000..e524243 --- /dev/null +++ b/server/public/assets/js/backend/message.js @@ -0,0 +1,56 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + // 初始化表格参数配置 + Table.api.init({ + extend: { + index_url: 'message/index' + location.search, + add_url: 'message/add', + edit_url: 'message/edit', + del_url: 'message/del', + multi_url: 'message/multi', + import_url: 'message/import', + table: 'message', + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'id', + columns: [ + [ + {checkbox: true}, + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'receive_type', title: __('Receive_type'), searchList: {"user":__('User'),"master":__('Master')}, formatter: Table.api.formatter.normal}, + {field: 'type', title: __('Type'), searchList: {"order":__('Order'),"system":__('System'),"audit":__('Audit'),"remind":__('Remind')}, formatter: Table.api.formatter.normal}, + {field: 'title', title: __('Title'), operate: 'LIKE'}, + {field: 'content', title: __('Content'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content}, + {field: 'is_read', title: __('Is_read'), searchList: {"0":__('未读'),"1":__('已读')}, formatter: Table.api.formatter.normal}, + {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime}, + {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate} + ] + ] + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + }, + add: function () { + Controller.api.bindevent(); + }, + edit: function () { + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + Form.api.bindevent($("form[role=form]")); + } + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/order.js b/server/public/assets/js/backend/order.js new file mode 100644 index 0000000..96639bb --- /dev/null +++ b/server/public/assets/js/backend/order.js @@ -0,0 +1,80 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + // 初始化表格参数配置 + Table.api.init({ + extend: { + index_url: 'order/index' + location.search, + add_url: 'order/add', + edit_url: 'order/edit', + del_url: 'order/del', + detail_url: 'order/detail', + multi_url: 'order/multi', + import_url: 'order/import', + table: 'order', + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'id', + fixedColumns: true, + fixedRightNumber: 1, + columns: [ + [ + {checkbox: true}, + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'order_no', title: __('Order_no'), operate: 'LIKE'}, + {field: 'service_name', title: __('Service_name'), operate: 'LIKE'}, + {field: 'user.username', title: __('下单用户'), operate: 'LIKE'}, + {field: 'master.realname', title: __('接单师傅'), operate: 'LIKE'}, + {field: 'contact', title: __('Contact'), operate: 'LIKE'}, + {field: 'mobile', title: __('Mobile'), operate: 'LIKE'}, + {field: 'pay_amount', title: __('Pay_amount'), operate: 'BETWEEN'}, + {field: 'status', title: __('Status'), searchList: {"0":__('待支付'),"1":__('待接单'),"2":__('报价中'),"3":__('已接单'),"4":__('待上门'),"5":__('已到达'),"6":__('服务中'),"7":__('待验收'),"8":__('已完成'),"9":__('已取消'),"10":__('售后中')}, formatter: Table.api.formatter.normal}, + {field: 'pay_status', title: __('Pay_status'), searchList: {"0":__('未支付'),"1":__('已支付'),"2":__('已退款')}, formatter: Table.api.formatter.normal}, + {field: 'appoint_time', title: __('Appoint_time'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime}, + {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime}, + { + field: 'operate', + title: __('Operate'), + table: table, + events: Table.api.events.operate, + formatter: Table.api.formatter.operate, + buttons: [ + { + name: 'detail', + text: '详情', + title: '工单全景详情看板', + icon: 'fa fa-list-alt', + classname: 'btn btn-xs btn-warning btn-dialog', + url: 'order/detail' + } + ] + } + ] + ] + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + }, + add: function () { + Controller.api.bindevent(); + }, + edit: function () { + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + Form.api.bindevent($("form[role=form]")); + } + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/review.js b/server/public/assets/js/backend/review.js new file mode 100644 index 0000000..2a67c11 --- /dev/null +++ b/server/public/assets/js/backend/review.js @@ -0,0 +1,59 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + // 初始化表格参数配置 + Table.api.init({ + extend: { + index_url: 'review/index' + location.search, + add_url: 'review/add', + edit_url: 'review/edit', + del_url: 'review/del', + multi_url: 'review/multi', + import_url: 'review/import', + table: 'review', + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'id', + fixedColumns: true, + fixedRightNumber: 1, + columns: [ + [ + {checkbox: true}, + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'order.order_no', title: __('关联订单号'), operate: 'LIKE'}, + {field: 'user.username', title: __('评价用户'), operate: 'LIKE'}, + {field: 'master.realname', title: __('服务师傅'), operate: 'LIKE'}, + {field: 'score', title: __('Score')}, + {field: 'content', title: __('Content'), operate: 'LIKE', table: table, class: 'autocontent', formatter: Table.api.formatter.content}, + {field: 'status', title: __('Status'), searchList: {"normal":__('Normal'),"hidden":__('Hidden')}, formatter: Table.api.formatter.status}, + {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime}, + {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate} + ] + ] + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + }, + add: function () { + Controller.api.bindevent(); + }, + edit: function () { + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + Form.api.bindevent($("form[role=form]")); + } + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/service.js b/server/public/assets/js/backend/service.js new file mode 100644 index 0000000..2f832a4 --- /dev/null +++ b/server/public/assets/js/backend/service.js @@ -0,0 +1,137 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + Table.api.init({ + extend: { + index_url: 'service/index' + location.search, + add_url: 'service/add', + edit_url: 'service/edit', + del_url: 'service/del', + multi_url: 'service/multi', + dragsort_url: '', + table: 'service', + } + }); + + var table = $("#table"); + + table.on('post-body.bs.table', function () { + $(".btn-editone, .btn-dialog", this).data("area", ["980px", "86%"]); + }); + $(".btn-add, .btn-edit").data("area", ["980px", "86%"]); + + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'weigh', + sortOrder: 'desc', + columns: [ + [ + {checkbox: true}, + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'cat_id', title: '所属类目', visible: false, operate: '='}, + {field: 'cat.name', title: '所属类目', operate: 'LIKE', width: '100px'}, + {field: 'image', title: '商品图片', operate: false, events: Table.api.events.image, formatter: Table.api.formatter.image, width: '80px'}, + {field: 'name', title: '商品名称', operate: 'LIKE', align: 'left', formatter: function (val) { + return '' + val + ''; + }}, + {field: 'unit', title: '单位', operate: false, width: '65px'}, + { + field: 'specs_list', + title: '细分规格', + operate: false, + align: 'left', + formatter: function (val, row) { + var list = row.specs_list || val || []; + if (!Array.isArray(list) || !list.length) { + return '-'; + } + var preview = list.slice(0, 2).join('、'); + if (list.length > 2) { + preview += ' 等' + list.length + '款'; + } + return '' + preview + ''; + } + }, + { + field: 'service_types_list', + title: '主服务及定价', + operate: false, + align: 'left', + formatter: function (val, row) { + var list = row.service_types_list || []; + if (!Array.isArray(list) || !list.length) { + return '-'; + } + var html = []; + for (var i = 0; i < list.length; i++) { + var st = list[i]; + var isFixed = (typeof st.support_fixed !== 'undefined') ? (Number(st.support_fixed) === 1) : (Number(st.price) > 0); + if (isFixed && Number(st.price) > 0) { + html.push('' + st.name + ' ¥' + Number(st.price || 0).toFixed(0) + ''); + } else { + html.push('' + st.name + ''); + } + } + return html.join(''); + } + }, + { + field: 'addons_list', + title: '增项服务及加价', + operate: false, + align: 'left', + formatter: function (val, row) { + var list = row.addons_list || []; + if (!Array.isArray(list) || !list.length) { + return '无增项'; + } + var html = []; + for (var i = 0; i < list.length; i++) { + var ad = list[i]; + html.push('' + ad.name + ' +¥' + Number(ad.price).toFixed(0) + '/' + (ad.unit || '份') + ''); + } + return html.join(''); + } + }, + {field: 'weigh', title: '排序', operate: false, width: '70px', sortable: true}, + {field: 'status', title: __('Status'), width: '80px', searchList: {"normal": __('Normal'), "hidden": __('Hidden')}, formatter: Table.api.formatter.status}, + { + field: 'operate', + title: __('Operate'), + table: table, + events: Table.api.events.operate, + buttons: [ + { + name: 'pricing', + text: '服务定价', + title: function (row) { + return '配置【' + row.name + '】服务与价格'; + }, + classname: 'btn btn-xs btn-warning btn-dialog', + url: 'service/edit?tab=services' + } + ], + formatter: Table.api.formatter.operate + } + ] + ] + }); + + Table.api.bindevent(table); + }, + add: function () { + Controller.api.bindevent(); + }, + edit: function () { + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + Form.api.bindevent($("form[role=form]")); + } + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/service/cat.js b/server/public/assets/js/backend/service/cat.js new file mode 100644 index 0000000..e6de798 --- /dev/null +++ b/server/public/assets/js/backend/service/cat.js @@ -0,0 +1,94 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + Table.api.init({ + extend: { + index_url: 'service/cat/index' + location.search, + add_url: 'service/cat/add', + edit_url: 'service/cat/edit', + del_url: 'service/cat/del', + multi_url: 'service/cat/multi', + dragsort_url: '', + table: 'service_cat', + } + }); + + var table = $("#table"); + + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'weigh', + sortOrder: 'desc', + pageSize: 20, + columns: [ + [ + {checkbox: true}, + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'name', title: __('Name'), operate: 'LIKE', formatter: function (val) { + return '' + val + ''; + }}, + { + field: 'icon', + title: '类目图标', + operate: false, + formatter: function (value, row, index) { + if (!value) return '-'; + if (value.indexOf('/') !== -1 || value.indexOf('.') !== -1 || value.indexOf('http') === 0) { + return Table.api.formatter.image.call(this, value, row, index); + } + return '' + value + ''; + } + }, + { + field: 'goods_count', + title: '关联商品数', + operate: false, + formatter: function (val) { + return '' + (val || 0) + ' 种商品'; + } + }, + {field: 'weigh', title: __('Weigh'), operate: false, sortable: true}, + {field: 'status', title: __('Status'), searchList: {"normal": __('Normal'), "hidden": __('Hidden')}, formatter: Table.api.formatter.status}, + {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime}, + { + field: 'operate', + title: __('Operate'), + table: table, + events: Table.api.events.operate, + buttons: [ + { + name: 'view_goods', + text: '查看商品', + title: function (row) { + return '【' + row.name + '】商品管理'; + }, + classname: 'btn btn-xs btn-info btn-addtabs', + url: function (row) { + return 'service/index?cat_id=' + row.id; + } + } + ], + formatter: Table.api.formatter.operate + } + ] + ] + }); + + Table.api.bindevent(table); + }, + add: function () { + Controller.api.bindevent(); + }, + edit: function () { + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + Form.api.bindevent($("form[role=form]")); + } + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/service/type.js b/server/public/assets/js/backend/service/type.js new file mode 100644 index 0000000..e36ac3a --- /dev/null +++ b/server/public/assets/js/backend/service/type.js @@ -0,0 +1,67 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + Table.api.init({ + extend: { + index_url: 'service/type/index' + location.search, + add_url: 'service/type/add', + edit_url: 'service/type/edit', + del_url: 'service/type/del', + multi_url: 'service/type/multi', + dragsort_url: '', + table: 'service_type', + } + }); + + var table = $("#table"); + + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'weigh', + sortOrder: 'desc', + pageSize: 25, + columns: [ + [ + {checkbox: true}, + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'name', title: '服务名称', operate: 'LIKE', formatter: function (val) { + return '' + val + ''; + }}, + {field: 'code', title: '服务标识', operate: 'LIKE', formatter: function (val) { + return ' ' + val + '';
+ }},
+ {field: 'unit', title: '默认单位', operate: false, width: '90px'},
+ {field: 'can_be_main', title: '可作主服务', searchList: {"1": "是", "0": "否"}, formatter: function (val) {
+ return val == 1 ? '是' : '否';
+ }},
+ {field: 'can_be_addon', title: '可作增项服务', searchList: {"1": "是", "0": "否"}, formatter: function (val) {
+ return val == 1 ? '是' : '否';
+ }},
+ {field: 'support_fixed', title: '一口价属性', searchList: {"1": "支持一口价", "0": "不支持一口价"}, formatter: function (val) {
+ return val == 1 ? '支持一口价' : '不支持一口价 (仅报价)';
+ }},
+ {field: 'weigh', title: '排序', operate: false, sortable: true, width: '80px'},
+ {field: 'status', title: __('Status'), width: '90px', searchList: {"normal": __('Normal'), "hidden": __('Hidden')}, formatter: Table.api.formatter.status},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/settle.js b/server/public/assets/js/backend/settle.js
new file mode 100644
index 0000000..96a572e
--- /dev/null
+++ b/server/public/assets/js/backend/settle.js
@@ -0,0 +1,60 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'settle/index' + location.search,
+ add_url: 'settle/add',
+ edit_url: 'settle/edit',
+ del_url: 'settle/del',
+ multi_url: 'settle/multi',
+ import_url: 'settle/import',
+ table: 'settle',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'id',
+ fixedColumns: true,
+ fixedRightNumber: 1,
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'order.order_no', title: __('关联订单号'), operate: 'LIKE'},
+ {field: 'master.realname', title: __('结算师傅'), operate: 'LIKE'},
+ {field: 'order_amount', title: __('Order_amount'), operate: 'BETWEEN'},
+ {field: 'commission', title: __('Commission'), operate: 'BETWEEN'},
+ {field: 'settle_amount', title: __('Settle_amount'), operate: 'BETWEEN'},
+ {field: 'status', title: __('Status'), searchList: {"wait":__('Wait'),"settled":__('Settled'),"offline":__('Offline')}, formatter: Table.api.formatter.status},
+ {field: 'settle_time', title: __('Settle_time'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime},
+ {field: 'createtime', title: __('Createtime'), operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/skill_category.js b/server/public/assets/js/backend/skill_category.js
new file mode 100644
index 0000000..efed7ca
--- /dev/null
+++ b/server/public/assets/js/backend/skill_category.js
@@ -0,0 +1,86 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'skill_category/index' + location.search,
+ add_url: 'skill_category/add',
+ edit_url: 'skill_category/edit',
+ del_url: 'skill_category/del',
+ multi_url: 'skill_category/multi',
+ table: 'skill_category',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'weigh',
+ sortOrder: 'desc',
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'id', title: 'ID', sortable: true},
+ {field: 'name', title: '分类名称', operate: 'LIKE'},
+ {
+ field: 'icon',
+ title: '分类图标',
+ operate: false,
+ events: Table.api.events.image,
+ formatter: function (value, row, index) {
+ if (!value) return '-';
+ if (value.indexOf('/') !== -1 || value.indexOf('.') !== -1) {
+ return Table.api.formatter.image.call(this, value, row, index);
+ }
+ return '';
+ }
+ },
+ {
+ field: 'skills_count',
+ title: '下属技能数',
+ operate: false,
+ formatter: function (val, row) {
+ return '' + (val || 0) + ' 项技能';
+ }
+ },
+ {field: 'weigh', title: '排序权重', sortable: true},
+ {
+ field: 'status',
+ title: '启用状态',
+ searchList: {"normal": '正常启用', "hidden": '隐藏停用'},
+ formatter: Table.api.formatter.status
+ },
+ {field: 'createtime', title: '创建时间', operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime},
+ {
+ field: 'operate',
+ title: __('Operate'),
+ table: table,
+ events: Table.api.events.operate,
+ formatter: Table.api.formatter.operate
+ }
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/skill_dict.js b/server/public/assets/js/backend/skill_dict.js
new file mode 100644
index 0000000..b8eb75c
--- /dev/null
+++ b/server/public/assets/js/backend/skill_dict.js
@@ -0,0 +1,103 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'skill_dict/index' + location.search,
+ add_url: 'skill_dict/add',
+ edit_url: 'skill_dict/edit',
+ del_url: 'skill_dict/del',
+ multi_url: 'skill_dict/multi',
+ table: 'skill_dict',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'weigh',
+ sortOrder: 'desc',
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'id', title: 'ID', sortable: true},
+ {field: 'category_id', title: '分类ID', visible: false, operate: '='},
+ {
+ field: 'category',
+ title: '所属分类',
+ searchList: {
+ "安装维修": '安装维修',
+ "建筑装修": '建筑装修',
+ "物流运输": '物流运输',
+ "汽车服务": '汽车服务',
+ "家政服务": '家政服务',
+ "便民服务": '便民服务',
+ "其他技能": '其他技能'
+ },
+ formatter: function (val) {
+ var colorMap = {
+ "安装维修": 'primary',
+ "建筑装修": 'success',
+ "物流运输": 'info',
+ "汽车服务": 'warning',
+ "家政服务": 'danger',
+ "便民服务": 'default',
+ "其他技能": 'default'
+ };
+ return '' + val + '';
+ }
+ },
+ {field: 'name', title: '技能名称', operate: 'LIKE'},
+ {
+ field: 'source',
+ title: '字典来源',
+ searchList: {"system": '系统预置', "custom": '师傅自定义'},
+ formatter: function (val) {
+ if (val === 'custom') {
+ return ' 师傅自定义';
+ }
+ return ' 系统预置';
+ }
+ },
+ {field: 'master.realname', title: '提交师傅', operate: 'LIKE', formatter: function (val) { return val || '-'; }},
+ {field: 'weigh', title: '排序权重', sortable: true},
+ {
+ field: 'status',
+ title: '启用状态',
+ searchList: {"normal": '正常启用', "hidden": '隐藏停用'},
+ formatter: Table.api.formatter.status
+ },
+ {field: 'createtime', title: '添加时间', operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime},
+ {
+ field: 'operate',
+ title: __('Operate'),
+ table: table,
+ events: Table.api.events.operate,
+ formatter: Table.api.formatter.operate
+ }
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/user/group.js b/server/public/assets/js/backend/user/group.js
new file mode 100644
index 0000000..24af040
--- /dev/null
+++ b/server/public/assets/js/backend/user/group.js
@@ -0,0 +1,114 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'jstree'], function ($, undefined, Backend, Table, Form, undefined) {
+ //读取选中的条目
+ $.jstree.core.prototype.get_all_checked = function (full) {
+ var obj = this.get_selected(), i, j;
+ for (i = 0, j = obj.length; i < j; i++) {
+ obj = obj.concat(this.get_node(obj[i]).parents);
+ }
+ obj = $.grep(obj, function (v, i, a) {
+ return v != '#';
+ });
+ obj = obj.filter(function (itm, i, a) {
+ return i == a.indexOf(itm);
+ });
+ return full ? $.map(obj, $.proxy(function (i) {
+ return this.get_node(i);
+ }, this)) : obj;
+ };
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'user/group/index',
+ add_url: 'user/group/add',
+ edit_url: 'user/group/edit',
+ del_url: 'user/group/del',
+ multi_url: 'user/group/multi',
+ table: 'user_group',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'id',
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'name', title: __('Name')},
+ {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {field: 'updatetime', title: __('Updatetime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"), null, null, function () {
+ if ($("#treeview").length > 0) {
+ var r = $("#treeview").jstree("get_all_checked");
+ $("input[name='row[rules]']").val(r.join(','));
+ }
+ return true;
+ });
+ //渲染权限节点树
+ //销毁已有的节点树
+ $("#treeview").jstree("destroy");
+ Controller.api.rendertree(nodeData);
+ //全选和展开
+ $(document).on("click", "#checkall", function () {
+ $("#treeview").jstree($(this).prop("checked") ? "check_all" : "uncheck_all");
+ });
+ $(document).on("click", "#expandall", function () {
+ $("#treeview").jstree($(this).prop("checked") ? "open_all" : "close_all");
+ });
+ $("select[name='row[pid]']").trigger("change");
+ },
+ rendertree: function (content) {
+ $("#treeview")
+ .on('redraw.jstree', function (e) {
+ $(".layer-footer").attr("domrefresh", Math.random());
+ })
+ .jstree({
+ "themes": {"stripes": true},
+ "checkbox": {
+ "keep_selected_style": false,
+ },
+ "types": {
+ "root": {
+ "icon": "fa fa-folder-open",
+ },
+ "menu": {
+ "icon": "fa fa-folder-open",
+ },
+ "file": {
+ "icon": "fa fa-file-o",
+ }
+ },
+ "plugins": ["checkbox", "types"],
+ "core": {
+ 'check_callback': true,
+ "data": content
+ }
+ });
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/user/rule.js b/server/public/assets/js/backend/user/rule.js
new file mode 100644
index 0000000..b29f87c
--- /dev/null
+++ b/server/public/assets/js/backend/user/rule.js
@@ -0,0 +1,130 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'user/rule/index',
+ add_url: 'user/rule/add',
+ edit_url: 'user/rule/edit',
+ del_url: 'user/rule/del',
+ multi_url: 'user/rule/multi',
+ table: 'user_rule',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'weigh',
+ escape: true,
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'pid', title: __('Pid'), visible: false},
+ {field: 'title', title: __('Title'), align: 'left', formatter: Controller.api.formatter.title},
+ {field: 'name', title: __('Name'), align: 'left', formatter: Controller.api.formatter.name},
+ {field: 'remark', title: __('Remark')},
+ // {field: 'ismenu', title: __('Ismenu'), formatter: Table.api.formatter.toggle},
+ {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true, visible: false},
+ {field: 'updatetime', title: __('Updatetime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true, visible: false},
+ {field: 'weigh', title: __('Weigh')},
+ {field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ],
+ pagination: false,
+ search: false,
+ commonSearch: false,
+ rowAttributes: function (row, index) {
+ return row.pid == 0 ? {} : {style: "display:none"};
+ }
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+
+ table.on('post-body.bs.table', function (e, settings, json, xhr) {
+ //显示隐藏子节点
+ $(">tbody>tr[data-index] > td", this).on('click', "a.btn-node-sub", function () {
+ var status = $(this).data("shown") ? true : false;
+ $("a[data-pid='" + $(this).data("id") + "']").each(function () {
+ $(this).closest("tr").toggle(!status);
+ });
+ if (status) {
+ $("a[data-pid='" + $(this).data("id") + "']").trigger("collapse");
+ }
+ $(this).data("shown", !status);
+ $("i", this).toggleClass("fa-caret-down").toggleClass("fa-caret-right");
+ return false;
+ });
+ });
+
+ //隐藏子节点
+ $(document).on("collapse", ".btn-node-sub", function () {
+ if ($("i", this).length > 0) {
+ $("a[data-pid='" + $(this).data("id") + "']").trigger("collapse");
+ }
+ $("i", this).removeClass("fa-caret-down").addClass("fa-caret-right");
+ $(this).data("shown", false);
+ $(this).closest("tr").toggle(false);
+ });
+
+ //展开隐藏一级
+ $(document.body).on("click", ".btn-toggle", function (e) {
+ $("a[data-id][data-pid][data-pid!=0].disabled").closest("tr").hide();
+ var that = this;
+ var show = $("i", that).hasClass("fa-chevron-down");
+ $("i", that).toggleClass("fa-chevron-down", !show).toggleClass("fa-chevron-up", show);
+ $("a[data-id][data-pid][data-pid!=0]").not('.disabled').closest("tr").toggle(show);
+ $(".btn-node-sub[data-pid=0]").data("shown", show);
+ });
+
+ //展开隐藏全部
+ $(document.body).on("click", ".btn-toggle-all", function (e) {
+ var that = this;
+ var show = $("i", that).hasClass("fa-plus");
+ $("i", that).toggleClass("fa-plus", !show).toggleClass("fa-minus", show);
+ $(".btn-node-sub:not([data-pid=0])").closest("tr").toggle(show);
+ $(".btn-node-sub").data("shown", show);
+ $(".btn-node-sub > i").toggleClass("fa-caret-down", show).toggleClass("fa-caret-right", !show);
+ });
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ formatter: {
+ title: function (value, row, index) {
+ value = value.toString().replace(/(&|&)nbsp;/g, ' ');
+ var caret = row.haschild == 1 || row.ismenu == 1 ? '' : '';
+ value = value.indexOf(" ") > -1 ? value.replace(/(.*) /, "$1" + caret) : caret + value;
+
+ value = !row.ismenu || row.status == 'hidden' ? "" + value + "" : value;
+ return '' + value + '';
+ },
+ name: function (value, row, index) {
+ return !row.ismenu || row.status == 'hidden' ? "" + value + "" : value;
+ },
+ },
+ bindevent: function () {
+ $(document).on('click', "input[name='row[ismenu]']", function () {
+ var name = $("input[name='row[name]']");
+ name.prop("placeholder", $(this).val() == 1 ? name.data("placeholder-menu") : name.data("placeholder-node"));
+ });
+ $("input[name='row[ismenu]']:checked").trigger("click");
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/user/user.js b/server/public/assets/js/backend/user/user.js
new file mode 100644
index 0000000..861f2a5
--- /dev/null
+++ b/server/public/assets/js/backend/user/user.js
@@ -0,0 +1,60 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'user/user/index',
+ add_url: 'user/user/add',
+ edit_url: 'user/user/edit',
+ del_url: 'user/user/del',
+ multi_url: 'user/user/multi',
+ table: 'user',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'user.id',
+ fixedColumns: true,
+ fixedRightNumber: 1,
+ columns: [
+ [
+ {checkbox: true},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'group.name', title: __('Group')},
+ {field: 'username', title: __('Username'), operate: 'LIKE'},
+ {field: 'nickname', title: __('Nickname'), operate: 'LIKE'},
+ {field: 'mobile', title: __('Mobile'), operate: 'LIKE'},
+ {field: 'avatar', title: __('Avatar'), events: Table.api.events.image, formatter: Table.api.formatter.image, operate: false},
+ {field: 'score', title: __('Score'), operate: 'BETWEEN', sortable: true},
+ {field: 'jointime', title: __('Jointime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {field: 'logintime', title: __('Logintime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {field: 'status', title: __('Status'), formatter: Table.api.formatter.status, searchList: {normal: __('Normal'), hidden: __('Hidden')}},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
\ No newline at end of file
diff --git a/server/public/assets/js/backend/wechat/autoreply.js b/server/public/assets/js/backend/wechat/autoreply.js
new file mode 100644
index 0000000..036bb2d
--- /dev/null
+++ b/server/public/assets/js/backend/wechat/autoreply.js
@@ -0,0 +1,78 @@
+define(['jquery', 'bootstrap', 'backend', 'form', 'table'], function ($, undefined, Backend, Form, Table) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'wechat/autoreply/index',
+ add_url: 'wechat/autoreply/add',
+ edit_url: 'wechat/autoreply/edit',
+ del_url: 'wechat/autoreply/del',
+ multi_url: 'wechat/autoreply/multi',
+ table: 'wechat_autoreply',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ sortName: 'weigh DESC,id DESC',
+ columns: [
+ [
+ {field: 'state', checkbox: true,},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'title', title: __('Title')},
+ {field: 'text', title: __('Text')},
+ {field: 'eventkey', title: __('Event key')},
+ {field: 'remark', title: __('Remark')},
+ {field: 'createtime', title: __('Create time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {field: 'updatetime', title: __('Update time'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {field: 'status', title: __('Status'), formatter: Table.api.formatter.status},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+
+ var refreshkey = function (data) {
+ $("input[name='row[eventkey]']").val(data.eventkey).trigger("change");
+ Layer.closeAll();
+ var keytitle = data.title;
+ var cont = $(".clickbox .create-click:first");
+ $(".keytitle", cont).remove();
+ if (keytitle) {
+ cont.append('' + __('Event key') + ':' + keytitle + ' ');
+ }
+ };
+ $(document).on('click', "#select-resources", function () {
+ var key = $("input[name='row[eventkey]']").val();
+ parent.Backend.api.open($(this).attr("href") + "?key=" + key, __('Select'), {callback: refreshkey});
+ return false;
+ });
+
+ $(document).on('click', "#add-resources", function () {
+ parent.Backend.api.open($(this).attr("href") + "?key=", __('Add'), {callback: refreshkey});
+ return false;
+ });
+ }
+ }
+
+ };
+ return Controller;
+});
\ No newline at end of file
diff --git a/server/public/assets/js/backend/wechat/config.js b/server/public/assets/js/backend/wechat/config.js
new file mode 100644
index 0000000..471d512
--- /dev/null
+++ b/server/public/assets/js/backend/wechat/config.js
@@ -0,0 +1,54 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置
+ Table.api.init({
+ extend: {
+ index_url: 'wechat/config/index',
+ add_url: 'wechat/config/add',
+ edit_url: 'wechat/config/edit',
+ del_url: 'wechat/config/del',
+ multi_url: 'wechat/config/multi',
+ table: 'wechat_config',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'id',
+ columns: [
+ [
+ {field: 'state', checkbox: true},
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'name', title: __('Name')},
+ {field: 'title', title: __('Title')},
+ {field: 'createtime', title: __('Createtime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {field: 'updatetime', title: __('Updatetime'), formatter: Table.api.formatter.datetime, operate: 'RANGE', addclass: 'datetimerange', sortable: true},
+ {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ add: function () {
+ Controller.api.bindevent();
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ $("input[name='row[type]']:checked").trigger("click");
+ }
+ }
+ };
+ return Controller;
+});
\ No newline at end of file
diff --git a/server/public/assets/js/backend/wechat/menu.js b/server/public/assets/js/backend/wechat/menu.js
new file mode 100644
index 0000000..7270330
--- /dev/null
+++ b/server/public/assets/js/backend/wechat/menu.js
@@ -0,0 +1,261 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'sortable'], function ($, undefined, Backend, Table, Form, Sortable) {
+ var Controller = {
+ index: function () {
+ var typeList = [
+ {name: "click", title: "发送消息"},
+ {name: "view", title: "跳转网页"},
+ {name: "miniprogram", title: "跳转小程序"},
+ {name: "scancode_push", title: "扫码推"},
+ {name: "scancode_waitmsg", title: "扫码推提示框"},
+ {name: "pic_sysphoto", title: "拍照发图"},
+ {name: "pic_photo_or_album", title: "拍照相册发图"},
+ {name: "pic_weixin", title: "相册发图"},
+ {name: "location_select", title: "地理位置选择"},
+ ];
+ String.prototype.subByte = function (start, bytes) {
+ for (var i = start; bytes > 0; i++) {
+ var code = this.charCodeAt(i);
+ bytes -= code < 256 ? 1 : 2;
+ }
+ return this.slice(start, i + bytes)
+ };
+
+ //初始化菜单
+ $("#menu-list").prepend(Template("menutpl", {menu: menu}));
+
+ //拖动排序
+ new Sortable($("#menu-list")[0], {
+ draggable: 'li.menu-item',
+ onEnd: function () {
+ updateChangeMenu();
+ }
+ });
+
+ //子菜单拖动排序
+ $(".sub-menu-list").each(function () {
+ new Sortable(this, {
+ draggable: 'li.sub-menu-item',
+ onEnd: function () {
+ updateChangeMenu();
+ }
+ });
+ });
+
+ //添加主菜单
+ $(document).on('click', '#add-item', function () {
+ var menu_item_total = $(".menu-item").length;
+ if (menu_item_total < 3) {
+ var item = '本地菜单将会被覆盖!!!", {icon: 3}, function () { + $.post("wechat/menu/remote", {}, function (ret) { + var msg = ret.hasOwnProperty("msg") && ret.msg != "" ? ret.msg : ""; + if (ret.code == 1) { + Layer.closeAll(); + Layer.alert('远程菜单覆盖成功!点击确定刷新页面!', {icon: 1}, function () { + location.reload(); + }); + } else { + Backend.api.toastr.error(msg ? msg : __('Operation failed')); + } + }, 'json'); + }); + return false; + }); + + //点击同步 + $(document).on('click', "#menuSyn", function () { + $.post("wechat/menu/sync", {}, function (ret) { + var msg = ret.hasOwnProperty("msg") && ret.msg != "" ? ret.msg : ""; + if (ret.code == 1) { + Backend.api.toastr.success('菜单同步更新成功,生效时间请查看微信官网说明,或你重新关注公众号!'); + } else { + Backend.api.toastr.error(msg ? msg : __('Operation failed')); + } + }, 'json'); + }); + + //刷新资源 + var refreshkey = function (data) { + responselist[data.eventkey] = data.title; + $("input[name=key]").val(data.eventkey).trigger("change"); + $("#menu-list li.current").trigger("click"); + Layer.closeAll(); + }; + + //选择资源 + $(document).on('click', "#select-resources", function () { + var key = $("#key").val(); + Backend.api.open($(this).attr("href") + "?key=" + key, __('Select'), { + callback: refreshkey + }); + return false; + }); + + //添加资源 + $(document).on('click', "#add-resources", function () { + Backend.api.open($(this).attr("href") + "?key=" + key, __('Add'), { + callback: refreshkey + }); + return false; + }); + + $("#menu-list li.menu-item:first").trigger("click"); + }, + add: function () { + Form.api.bindevent($("form[role=form]")); + }, + edit: function () { + Form.api.bindevent($("form[role=form]")); + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/wechat/response.js b/server/public/assets/js/backend/wechat/response.js new file mode 100644 index 0000000..dbc6f4e --- /dev/null +++ b/server/public/assets/js/backend/wechat/response.js @@ -0,0 +1,157 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'adminlte'], function ($, undefined, Backend, Table, Form, Adminlte) { + + var Controller = { + index: function () { + // 初始化表格参数配置 + Table.api.init({ + extend: { + index_url: 'wechat/response/index', + add_url: 'wechat/response/add', + edit_url: 'wechat/response/edit', + del_url: 'wechat/response/del', + multi_url: 'wechat/response/multi', + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + sortName: 'id', + columns: [ + [ + {field: 'state', checkbox: true,}, + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'type', title: __('Type')}, + {field: 'title', title: __('Resource title')}, + {field: 'eventkey', title: __('Event key')}, + {field: 'status', title: __('Status'), formatter: Table.api.formatter.status, operate: false}, + {field: 'operate', title: __('Operate'), table: table, events: Table.api.events.operate, formatter: Table.api.formatter.operate} + ] + ] + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + }, + select: function () { + // 初始化表格参数配置 + Table.api.init({ + extend: { + index_url: 'wechat/response/index', + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + sortName: 'id', + columns: [ + [ + {field: 'state', checkbox: true,}, + {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'}, + {field: 'type', title: __('Type')}, + {field: 'title', title: __('Title')}, + {field: 'eventkey', title: __('Event key')}, + {field: 'status', title: __('Status'), formatter: Table.api.formatter.status, operate: false}, + { + field: 'operate', title: __('Operate'), events: { + 'click .btn-chooseone': function (e, value, row, index) { + Fast.api.close(row); + }, + }, formatter: function () { + return ' ' + __('Choose') + ''; + } + } + ] + ] + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + }, + add: function () { + Form.api.bindevent($("form[role=form]"), function (data) { + Fast.api.close(data); + }); + Controller.api.bindevent(); + }, + edit: function () { + Form.api.bindevent($("form[role=form]")); + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + var getAppFileds = function (id) { + var app = apps[id]; + var appConfig = app['config']; + var str = ''; + for (var i in appConfig) { + var type = appConfig[i]['type']; + var field = appConfig[i]['field']; + var caption = appConfig[i]['caption']; + var defaultvalue = typeof appConfig[i]['defaultvalue'] != 'undefined' ? appConfig[i]['defaultvalue'] : ''; + if (type == 'text' || type == 'textarea') { + if (type == 'textarea') { + str += ' ' + html + ' ');
+ $("form[role='form']").field("row[content][content]", datas.content);
+ } else if (type == 'app') {
+ $("#expand").html(' ');
+ $("form[role='form']").field("row[content][app]", datas.app);
+ $("#app").trigger('change');
+ }
+ });
+ $("input[name='row[type]']:checked").trigger("change");
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/server/public/assets/js/backend/withdraw.js b/server/public/assets/js/backend/withdraw.js
new file mode 100644
index 0000000..d498393
--- /dev/null
+++ b/server/public/assets/js/backend/withdraw.js
@@ -0,0 +1,60 @@
+define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) {
+
+ var Controller = {
+ index: function () {
+ // 初始化表格参数配置(提现单仅师傅端发起,后台只提供审核)
+ Table.api.init({
+ extend: {
+ index_url: 'withdraw/index' + location.search,
+ edit_url: 'withdraw/edit',
+ table: 'withdraw',
+ }
+ });
+
+ var table = $("#table");
+
+ // 初始化表格
+ table.bootstrapTable({
+ url: $.fn.bootstrapTable.defaults.extend.index_url,
+ pk: 'id',
+ sortName: 'id',
+ sortOrder: 'desc',
+ fixedColumns: true,
+ fixedRightNumber: 1,
+ columns: [
+ [
+ {field: 'index', title: '序号', operate: false, formatter: Table.api.formatter.index, width: '60px'},
+ {field: 'master.realname', title: '提现师傅', operate: 'LIKE'},
+ {field: 'master.mobile', title: '师傅手机号', operate: 'LIKE'},
+ {field: 'amount', title: '申请金额', operate: 'BETWEEN'},
+ {field: 'fee', title: '手续费', operate: 'BETWEEN'},
+ {field: 'arrive_amount', title: '到账金额', operate: 'BETWEEN'},
+ {field: 'channel', title: '结算渠道', searchList: {"wechat":__('Wechat'),"bank":__('Bank')}, formatter: Table.api.formatter.normal},
+ {field: 'account', title: '收款账号', operate: 'LIKE'},
+ {field: 'status', title: '审核状态', searchList: {"wait":__('Wait'),"pass":__('Pass'),"reject":__('Reject'),"done":__('Done')}, formatter: Table.api.formatter.status},
+ {field: 'audit_msg', title: '审核批注', operate: 'LIKE', class: 'autocontent', formatter: Table.api.formatter.content},
+ {field: 'createtime', title: '申请时间', operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime, width: '150px'},
+ {field: 'updatetime', title: '处理时间', operate: 'RANGE', addclass: 'datetimerange', autocomplete: false, formatter: Table.api.formatter.datetime, width: '150px'},
+ {field: 'operate', title: '审核操作', table: table, events: Table.api.events.operate,
+ buttons: [
+ {name: 'edit', text: '审核', title: '财务审核', icon: '', classname: 'btn btn-xs btn-success btn-editone'}
+ ],
+ formatter: Table.api.formatter.operate}
+ ]
+ ]
+ });
+
+ // 为表格绑定事件
+ Table.api.bindevent(table);
+ },
+ edit: function () {
+ Controller.api.bindevent();
+ },
+ api: {
+ bindevent: function () {
+ Form.api.bindevent($("form[role=form]"));
+ }
+ }
+ };
+ return Controller;
+});
diff --git a/yjyf-app/components/GoodsPickerDrawer.vue b/yjyf-app/components/GoodsPickerDrawer.vue
index a1c0975..f68ce9a 100644
--- a/yjyf-app/components/GoodsPickerDrawer.vue
+++ b/yjyf-app/components/GoodsPickerDrawer.vue
@@ -224,13 +224,18 @@
const goods = currentGoods.value;
if (!goods) return;
const gBase = Number(goods.base_price) || 0;
- const sBase = Number(spec.base_price) || gBase;
- const ratio = gBase > 0 && sBase > 0 ? sBase / gBase : 1;
- const scaledTypes = (goods.service_types || []).map(st => ({
- ...st,
- price: st.price > 0 ? Math.round(Number(st.price) * ratio) : 0
- }));
- const defType = scaledTypes.find(st => st.is_default) || scaledTypes[0] || { code: 'install', name: '安装' };
+ const hasCustomSpecPrice = Number(spec.base_price) > 0;
+ const sBase = hasCustomSpecPrice ? Number(spec.base_price) : gBase;
+ const rawTypes = goods.service_types || [];
+ const hasExplicitDefault = rawTypes.some(x => Number(x.is_default) === 1);
+ const resolvedTypes = rawTypes.map((st, idx) => {
+ const isDef = Number(st.is_default) === 1 || (!hasExplicitDefault && idx === 0);
+ return {
+ ...st,
+ price: (isDef && hasCustomSpecPrice) ? sBase : (Number(st.price) || 0)
+ };
+ });
+ const defType = resolvedTypes.find(st => Number(st.is_default) === 1) || resolvedTypes[0] || { code: 'install', name: '安装', price: sBase };
emit('select', {
service_id: goods.id,
item_name: goods.name,
@@ -239,10 +244,10 @@
cat_id: goods.cat_id || props.catId,
cat_name: props.catName || '',
unit: goods.unit || '件',
- base_price: sBase,
+ base_price: Number(defType.price) || sBase,
price_type: goods.price_type || 'fixed',
description: spec.description || goods.description || '',
- service_types: scaledTypes,
+ service_types: resolvedTypes,
addons: goods.addons || [],
default_service_type: defType.code,
default_service_name: defType.name
diff --git a/yjyf-app/manifest.json b/yjyf-app/manifest.json
index fa99eb3..4df26a9 100644
--- a/yjyf-app/manifest.json
+++ b/yjyf-app/manifest.json
@@ -1,6 +1,6 @@
{
"name" : "优匠优服",
- "appid" : "__UNI__D5A0D20",
+ "appid" : "__UNI__7BBB98A",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
diff --git a/yjyf-app/pages/order/quick.vue b/yjyf-app/pages/order/quick.vue
index 2d36319..dff9f8a 100644
--- a/yjyf-app/pages/order/quick.vue
+++ b/yjyf-app/pages/order/quick.vue
@@ -892,12 +892,10 @@
const showModeConflictModal = ref(false);
const conflictGoodsNames = ref('');
- // 判断某个服务项目是否支持“一口价”模式(对标视频 00:14 安装/00:31 维修/00:52 拆旧支持一口价;01:00 补漆修复/测量/送货到家等转报价模式)
+ // 判断某个服务项目是否支持“一口价”模式(只要后台配置了 >0 的一口价且非显式禁用,即支持一口价下单)
function isServiceTypeFixedSupported(code, price, supportFixedFlag) {
if (supportFixedFlag === 0) return false;
- if (supportFixedFlag === 1 && Number(price) > 0) return true;
- const fixedCodes = ['install', 'repair', 'remove'];
- return fixedCodes.includes(code) && Number(price) > 0;
+ return Number(price) > 0;
}
function handleModeSelect(mode) {
@@ -972,32 +970,26 @@
uni.navigateTo({ url: '/pages/category/category' });
}
- // 商品信息列表(支持多组)
+ // 商品信息列表(支持多组,数据由真实接口返回填充)
const goodsList = ref([
{
service_id: 0,
item_name: '',
+ spec_id: 0,
spec_name: '',
cat_id: 0,
cat_name: '',
unit: '个',
base_price: 0,
num: 1,
- service_type: 'install',
- service_type_name: '安装',
- service_types: [
- { code: 'install', name: '安装', price: 60 },
- { code: 'repair', name: '维修', price: 40 },
- { code: 'remove', name: '拆旧', price: 30 }
- ],
+ service_type: '',
+ service_type_name: '',
+ service_types: [],
remark: '',
images: [],
videos: [],
envImages: [],
- extras: [
- { key: 'tear', name: '拆旧', price: 50, unit: '份', num: 0 },
- { key: 'cut', name: '切割', price: 20, unit: '份', num: 0 }
- ]
+ extras: []
}
]);
@@ -1133,18 +1125,13 @@
num: 0
}));
- const serviceTypes = spec.service_types && spec.service_types.length ? spec.service_types : [
- { code: 'install', name: '安装', price: Number(spec.base_price) || 60, is_default: 1 },
- { code: 'repair', name: '维修', price: Math.round((Number(spec.base_price) || 60) * 0.7), is_default: 0 },
- { code: 'remove', name: '拆旧', price: Math.round((Number(spec.base_price) || 60) * 0.5), is_default: 0 },
- { code: 'deliver', name: '送货到家', price: 50, is_default: 0 },
- { code: 'deliver_install', name: '送货到家并安装', price: Math.round((Number(spec.base_price) || 60) * 1.3), is_default: 0 }
- ];
- const defType = serviceTypes.find(st => st.is_default) || serviceTypes[0];
+ const serviceTypes = spec.service_types && spec.service_types.length ? spec.service_types : [];
+ const defType = serviceTypes.find(st => st.is_default) || serviceTypes[0] || { code: spec.default_service_type || 'install', name: spec.default_service_name || '安装', price: Number(spec.base_price) || 0 };
const newGroup = {
service_id: spec.service_id,
item_name: spec.item_name,
+ spec_id: spec.spec_id || 0,
spec_name: spec.spec_name,
cat_id: spec.cat_id || drawerCatId.value,
cat_name: spec.cat_name || drawerCatName.value,
@@ -1884,28 +1871,34 @@
price: Number(ad.price) || 0,
num: 0
}));
- const serviceTypes = firstS.service_types && firstS.service_types.length ? firstS.service_types : [
- { code: 'install', name: '安装', price: Number(firstS.base_price) || 60, is_default: 1 },
- { code: 'repair', name: '维修', price: Math.round((Number(firstS.base_price) || 60) * 0.7), is_default: 0 },
- { code: 'remove', name: '拆旧', price: Math.round((Number(firstS.base_price) || 60) * 0.5), is_default: 0 },
- { code: 'deliver', name: '送货到家', price: 50, is_default: 0 },
- { code: 'deliver_install', name: '送货到家并安装', price: Math.round((Number(firstS.base_price) || 60) * 1.3), is_default: 0 }
- ];
- const defType = serviceTypes.find(st => st.is_default) || serviceTypes[0];
+ const serviceTypes = firstS.service_types && firstS.service_types.length ? firstS.service_types : [];
+ const defType = serviceTypes.find(st => st.is_default) || serviceTypes[0] || { code: 'install', name: '安装', price: Number(firstS.base_price) || 0 };
+
+ const firstSpec = (firstS.specs && firstS.specs.length) ? firstS.specs[0] : null;
+ const hasCustomSpecPrice = firstSpec && Number(firstSpec.base_price) > 0;
+ const effectivePrice = hasCustomSpecPrice ? Number(firstSpec.base_price) : (Number(firstS.base_price) || Number(defType.price) || 0);
+ const resolvedServiceTypes = serviceTypes.map((st, idx) => {
+ const isDef = Number(st.is_default) === 1 || (idx === 0 && !serviceTypes.some(x => Number(x.is_default) === 1));
+ return {
+ ...st,
+ price: (isDef && hasCustomSpecPrice) ? effectivePrice : (Number(st.price) || 0)
+ };
+ });
goodsList.value = [
{
service_id: firstS.id,
item_name: firstS.name,
- spec_name: firstS.name,
+ spec_id: firstSpec ? firstSpec.id : 0,
+ spec_name: firstSpec ? firstSpec.name : firstS.name,
cat_id: matchedCat.id,
cat_name: matchedCat.name,
unit: firstS.unit || '个',
- base_price: Number(firstS.base_price) || Number(defType.price) || 0,
+ base_price: effectivePrice,
num: 1,
service_type: defType.code,
service_type_name: defType.name,
- service_types: serviceTypes,
+ service_types: resolvedServiceTypes,
remark: '',
images: [],
videos: [],
|