From 9e83a869e16c7f65103cedbcb43ee4be19eab207 Mon Sep 17 00:00:00 2001 From: MiAn Date: Tue, 10 Nov 2026 16:47:41 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20=E7=A7=BB=E9=99=A4=E5=BA=9F=E5=BC=83?= =?UTF-8?q?=E7=9A=84=E5=A2=9E=E9=A1=B9=E6=9C=8D=E5=8A=A1=E6=A8=A1=E5=9D=97?= =?UTF-8?q?=E5=B9=B6=E5=8D=87=E7=BA=A7=E6=9C=8D=E5=8A=A1=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 Addon 相关控制器、模型、语言包和视图文件 - 新增服务类型支持一口价属性配置 - 新增系统监控、用户资料、订单评价等后端管理页面 - 新增数据库表结构及索引优化脚本 - 更新应用版本号至 1.0.44 - 更新小程序应用标识 - 完善 .gitignore 配置 --- .gitignore | 5 +- db/schema_phase15_menu_reorganize.sql | 191 ++++ db/schema_phase15_service_pricing.sql | 39 + scripts/reorganize_menus.php | 110 +-- .../application/admin/controller/Service.php | 84 +- .../admin/controller/service/Addon.php | 51 -- .../admin/lang/zh-cn/service/addon.php | 12 - server/application/admin/model/Service.php | 22 +- .../application/admin/model/service/Addon.php | 41 - .../application/admin/view/service/add.html | 34 +- .../admin/view/service/addon/add.html | 79 -- .../admin/view/service/addon/edit.html | 79 -- .../admin/view/service/addon/index.html | 24 - .../application/admin/view/service/edit.html | 44 +- .../admin/view/service/type/add.html | 13 +- .../admin/view/service/type/edit.html | 13 +- server/application/api/controller/Home.php | 607 ++----------- server/application/extra/site.php | 2 +- server/public/assets/js/backend/addon.js | 847 ++++++++++++++++++ server/public/assets/js/backend/appeal.js | 59 ++ server/public/assets/js/backend/auth/admin.js | 62 ++ .../public/assets/js/backend/auth/adminlog.js | 64 ++ server/public/assets/js/backend/auth/group.js | 160 ++++ server/public/assets/js/backend/auth/rule.js | 221 +++++ server/public/assets/js/backend/banner.js | 59 ++ server/public/assets/js/backend/category.js | 93 ++ server/public/assets/js/backend/coupon.js | 60 ++ server/public/assets/js/backend/dashboard.js | 212 +++++ .../assets/js/backend/general/attachment.js | 323 +++++++ .../assets/js/backend/general/banner.js | 115 +++ .../assets/js/backend/general/config.js | 140 +++ .../assets/js/backend/general/dashboard.js | 65 ++ .../assets/js/backend/general/profile.js | 57 ++ .../assets/js/backend/general/system.js | 9 + server/public/assets/js/backend/home.js | 224 +++++ server/public/assets/js/backend/index.js | 441 +++++++++ server/public/assets/js/backend/invoice.js | 60 ++ server/public/assets/js/backend/master.js | 107 +++ .../public/assets/js/backend/master_skill.js | 79 ++ server/public/assets/js/backend/merchant.js | 79 ++ server/public/assets/js/backend/message.js | 56 ++ server/public/assets/js/backend/order.js | 80 ++ server/public/assets/js/backend/review.js | 59 ++ server/public/assets/js/backend/service.js | 137 +++ .../public/assets/js/backend/service/cat.js | 94 ++ .../public/assets/js/backend/service/type.js | 67 ++ server/public/assets/js/backend/settle.js | 60 ++ .../assets/js/backend/skill_category.js | 86 ++ server/public/assets/js/backend/skill_dict.js | 103 +++ server/public/assets/js/backend/user/group.js | 114 +++ server/public/assets/js/backend/user/rule.js | 130 +++ server/public/assets/js/backend/user/user.js | 60 ++ .../assets/js/backend/wechat/autoreply.js | 78 ++ .../public/assets/js/backend/wechat/config.js | 54 ++ .../public/assets/js/backend/wechat/menu.js | 261 ++++++ .../assets/js/backend/wechat/response.js | 157 ++++ server/public/assets/js/backend/withdraw.js | 60 ++ yjyf-app/components/GoodsPickerDrawer.vue | 23 +- yjyf-app/manifest.json | 2 +- yjyf-app/pages/order/quick.vue | 63 +- 60 files changed, 5710 insertions(+), 1020 deletions(-) create mode 100644 db/schema_phase15_menu_reorganize.sql create mode 100644 db/schema_phase15_service_pricing.sql delete mode 100644 server/application/admin/controller/service/Addon.php delete mode 100644 server/application/admin/lang/zh-cn/service/addon.php delete mode 100644 server/application/admin/model/service/Addon.php delete mode 100644 server/application/admin/view/service/addon/add.html delete mode 100644 server/application/admin/view/service/addon/edit.html delete mode 100644 server/application/admin/view/service/addon/index.html create mode 100644 server/public/assets/js/backend/addon.js create mode 100644 server/public/assets/js/backend/appeal.js create mode 100644 server/public/assets/js/backend/auth/admin.js create mode 100644 server/public/assets/js/backend/auth/adminlog.js create mode 100644 server/public/assets/js/backend/auth/group.js create mode 100644 server/public/assets/js/backend/auth/rule.js create mode 100644 server/public/assets/js/backend/banner.js create mode 100644 server/public/assets/js/backend/category.js create mode 100644 server/public/assets/js/backend/coupon.js create mode 100644 server/public/assets/js/backend/dashboard.js create mode 100644 server/public/assets/js/backend/general/attachment.js create mode 100644 server/public/assets/js/backend/general/banner.js create mode 100644 server/public/assets/js/backend/general/config.js create mode 100644 server/public/assets/js/backend/general/dashboard.js create mode 100644 server/public/assets/js/backend/general/profile.js create mode 100644 server/public/assets/js/backend/general/system.js create mode 100644 server/public/assets/js/backend/home.js create mode 100644 server/public/assets/js/backend/index.js create mode 100644 server/public/assets/js/backend/invoice.js create mode 100644 server/public/assets/js/backend/master.js create mode 100644 server/public/assets/js/backend/master_skill.js create mode 100644 server/public/assets/js/backend/merchant.js create mode 100644 server/public/assets/js/backend/message.js create mode 100644 server/public/assets/js/backend/order.js create mode 100644 server/public/assets/js/backend/review.js create mode 100644 server/public/assets/js/backend/service.js create mode 100644 server/public/assets/js/backend/service/cat.js create mode 100644 server/public/assets/js/backend/service/type.js create mode 100644 server/public/assets/js/backend/settle.js create mode 100644 server/public/assets/js/backend/skill_category.js create mode 100644 server/public/assets/js/backend/skill_dict.js create mode 100644 server/public/assets/js/backend/user/group.js create mode 100644 server/public/assets/js/backend/user/rule.js create mode 100644 server/public/assets/js/backend/user/user.js create mode 100644 server/public/assets/js/backend/wechat/autoreply.js create mode 100644 server/public/assets/js/backend/wechat/config.js create mode 100644 server/public/assets/js/backend/wechat/menu.js create mode 100644 server/public/assets/js/backend/wechat/response.js create mode 100644 server/public/assets/js/backend/withdraw.js 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 @@
-
商品细分型号与款式规格
+
+ 商品细分型号与款式规格 + (常规规格留空价格即可,自动沿用商品默认服务价;仅施工难度特殊的规格需填专属一口价) +
- + + @@ -235,7 +239,7 @@ $addonPrice = (float)$tp['default_addon_price'] > 0 ? (float)$tp['default_addon_price'] : 30.00; $rowEnabled = ($isMainChecked || $isAddonChecked); ?> - + ' + '' + '' + + '' + '' + '' + ''; diff --git a/server/application/admin/view/service/addon/add.html b/server/application/admin/view/service/addon/add.html deleted file mode 100644 index c6a2e31..0000000 --- a/server/application/admin/view/service/addon/add.html +++ /dev/null @@ -1,79 +0,0 @@ - - - -
-
-

增项施工服务定义

-
-
-
- -
- {:build_select('row[cat_id]', $catList, 0, ['class'=>'form-control selectpicker', 'data-rule'=>'required'])} -
-
-
- -
- -
-
-
- -
-
-
-
- 指导价 ¥ - -
-
-
-
- 计量单位 - -
-
-
-
-
-
-
- - -
-
-

排序与启用状态

-
-
-
- -
-
-
-
- 排序权重 - -
-
-
-
- {foreach name="statusList" item="vo"} - - {/foreach} -
-
-
-
-
-
-
- - - diff --git a/server/application/admin/view/service/addon/edit.html b/server/application/admin/view/service/addon/edit.html deleted file mode 100644 index 8cd51b5..0000000 --- a/server/application/admin/view/service/addon/edit.html +++ /dev/null @@ -1,79 +0,0 @@ - - - -
-
-

增项施工服务定义

-
-
-
- -
- {:build_select('row[cat_id]', $catList, $row['cat_id'], ['class'=>'form-control selectpicker', 'data-rule'=>'required'])} -
-
-
- -
- -
-
-
- -
-
-
-
- 指导价 ¥ - -
-
-
-
- 计量单位 - -
-
-
-
-
-
-
- - -
-
-

排序与启用状态

-
-
-
- -
-
-
-
- 排序权重 - -
-
-
-
- {foreach name="statusList" item="vo"} - - {/foreach} -
-
-
-
-
-
-
- - - diff --git a/server/application/admin/view/service/addon/index.html b/server/application/admin/view/service/addon/index.html deleted file mode 100644 index 6745585..0000000 --- a/server/application/admin/view/service/addon/index.html +++ /dev/null @@ -1,24 +0,0 @@ -
- {:build_heading()} - -
序号细分规格/款式名称细分规格/款式名称特殊规格一口价(选填) 规格说明或施工提示(选填) 操作
{$name} @@ -249,6 +253,7 @@
+ {if $mainSupportFixed == 1}
¥ @@ -257,6 +262,9 @@ + {else/} + 不支持一口价(下单时自动转师傅报价) + {/if}
未启用主服务
@@ -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 = '
' + seq + '
¥
-
-
- - - - - 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 @@
商品细分型号与款式规格 - (用户下单选择该商品时,二级联动展示的具体款式,无单独商品价格) + (常规规格留空价格即可,自动沿用商品默认服务价;仅施工难度特殊的规格需填专属一口价)
@@ -438,7 +438,8 @@ 序号 - 细分规格/款式名称 + 细分规格/款式名称 + 特殊规格一口价(选填) 规格说明或施工提示(选填) 操作 @@ -451,6 +452,12 @@ + +
+ ¥ + +
+ @@ -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/add.html b/server/application/admin/view/service/type/add.html index 9cd7c08..b7a4f81 100644 --- a/server/application/admin/view/service/type/add.html +++ b/server/application/admin/view/service/type/add.html @@ -31,20 +31,27 @@
-
+
-
+
- +
+
+ +
+ + +
+
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) { + $("
  • " + j.name + "
  • ").insertBefore($(".nav-category li:last")); + }); + } + if (typeof json.rows === 'undefined' && typeof json.code != 'undefined') { + switch_local(); + } + }); + table.on('load-error.bs.table', function (e, status, res) { + console.log(e, status, res); + switch_local(); + }); + table.on('post-body.bs.table', function (e, settings, json, xhr) { + var parenttable = table.closest('.bootstrap-table'); + var d = $(".fixed-table-toolbar", parenttable).find(".search input"); + d.off("keyup drop blur"); + d.on("keyup", function (e) { + if (e.keyCode == 13) { + var that = this; + var options = table.bootstrapTable('getOptions'); + var queryParams = options.queryParams; + options.pageNumber = 1; + options.queryParams = function (params) { + var params = queryParams(params); + params.search = $(that).val(); + return params; + }; + table.bootstrapTable('refresh', {}); + } + }); + }); + + Template.helper("Moment", Moment); + Template.helper("addons", Config['addons']); + + $("#faupload-addon").data("params", function (files, xhr) { + var userinfo = Controller.api.userinfo.get(); + return { + uid: userinfo ? userinfo.id : '', + token: userinfo ? userinfo.token : '', + version: Config.faversion, + force: (files[0].force || false) ? 1 : 0 + }; + }); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pageSize: 50, + queryParams: function (params) { + var userinfo = Controller.api.userinfo.get(); + $.extend(params, { + uid: userinfo ? userinfo.id : '', + token: userinfo ? userinfo.token : '', + domain: Config.domain, + version: Config.faversion, + sid: Controller.api.sid() + }); + return params; + }, + columns: [ + [ + {field: 'id', title: 'ID', operate: false, visible: false}, + { + field: 'home', + title: __('Index'), + width: '50px', + formatter: Controller.api.formatter.home + }, + {field: 'name', title: __('Name'), operate: false, visible: false, width: '120px'}, + { + field: 'title', + title: __('Title'), + operate: 'LIKE', + align: 'left', + formatter: Controller.api.formatter.title + }, + { + field: 'intro', + title: __('Intro'), + operate: 'LIKE', + align: 'left', + class: 'visible-lg', + formatter: Controller.api.formatter.intro + }, + { + field: 'author', + title: __('Author'), + operate: 'LIKE', + width: '100px', + formatter: Controller.api.formatter.author + }, + { + field: 'price', + title: __('Price'), + operate: 'LIKE', + width: '100px', + align: 'center', + formatter: Controller.api.formatter.price + }, + { + field: 'downloads', + title: __('Downloads'), + operate: 'LIKE', + width: '80px', + align: 'center', + formatter: Controller.api.formatter.downloads + }, + { + field: 'version', + title: __('Version'), + operate: 'LIKE', + width: '80px', + align: 'center', + formatter: Controller.api.formatter.version + }, + { + field: 'toggle', + title: __('Status'), + width: '80px', + formatter: Controller.api.formatter.toggle + }, + { + field: 'id', + title: __('Operate'), + table: table, + formatter: Controller.api.formatter.operate, + align: 'right', + cellStyle: function (value, row, index) { + return {css: {'min-width': '158px'}}; + } + }, + ] + ], + responseHandler: function (res) { + $.each(res.rows, function (i, j) { + j.addon = typeof Config.addons[j.name] != 'undefined' ? Config.addons[j.name] : null; + }); + return res; + }, + dataType: 'jsonp', + templateView: false, + clickToSelect: false, + search: true, + showColumns: false, + showToggle: false, + showExport: false, + showSearch: false, + commonSearch: true, + searchFormVisible: true, + searchFormTemplate: 'searchformtpl', + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + + // 离线安装 + require(['upload'], function (Upload) { + Upload.api.upload("#faupload-addon", function (data, ret, up, file) { + Config['addons'][data.addon.name] = data.addon; + var addon = data.addon; + var testdata = data.addon.testdata; + operate(data.addon.name, 'enable', false, function (data, ret) { + Layer.alert(__('Offline installed tips') + (testdata ? __('Testdata tips') : ""), { + btn: testdata ? [__('Import testdata'), __('Skip testdata')] : [__('OK')], + title: __('Warning'), + yes: function (index) { + if (testdata) { + Fast.api.ajax({ + url: 'addon/testdata', + data: { + name: addon.name, + version: addon.version, + faversion: Config.faversion + } + }, function (data, ret) { + Layer.close(index); + }); + } else { + Layer.close(index); + } + }, + icon: 1 + }); + }); + return false; + }, function (data, ret, up, file) { + if (ret.msg && ret.msg.match(/(login|登录)/g)) { + return Layer.alert(ret.msg, { + title: __('Warning'), + btn: [__('Login now')], + yes: function (index, layero) { + $(".btn-userinfo").trigger("click"); + } + }); + } else if (ret.code === -1) { + Layer.confirm(__('Upgrade tips', data.title), {title: __('Warmtips')}, function (index, layero) { + up.removeFile(file); + file.force = true; + up.uploadFile(file); + Layer.close(index); + }); + 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 (index) { + up.removeFile(file); + file.force = true; + up.uploadFile(file); + Layer.close(index); + } + }); + + } else { + Layer.alert(ret.msg, {title: __('Warning'), icon: 0}); + } + }); + + // 检测是否登录 + $(document).on("mousedown", "#faupload-addon", function (e) { + var userinfo = Controller.api.userinfo.get(); + var uid = userinfo ? userinfo.id : 0; + var uploadBtn = Upload.list['faupload-addon']; + + if (parseInt(uid) === 0) { + uploadBtn.disable(); + $(".btn-userinfo").trigger("click"); + return false; + } else { + if (uploadBtn.disabled) { + uploadBtn.enable(); + } + } + }); + }); + + // 查看插件首页 + $(document).on("click", ".btn-addonindex", function () { + if ($(this).attr("href") == 'javascript:;') { + Layer.msg(__('Not installed tips'), {icon: 7}); + } else if ($(this).closest(".operate").find("a.btn-enable").length > 0) { + Layer.msg(__('Not enabled tips'), {icon: 7}); + return false; + } + }); + + // 切换 + $(document).on("click", ".btn-switch", function () { + $(".btn-switch").removeClass("active"); + $(this).addClass("active"); + $("form.form-commonsearch input[name='type']").val($(this).data("type")); + var method = $(this).data("type") == 'local' ? 'hideColumn' : 'showColumn'; + table.bootstrapTable(method, 'price'); + table.bootstrapTable(method, 'downloads'); + table.bootstrapTable('refresh', {url: ($(this).data("url") ? $(this).data("url") : $.fn.bootstrapTable.defaults.extend.index_url), pageNumber: 1}); + return false; + }); + + // 切换分类 + $(document).on("click", ".nav-category li a", function () { + $(".nav-category li").removeClass("active"); + $(this).parent().addClass("active"); + $("form.form-commonsearch input[name='category_id']").val($(this).data("id")); + table.bootstrapTable('refresh', {url: $(this).data("url"), pageNumber: 1}); + return false; + }); + var tables = []; + $(document).on("click", "#droptables", function () { + if ($(this).prop("checked")) { + Fast.api.ajax({ + url: "addon/get_table_list", + async: false, + data: {name: $(this).data("name")} + }, function (data) { + tables = data.tables; + return false; + }); + var html; + html = tables.length > 0 ? '
    ' + __('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 = ''; + } else { + var iconurl = Controller.api.safeurl(Fast.api.fixurl("ajax/icon") + "?suffix=" + encodeURIComponent(row.imagetype || '')); + html = ''; + } + return '
    ' + html + '
    '; + }, + url: function (value, row, index) { + return '' + Controller.api.formatter.text(row.url) + ''; + }, + filename: function (value, row, index) { + if (row && typeof row.filename !== 'undefined') { + value = row.filename; + } + value = Controller.api.formatter.legacytext(value); + return '
    ' + Table.api.formatter.search.call(this, value, row, index) + '
    '; + }, + mimetype: function (value, row, index) { + return '
    ' + Controller.api.formatter.search.call(this, value, row, index) + '
    '; + }, + } + } + + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/general/banner.js b/server/public/assets/js/backend/general/banner.js new file mode 100644 index 0000000..c0a8a51 --- /dev/null +++ b/server/public/assets/js/backend/general/banner.js @@ -0,0 +1,115 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + // 初始化表格参数配置 + Table.api.init({ + extend: { + index_url: 'general/banner/index' + location.search, + add_url: 'general/banner/add', + edit_url: 'general/banner/edit', + del_url: 'general/banner/del', + multi_url: 'general/banner/multi', + table: 'banner', + } + }); + + var table = $("#table"); + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'weigh', + sortOrder: 'desc', + columns: [ + [ + {checkbox: true}, + { + field: 'index', + title: '序号', + width: '60px', + align: 'center', + operate: false, + formatter: function (value, row, index) { + var options = table.bootstrapTable('getOptions'); + var pageNumber = options.pageNumber || 1; + var pageSize = options.pageSize || 10; + return (pageNumber - 1) * pageSize + index + 1; + } + }, + { + field: 'image', + title: __('Image'), + events: Table.api.events.image, + formatter: Table.api.formatter.image + }, + {field: 'title', title: __('Title'), align: 'left'}, + { + field: 'url', + title: __('Url'), + align: 'left', + formatter: function (value, row, index) { + if (!value) { + return '无跳转'; + } + return '' + value + ''; + } + }, + + {field: 'weigh', title: __('Weigh'), sortable: true}, + { + field: 'status', + title: __('Status'), + searchList: {"normal": __('Status normal'), "hidden": __('Status hidden')}, + formatter: Table.api.formatter.status + }, + { + field: 'updatetime', + title: __('Updatetime'), + operate: 'RANGE', + addclass: 'datetimerange', + autocomplete: false, + formatter: Table.api.formatter.datetime + }, + { + field: 'operate', + title: __('Operate'), + table: table, + width: '180px', + events: Table.api.events.operate, + formatter: function (value, row, index) { + var html = []; + html.push(' 编辑 '); + html.push(' 预览 '); + html.push(' 删除'); + return html.join(''); + } + } + ] + ] + }); + + // 为表格绑定事件 + Table.api.bindevent(table); + }, + add: function () { + Controller.api.bindevent(); + }, + edit: function () { + Controller.api.bindevent(); + }, + api: { + bindevent: function () { + Form.api.bindevent($("form[role=form]")); + + // 快捷填充常用小程序/H5页面路由 + $(document).on('click', '.btn-route-fill', function () { + var url = $(this).data('url'); + $('#c-url').val(url); + }); + } + } + }; + return Controller; +}); diff --git a/server/public/assets/js/backend/general/config.js b/server/public/assets/js/backend/general/config.js new file mode 100644 index 0000000..c14e21b --- /dev/null +++ b/server/public/assets/js/backend/general/config.js @@ -0,0 +1,140 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form'], function ($, undefined, Backend, Table, Form) { + + var Controller = { + index: function () { + $("form.edit-form").data("validator-options", { + display: function (elem) { + return $(elem).closest('tr').find("td:first").text(); + } + }); + Form.api.bindevent($("form.edit-form")); + + //不可见的元素不验证 + $("form#add-form").data("validator-options", { + ignore: ':hidden', + rules: { + content: function () { + return ['radio', 'checkbox', 'select', 'selects'].indexOf($("#add-form select[name='row[type]']").val()) > -1; + }, + extend: function () { + return $("#add-form select[name='row[type]']").val() == 'custom'; + } + } + }); + Form.api.bindevent($("form#add-form"), function (ret) { + setTimeout(function () { + location.reload(); + }, 1500); + }); + + //渲染关联显示字段和存储字段 + var renderselect = function (id, data, defaultvalue) { + var html = []; + for (var i = 0; i < data.length; i++) { + html.push(""); + } + var select = $(id); + $(select).html(html.join("")); + select.trigger("change"); + if (select.data("selectpicker")) { + select.selectpicker('refresh'); + } + }; + //关联表切换 + $(document).on('change', "#c-selectpage-table", function (e, first) { + var that = this; + Fast.api.ajax({ + url: "general/config/get_fields_list", + data: {table: $(that).val()}, + }, function (data, ret) { + renderselect("#c-selectpage-primarykey", data.fieldList, first ? $("#c-selectpage-primarykey").data("value") : ''); + renderselect("#c-selectpage-field", data.fieldList, first ? $("#c-selectpage-field").data("value") : ''); + return false; + }); + return false; + }); + //如果编辑模式则渲染已知数据 + if (['selectpage', 'selectpages'].indexOf($("#c-type").val()) > -1) { + $("#c-selectpage-table").trigger("change", true); + } + + //切换类型时 + $(document).on("change", "#c-type", function () { + var value = $(this).val(); + $(".tf").addClass("hidden"); + $(".tf.tf-" + value).removeClass("hidden"); + if (["selectpage", "selectpages"].indexOf(value) > -1 && $("#c-selectpage-table option").length == 1) { + //异步加载表列表 + Fast.api.ajax({ + url: "general/config/get_table_list", + }, function (data, ret) { + renderselect("#c-selectpage-table", data.tableList); + return false; + }); + } + }); + + //切换显示隐藏变量字典列表 + $(document).on("change", "form#add-form select[name='row[type]']", function (e) { + $("#add-content-container").toggleClass("hide", ['select', 'selects', 'checkbox', 'radio'].indexOf($(this).val()) > -1 ? false : true); + }); + + //选择规则 + $(document).on("click", ".rulelist > li > a", function () { + var ruleArr = $("#rule").val() == '' ? [] : $("#rule").val().split(";"); + var rule = $(this).data("value"); + var index = ruleArr.indexOf(rule); + if (index > -1) { + ruleArr.splice(index, 1); + } else { + ruleArr.push(rule); + } + $("#rule").val(ruleArr.join(";")); + $(this).parent().toggleClass("active"); + }); + + //添加向发件人发送测试邮件按钮和方法 + $('input[name="row[mail_from]"]').parent().next().append('' + __('Send a test message') + ''); + $(document).on("click", ".testmail", function () { + var that = this; + Layer.prompt({title: __('Please input your email'), formType: 0}, function (value, index) { + Backend.api.ajax({ + url: "general/config/emailtest", + data: $(that).closest("form").serialize() + "&receiver=" + value + }); + }); + + }); + + //删除配置 + $(document).on("click", ".btn-delcfg", function () { + var that = this; + Layer.confirm(__('Are you sure you want to delete this item?'), { + icon: 3, + title: '提示' + }, function (index) { + Backend.api.ajax({ + url: "general/config/del", + data: {name: $(that).data("name")} + }, function () { + $(that).closest("tr").remove(); + Layer.close(index); + }); + }); + + }); + }, + 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/general/dashboard.js b/server/public/assets/js/backend/general/dashboard.js new file mode 100644 index 0000000..012e5ee --- /dev/null +++ b/server/public/assets/js/backend/general/dashboard.js @@ -0,0 +1,65 @@ +define(['jquery', 'bootstrap', 'backend', 'addtabs', 'table', 'echarts', 'echarts-theme'], function ($, undefined, Backend, Datatable, Table, Echarts) { + + var Controller = { + index: function () { + var myChart = Echarts.init(document.getElementById('echart'), 'walden'); + var option = { + title: { text: '', subtext: '' }, + color: ["#ff6a00"], + tooltip: { + trigger: 'axis', + formatter: '{b}
    注册用户数: {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 = ''; + var itemDom = $(item); + itemDom.insertBefore(this); + itemDom.trigger("click"); + $("#item_title").focus().select(); + $(".sub-menu-box", itemDom).show(); + updateChangeMenu(); + new Sortable($(".sub-menu-list", itemDom)[0], {draggable: 'li.sub-menu-item'}); + } + }); + + //切换类型 + $(document).on('change', 'input[name=type]', function () { + $(".sub-menu-item.current,.menu-item.current").data("type", $(this).val()).trigger("click"); + }); + + //删除菜单 + $(document).on('click', '#item_delete', function () { + var current = $("#menu-list li.current"); + var prev = current.prev("li[data-type]"); + var next = current.next("li[data-type]"); + + if (prev.length == 0 && next.length == 0 && $(".sub-menu-box", current).length == 0) { + last = current.closest(".menu-item"); + } else if (prev.length > 0 || next.length > 0) { + last = prev.length > 0 ? prev : next; + } else { + last = null; + $(".weixin-content").hide(); + $(".no-weixin-content").show(); + } + if (current.hasClass("sub-menu-item")) { + $(".add-sub-item", current.parent()).removeClass("hidden"); + } + current.remove(); + if (last) { + last.trigger('click'); + } else { + $("input[name='name']").val(''); + } + + updateChangeMenu(); + }); + + //更新修改与变动 + var updateChangeMenu = function () { + var item = $("#menu-list li.current"); + var values = $("#form-item").serializeArray(); + $.each(values, function (i, j) { + if (j.name == 'name') { + $(">a", item).html(j.value); + } + item.data(j.name, j.value); + }); + menuUpdate(); + }; + + //更新菜单数据 + var menuUpdate = function () { + $.post("wechat/menu/edit", {menu: JSON.stringify(getMenuList())}, function (data) { + if (data['code'] == 1) { + } else { + Toastr.error(__('Operation failed')); + } + }, 'json'); + }; + + //获取菜单数据 + var getMenuList = function () { + var menus = []; + var sub_button = []; + var menu_i = 0; + var sub_menu_i = 0; + var item; + $("#menu-list li").each(function (i) { + item = $(this); + var name = item.data('name'); + if (name != null) { + if (item.hasClass('menu-item')) { + sub_menu_i = 0; + if (item.find('.sub-menu-item').length > 0) { + menus[menu_i] = {"name": name, "sub_button": "sub_button"} + } else { + menus[menu_i] = $(this).data(); + } + if (menu_i > 0) { + menus[menu_i - 1]['sub_button'] = menus[menu_i - 1]['sub_button'] == "sub_button" ? sub_button : menus[menu_i - 1]['sub_button']; + } + sub_button = []; + menu_i++; + } else { + sub_button[sub_menu_i++] = $(this).data(); + } + } + }); + if (sub_button.length > 0) { + var len = menus.length; + menus[len - 1]['sub_button'] = sub_button; + } + return menus; + }; + + //添加子菜单 + $(document).on('click', ".add-sub-item", function () { + var sub_menu_item_total = $(this).parent().find(".sub-menu-item").length; + if (sub_menu_item_total < 5) { + var item = ''; + var itemDom = $(item); + itemDom.insertBefore(this); + itemDom.trigger("click"); + $("#item_title").focus().select(); + updateChangeMenu(); + if (sub_menu_item_total == 4) { + $(this).addClass("hidden"); + } + } + return false; + }); + + //主菜单子菜单点击事件 + $(document).on('click', ".menu-item, .sub-menu-item", function () { + var hasChild = $(".sub-menu-item", this).length > 0 ? true : false; + if ($(this).hasClass("sub-menu-item")) { + $("#menu-list li").removeClass('current'); + } else { + $("#menu-list li").removeClass('current'); + $("#menu-list > li").not(this).find(".sub-menu-box").hide(); + $(".sub-menu-box", this).toggle(); + } + $(this).addClass('current'); + + var data = $.extend({}, $(this).data()); + data.keytitle = data.key && typeof responselist[data.key] != 'undefined' ? responselist[data.key] : ''; + data.typeList = typeList; + data.hasChild = hasChild; + data.first = $(this).hasClass("menu-item") ? true : false; + $(".weixin-content").show(); + $(".no-weixin-content").hide(); + $("#item-body").html(Template("itemtpl", data)); + + return false; + }); + + //触发保存 + $("form#form-item").on('change', "input,textarea", function () { + updateChangeMenu(); + }); + + //加载远程菜单 + $(document).on('click', "#menuRemote", function () { + Layer.confirm("确认加载远程微信服务端菜单?
    本地菜单将会被覆盖!!!", {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 += '
    '; + } else { + str += '
    '; + } + } else { + var options = appConfig[i]['options']; + var html = ''; + if (type == 'select') { + for (var j in options) { + html += ''; + } + html = ''; + } else if (type == 'checkbox') { + for (var j in options) { + html += ' ' + options[j] + ' '; + } + + } else if (type == 'radio') { + var index = 0; + for (var j in options) { + html += ' ' + options[j] + ' '; + index++; + } + } + str += '
    ' + html + '
    '; + } + + } + return str; + }; + $(document).on('change', "#app", function () { + var app = $(this).val(); + $("#appfields").html(getAppFileds(app)); + if (datas.app == app) { + delete (datas.app); + var form = $("form.form-ajax"); + $.each(datas, function (i, j) { + form.field("row[content][" + i + "]" + ($("input[name='row[content][" + i + "][]']", form).length > 0 ? '[]' : ''), j); + }); + } + Form.api.bindevent("#appfields"); + }); + $(document).on('change', "input[name='row[type]']", function () { + var type = $(this).val(); + if (type == 'text') { + $("#expand").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: [],