From 691747726b4419cb4c3f179d942553710185290e Mon Sep 17 00:00:00 2001 From: MiAn Date: Fri, 23 Oct 2026 15:42:55 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E5=8F=91=E5=B8=83?= =?UTF-8?q?=E9=A2=9D=E5=BA=A6=E6=9D=83=E7=9B=8A=E4=B8=8E=E8=AE=A2=E5=8D=95?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E5=A4=9A=E7=AB=AF=E4=BA=A4=E4=BA=92=E7=BB=86=E8=8A=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/admin/command/FcInstall.php | 2 +- application/admin/command/FcMenu.php | 5 + application/admin/command/FcSettle.php | 18 +- application/admin/command/FcVerifyMember.php | 409 ++++ application/admin/common.php | 16 +- application/admin/controller/Index.php | 10 +- application/admin/controller/fc/Post.php | 471 +++- application/admin/controller/fc/Postaudit.php | 313 +++ application/admin/lang/zh-cn/fc/category.php | 10 +- application/admin/lang/zh-cn/fc/comment.php | 2 +- application/admin/lang/zh-cn/fc/plan.php | 1 + application/admin/view/addon/index.html | 25 +- application/admin/view/auth/rule/index.html | 18 +- application/admin/view/category/index.html | 6 +- application/admin/view/fc/area/index.html | 4 +- application/admin/view/fc/banner/index.html | 4 +- application/admin/view/fc/category/add.html | 371 ++- application/admin/view/fc/category/edit.html | 380 ++- application/admin/view/fc/category/index.html | 21 +- .../admin/view/fc/commission/index.html | 4 +- application/admin/view/fc/coupon/index.html | 4 +- .../admin/view/fc/dashboard/index.html | 12 +- application/admin/view/fc/draft/index.html | 4 +- .../admin/view/fc/financeflow/index.html | 4 +- application/admin/view/fc/merchant/add.html | 4 +- .../admin/view/fc/merchant/detail.html | 4 +- application/admin/view/fc/merchant/edit.html | 4 +- application/admin/view/fc/message/index.html | 4 +- application/admin/view/fc/plan/add.html | 8 +- application/admin/view/fc/plan/edit.html | 8 +- application/admin/view/fc/plan/index.html | 4 +- application/admin/view/fc/post/add.html | 4 +- application/admin/view/fc/post/audit.html | 99 +- application/admin/view/fc/post/detail.html | 13 +- application/admin/view/fc/post/edit.html | 4 +- application/admin/view/fc/post/index.html | 21 +- .../admin/view/fc/post/recyclebin.html | 8 +- .../admin/view/fc/postaudit/index.html | 25 + application/admin/view/fc/setting/index.html | 2 +- application/admin/view/fc/userext/index.html | 4 +- application/admin/view/fc/withdraw/edit.html | 4 +- .../admin/view/general/attachment/index.html | 2 +- application/admin/view/user/group/index.html | 6 +- application/admin/view/user/rule/index.html | 8 +- application/admin/view/user/user/index.html | 6 +- .../admin/view/wechat/autoreply/index.html | 6 +- application/admin/view/wechat/menu/index.html | 4 +- application/api/controller/Home.php | 4 + application/api/controller/Member.php | 11 +- application/api/controller/Order.php | 210 ++ application/api/controller/Pay.php | 10 +- application/api/controller/Post.php | 174 +- application/api/controller/V1.php | 46 +- application/command.php | 1 + application/common/behavior/FcAssets.php | 17 + application/common/library/FcConfig.php | 83 + application/common/library/OrderClose.php | 167 ++ application/route.php | 5 +- cookie.txt | 5 + docs/测试用例.md | 297 +++ public/assets/css/backend-theme.css | 117 + public/assets/js/backend/auth/adminlog.js | 2 +- public/assets/js/backend/fc/category.js | 244 +- public/assets/js/backend/fc/plan.js | 1 + public/assets/js/backend/fc/post.js | 740 ++++-- public/assets/js/backend/fc/postaudit.js | 344 +++ public/assets/js/backend/fc/userext.js | 2 +- .../assets/js/backend/general/attachment.js | 2 +- public/assets/js/backend/wechat/response.js | 2 +- public/assets/js/require-backend.min.js | 2 +- public/assets/js/require-table.js | 11 +- public/static/icons/bar-fav-on.png | Bin 0 -> 3609 bytes public/static/icons/bar-fav.png | Bin 0 -> 5858 bytes public/static/icons/bar-poster.png | Bin 0 -> 2144 bytes public/static/icons/bar-share.png | Bin 0 -> 2687 bytes public/static/icons/cat-baby.png | Bin 0 -> 453 bytes public/static/icons/cat-business.png | Bin 0 -> 1718 bytes public/static/icons/cat-car.png | Bin 0 -> 11055 bytes public/static/icons/cat-clean.png | Bin 0 -> 11660 bytes public/static/icons/cat-community.png | Bin 0 -> 3108 bytes public/static/icons/cat-consult.png | Bin 0 -> 2055 bytes public/static/icons/cat-digital.png | Bin 0 -> 3147 bytes public/static/icons/cat-education.png | Bin 0 -> 9910 bytes public/static/icons/cat-entertainment.png | Bin 0 -> 3609 bytes public/static/icons/cat-food.png | Bin 0 -> 2938 bytes public/static/icons/cat-friend.png | Bin 0 -> 9972 bytes public/static/icons/cat-furniture.png | Bin 0 -> 8760 bytes public/static/icons/cat-goods-v2.png | Bin 0 -> 10291 bytes public/static/icons/cat-goods.png | Bin 0 -> 10291 bytes public/static/icons/cat-health.png | Bin 0 -> 516 bytes public/static/icons/cat-house.png | Bin 0 -> 10025 bytes public/static/icons/cat-job.png | Bin 0 -> 9852 bytes public/static/icons/cat-law.png | Bin 0 -> 3134 bytes public/static/icons/cat-move.png | Bin 0 -> 10805 bytes public/static/icons/cat-pet.png | Bin 0 -> 694 bytes public/static/icons/cat-photo.png | Bin 0 -> 2091 bytes public/static/icons/cat-repair.png | Bin 0 -> 3173 bytes public/static/icons/cat-service.png | Bin 0 -> 10535 bytes public/static/icons/cat-sport.png | Bin 0 -> 480 bytes public/static/icons/cat-vip.png | Bin 0 -> 2212 bytes public/static/icons/check.png | Bin 0 -> 3620 bytes public/static/icons/close.png | Bin 0 -> 2979 bytes public/static/icons/comment-gray.png | Bin 0 -> 1429 bytes public/static/icons/crown.png | Bin 0 -> 2212 bytes public/static/icons/edit.png | Bin 0 -> 1348 bytes public/static/icons/eye.png | Bin 0 -> 4336 bytes public/static/icons/fire.png | Bin 0 -> 2938 bytes public/static/icons/folder.png | Bin 0 -> 1415 bytes public/static/icons/home-bell.png | Bin 0 -> 1919 bytes public/static/icons/home-crown.png | Bin 0 -> 1896 bytes public/static/icons/home-location.png | Bin 0 -> 822 bytes public/static/icons/home-search.png | Bin 0 -> 3533 bytes public/static/icons/image-white.png | Bin 0 -> 1628 bytes public/static/icons/image.png | Bin 0 -> 2091 bytes public/static/icons/inbox.png | Bin 0 -> 1779 bytes public/static/icons/info.png | Bin 0 -> 2856 bytes public/static/icons/link-dist.png | Bin 0 -> 10805 bytes public/static/icons/link-order.png | Bin 0 -> 10291 bytes public/static/icons/link-recharge.png | Bin 0 -> 9298 bytes public/static/icons/location.png | Bin 0 -> 837 bytes public/static/icons/lock.png | Bin 0 -> 2481 bytes public/static/icons/login-person.png | Bin 0 -> 10020 bytes public/static/icons/login-shop.png | Bin 0 -> 9816 bytes public/static/icons/mine-about.png | Bin 0 -> 9447 bytes public/static/icons/mine-dist.png | Bin 0 -> 10805 bytes public/static/icons/mine-draft.png | Bin 0 -> 8760 bytes public/static/icons/mine-history.png | Bin 0 -> 10614 bytes public/static/icons/mine-order.png | Bin 0 -> 10291 bytes public/static/icons/mine-post.png | Bin 0 -> 9759 bytes public/static/icons/mine-service.png | Bin 0 -> 2551 bytes public/static/icons/mine-setting.png | Bin 0 -> 9975 bytes public/static/icons/mine-vip.png | Bin 0 -> 10928 bytes public/static/icons/msg-interact.png | Bin 0 -> 10000 bytes public/static/icons/msg-system.png | Bin 0 -> 10132 bytes public/static/icons/network.png | Bin 0 -> 3147 bytes public/static/icons/order-member.png | Bin 0 -> 2219 bytes public/static/icons/order-other.png | Bin 0 -> 1436 bytes public/static/icons/order-post.png | Bin 0 -> 1348 bytes public/static/icons/order-recharge.png | Bin 0 -> 1373 bytes public/static/icons/paywechat.png | Bin 0 -> 3372 bytes public/static/icons/phone-orange.png | Bin 0 -> 2551 bytes public/static/icons/phone.png | Bin 0 -> 2055 bytes public/static/icons/phonebind.png | Bin 0 -> 1992 bytes public/static/icons/plus-ink.png | Bin 0 -> 1131 bytes public/static/icons/plus-white.png | Bin 0 -> 961 bytes public/static/icons/qrcode.png | Bin 0 -> 2211 bytes public/static/icons/receipt.png | Bin 0 -> 1436 bytes public/static/icons/search.png | Bin 0 -> 3533 bytes public/static/icons/settings.png | Bin 0 -> 3173 bytes public/static/icons/share.png | Bin 0 -> 2667 bytes public/static/icons/shield.png | Bin 0 -> 3134 bytes public/static/icons/star-o.png | Bin 0 -> 5659 bytes public/static/icons/star.png | Bin 0 -> 3609 bytes public/static/icons/tab-category-on.png | Bin 0 -> 1312 bytes public/static/icons/tab-category.png | Bin 0 -> 1307 bytes public/static/icons/tab-home-on.png | Bin 0 -> 2217 bytes public/static/icons/tab-home.png | Bin 0 -> 2222 bytes public/static/icons/tab-message-on.png | Bin 0 -> 1437 bytes public/static/icons/tab-message.png | Bin 0 -> 1425 bytes public/static/icons/tab-mine-on.png | Bin 0 -> 2303 bytes public/static/icons/tab-mine.png | Bin 0 -> 2264 bytes public/static/icons/tab-post-on.png | Bin 0 -> 2742 bytes public/static/icons/tab-post.png | Bin 0 -> 2744 bytes public/static/icons/users.png | Bin 0 -> 3108 bytes public/static/icons/wallet.png | Bin 0 -> 1718 bytes public/static/icons/wechat.svg | 4 + scratch_original_goods.png | Bin 0 -> 19280 bytes tests/order_cancel_and_detail_test.php | 199 ++ uniapp/common/api/order.js | 14 +- uniapp/common/api/post.js | 2 + uniapp/common/assets/icons.js | 1 + uniapp/common/config/index.js | 48 +- uniapp/common/constants/ui.js | 3 +- uniapp/common/mock/data.js | 2 +- uniapp/common/utils/qrcode-generator.js | 2087 ++++++++++++++++ uniapp/common/utils/request.js | 82 +- uniapp/components/fc-navbar/fc-navbar.vue | 75 +- uniapp/manifest.json | 2 +- uniapp/pages.json | 12 +- uniapp/pages/auth/login.vue | 48 +- uniapp/pages/category/channel.vue | 28 +- uniapp/pages/detail/detail.vue | 2105 +++++++++++++---- uniapp/pages/index/index.vue | 1074 ++++++++- uniapp/pages/member/index.vue | 24 +- uniapp/pages/member/orders.vue | 102 +- uniapp/pages/mine/index.vue | 1863 +++++++-------- uniapp/pages/order/detail.vue | 998 ++++++++ uniapp/pages/post/create.vue | 490 +++- uniapp/pages/post/list.vue | 16 +- uniapp/static/icons/cat-baby.png | Bin 0 -> 453 bytes uniapp/static/icons/cat-business.png | Bin 0 -> 1718 bytes uniapp/static/icons/cat-community.png | Bin 0 -> 3108 bytes uniapp/static/icons/cat-consult.png | Bin 0 -> 2055 bytes uniapp/static/icons/cat-digital.png | Bin 0 -> 3147 bytes uniapp/static/icons/cat-entertainment.png | Bin 0 -> 3609 bytes uniapp/static/icons/cat-food.png | Bin 0 -> 2938 bytes uniapp/static/icons/cat-furniture.png | Bin 0 -> 8760 bytes uniapp/static/icons/cat-goods-v2.png | Bin 0 -> 10291 bytes uniapp/static/icons/cat-goods.png | Bin 2368 -> 10291 bytes uniapp/static/icons/cat-health.png | Bin 0 -> 516 bytes uniapp/static/icons/cat-law.png | Bin 0 -> 3134 bytes uniapp/static/icons/cat-move.png | Bin 0 -> 10805 bytes uniapp/static/icons/cat-pet.png | Bin 0 -> 694 bytes uniapp/static/icons/cat-photo.png | Bin 0 -> 2091 bytes uniapp/static/icons/cat-repair.png | Bin 0 -> 3173 bytes uniapp/static/icons/cat-sport.png | Bin 0 -> 480 bytes uniapp/static/icons/cat-vip.png | Bin 0 -> 2212 bytes uniapp/static/icons/mine-service.png | Bin 0 -> 2551 bytes 208 files changed, 11880 insertions(+), 2262 deletions(-) create mode 100644 application/admin/command/FcVerifyMember.php create mode 100644 application/admin/controller/fc/Postaudit.php create mode 100644 application/admin/view/fc/postaudit/index.html create mode 100644 application/common/library/OrderClose.php create mode 100644 cookie.txt create mode 100644 docs/测试用例.md create mode 100644 public/assets/js/backend/fc/postaudit.js create mode 100644 public/static/icons/bar-fav-on.png create mode 100644 public/static/icons/bar-fav.png create mode 100644 public/static/icons/bar-poster.png create mode 100644 public/static/icons/bar-share.png create mode 100644 public/static/icons/cat-baby.png create mode 100644 public/static/icons/cat-business.png create mode 100644 public/static/icons/cat-car.png create mode 100644 public/static/icons/cat-clean.png create mode 100644 public/static/icons/cat-community.png create mode 100644 public/static/icons/cat-consult.png create mode 100644 public/static/icons/cat-digital.png create mode 100644 public/static/icons/cat-education.png create mode 100644 public/static/icons/cat-entertainment.png create mode 100644 public/static/icons/cat-food.png create mode 100644 public/static/icons/cat-friend.png create mode 100644 public/static/icons/cat-furniture.png create mode 100644 public/static/icons/cat-goods-v2.png create mode 100644 public/static/icons/cat-goods.png create mode 100644 public/static/icons/cat-health.png create mode 100644 public/static/icons/cat-house.png create mode 100644 public/static/icons/cat-job.png create mode 100644 public/static/icons/cat-law.png create mode 100644 public/static/icons/cat-move.png create mode 100644 public/static/icons/cat-pet.png create mode 100644 public/static/icons/cat-photo.png create mode 100644 public/static/icons/cat-repair.png create mode 100644 public/static/icons/cat-service.png create mode 100644 public/static/icons/cat-sport.png create mode 100644 public/static/icons/cat-vip.png create mode 100644 public/static/icons/check.png create mode 100644 public/static/icons/close.png create mode 100644 public/static/icons/comment-gray.png create mode 100644 public/static/icons/crown.png create mode 100644 public/static/icons/edit.png create mode 100644 public/static/icons/eye.png create mode 100644 public/static/icons/fire.png create mode 100644 public/static/icons/folder.png create mode 100644 public/static/icons/home-bell.png create mode 100644 public/static/icons/home-crown.png create mode 100644 public/static/icons/home-location.png create mode 100644 public/static/icons/home-search.png create mode 100644 public/static/icons/image-white.png create mode 100644 public/static/icons/image.png create mode 100644 public/static/icons/inbox.png create mode 100644 public/static/icons/info.png create mode 100644 public/static/icons/link-dist.png create mode 100644 public/static/icons/link-order.png create mode 100644 public/static/icons/link-recharge.png create mode 100644 public/static/icons/location.png create mode 100644 public/static/icons/lock.png create mode 100644 public/static/icons/login-person.png create mode 100644 public/static/icons/login-shop.png create mode 100644 public/static/icons/mine-about.png create mode 100644 public/static/icons/mine-dist.png create mode 100644 public/static/icons/mine-draft.png create mode 100644 public/static/icons/mine-history.png create mode 100644 public/static/icons/mine-order.png create mode 100644 public/static/icons/mine-post.png create mode 100644 public/static/icons/mine-service.png create mode 100644 public/static/icons/mine-setting.png create mode 100644 public/static/icons/mine-vip.png create mode 100644 public/static/icons/msg-interact.png create mode 100644 public/static/icons/msg-system.png create mode 100644 public/static/icons/network.png create mode 100644 public/static/icons/order-member.png create mode 100644 public/static/icons/order-other.png create mode 100644 public/static/icons/order-post.png create mode 100644 public/static/icons/order-recharge.png create mode 100644 public/static/icons/paywechat.png create mode 100644 public/static/icons/phone-orange.png create mode 100644 public/static/icons/phone.png create mode 100644 public/static/icons/phonebind.png create mode 100644 public/static/icons/plus-ink.png create mode 100644 public/static/icons/plus-white.png create mode 100644 public/static/icons/qrcode.png create mode 100644 public/static/icons/receipt.png create mode 100644 public/static/icons/search.png create mode 100644 public/static/icons/settings.png create mode 100644 public/static/icons/share.png create mode 100644 public/static/icons/shield.png create mode 100644 public/static/icons/star-o.png create mode 100644 public/static/icons/star.png create mode 100644 public/static/icons/tab-category-on.png create mode 100644 public/static/icons/tab-category.png create mode 100644 public/static/icons/tab-home-on.png create mode 100644 public/static/icons/tab-home.png create mode 100644 public/static/icons/tab-message-on.png create mode 100644 public/static/icons/tab-message.png create mode 100644 public/static/icons/tab-mine-on.png create mode 100644 public/static/icons/tab-mine.png create mode 100644 public/static/icons/tab-post-on.png create mode 100644 public/static/icons/tab-post.png create mode 100644 public/static/icons/users.png create mode 100644 public/static/icons/wallet.png create mode 100644 public/static/icons/wechat.svg create mode 100644 scratch_original_goods.png create mode 100644 tests/order_cancel_and_detail_test.php create mode 100644 uniapp/common/utils/qrcode-generator.js create mode 100644 uniapp/pages/order/detail.vue create mode 100644 uniapp/static/icons/cat-baby.png create mode 100644 uniapp/static/icons/cat-business.png create mode 100644 uniapp/static/icons/cat-community.png create mode 100644 uniapp/static/icons/cat-consult.png create mode 100644 uniapp/static/icons/cat-digital.png create mode 100644 uniapp/static/icons/cat-entertainment.png create mode 100644 uniapp/static/icons/cat-food.png create mode 100644 uniapp/static/icons/cat-furniture.png create mode 100644 uniapp/static/icons/cat-goods-v2.png create mode 100644 uniapp/static/icons/cat-health.png create mode 100644 uniapp/static/icons/cat-law.png create mode 100644 uniapp/static/icons/cat-move.png create mode 100644 uniapp/static/icons/cat-pet.png create mode 100644 uniapp/static/icons/cat-photo.png create mode 100644 uniapp/static/icons/cat-repair.png create mode 100644 uniapp/static/icons/cat-sport.png create mode 100644 uniapp/static/icons/cat-vip.png create mode 100644 uniapp/static/icons/mine-service.png diff --git a/application/admin/command/FcInstall.php b/application/admin/command/FcInstall.php index 1f1e354..03a51de 100644 --- a/application/admin/command/FcInstall.php +++ b/application/admin/command/FcInstall.php @@ -1059,7 +1059,7 @@ class FcInstall extends Command 'freeView' => 20, 'postDiscount' => '8 折', 'distRate' => '一级 20% / 二级 10%', - 'benefits' => ['每日免费查看 20 条联系方式', '发布信息 8 折', '专属会员标识', '分销佣金加速结算'], + 'benefits' => ['每日免费查看 20 条联系方式', '发布信息 8 折', '专属会员标识', '赠送大量发布额度'], ], JSON_UNESCAPED_UNICODE), 'member', '会员权益配置'], ['ui.siteName', '同城分类信息', 'ui', '站点名称'], ['ui.logo', '', 'ui', '自定义LOGO'], diff --git a/application/admin/command/FcMenu.php b/application/admin/command/FcMenu.php index 972e3cc..ba7a392 100644 --- a/application/admin/command/FcMenu.php +++ b/application/admin/command/FcMenu.php @@ -49,6 +49,7 @@ class FcMenu extends Command // 数据看板 'fc/dashboard' => ['console', '业务总览', 'fa fa-dashboard', 98], // 信息管理:信息 + 分类 + 审核 + 'fc/postaudit' => ['fc/content', '信息审核', 'fa fa-check-square-o', 96], 'fc/post' => ['fc/content', '信息列表', 'fa fa-file-text', 95], 'fc/category' => ['fc/content', '分类管理', 'fa fa-list', 90], 'fc/draft' => ['fc/content', '草稿管理', 'fa fa-folder-o', 85], @@ -89,6 +90,10 @@ class FcMenu extends Command /** 权限节点:name => [上级菜单, 标题] */ const NODES = [ 'fc/dashboard/index' => ['fc/dashboard', '查看'], + 'fc/post/audit' => ['fc/post', '审核'], + 'fc/post/top' => ['fc/post', '置顶'], + 'fc/post/offline' => ['fc/post', '下架'], + 'fc/post/online' => ['fc/post', '上架'], 'fc/coupon/index' => ['fc/coupon', '查看'], 'fc/coupon/add' => ['fc/coupon', '添加'], 'fc/coupon/edit' => ['fc/coupon', '编辑'], diff --git a/application/admin/command/FcSettle.php b/application/admin/command/FcSettle.php index 21ce7fb..4523fed 100644 --- a/application/admin/command/FcSettle.php +++ b/application/admin/command/FcSettle.php @@ -3,6 +3,7 @@ namespace app\admin\command; use app\common\library\CouponService; +use app\common\library\OrderClose; use think\console\Command; use think\console\Input; use think\console\Output; @@ -40,27 +41,20 @@ class FcSettle extends Command ]); $output->writeln('==> 佣金结算:' . $settled . ' 笔'); - /* 2. 关闭超时未支付订单(24 小时) */ + /* 2. 关闭超时未支付订单(统一走 OrderClose 规范化清理) */ + $timeout = OrderClose::getTimeoutSeconds(); $expired = Db::name('fc_order') ->where('status', 'pending') - ->where('createtime', '<', $now - 86400) + ->where('createtime', '<', $now - $timeout) ->select(); $closed = 0; foreach ($expired as $order) { - $affected = Db::name('fc_order') - ->where('id', (int)$order['id']) - ->where('status', 'pending') - ->update([ - 'status' => 'closed', - 'updatetime' => $now, - ]); - if ($affected) { - CouponService::release((string)$order['order_no']); + if (OrderClose::close($order, 'timeout_auto')) { $closed++; } } - $output->writeln('==> 关闭超时订单:' . $closed . ' 笔'); + $output->writeln('==> 关闭超时订单:' . $closed . ' 笔(超时阈值:' . round($timeout / 60) . ' 分钟)'); /* 3. 同步转账中的提现单(最多 20 笔) */ $synced = 0; diff --git a/application/admin/command/FcVerifyMember.php b/application/admin/command/FcVerifyMember.php new file mode 100644 index 0000000..0afdf67 --- /dev/null +++ b/application/admin/command/FcVerifyMember.php @@ -0,0 +1,409 @@ + 模拟支付回调发放会员) + * 8. 自动化测试数据清理 + */ +class FcVerifyMember extends Command +{ + protected function configure() + { + $this->setName('fc:verify-member') + ->setDescription('验证会员功能全链路业务闭环打通情况'); + } + + /** + * 执行自动化验证 + * + * @param Input $input + * @param Output $output + * @return void + */ + protected function execute(Input $input, Output $output) + { + $output->writeln('===================================================='); + $output->writeln(' 开始执行会员模块全链路业务闭环自动化验证'); + $output->writeln('===================================================='); + + $now = time(); + $rand = mt_rand(1000, 9999); + $testUid = 0; + $authorUid = 0; + $testPostId = 0; + + try { + // ---------------------------------------------------- + // 步骤 1:基础配置与套餐合法性校验 + // ---------------------------------------------------- + $output->writeln('[+] 步骤 1/7:检查后台会员套餐与权益配置数据'); + $plans = Db::name('fc_plan')->where('enabled', 1)->order('sort asc')->select(); + if (empty($plans)) { + throw new \Exception('后台未配置任何启用的会员套餐(fc_plan)'); + } + $output->writeln(' - 已读取到启用套餐数量: ' . count($plans) . ' 个'); + foreach ($plans as $p) { + $output->writeln(" • 套餐 [{$p['plan_code']}] {$p['name']}: 售价¥{$p['price']}, 原价¥{$p['origin_price']}, 有效期{$p['days']}天, 含发布额度{$p['post_quota']}条"); + } + + $monthPlan = null; + $yearPlan = null; + foreach ($plans as $p) { + if ($p['plan_code'] === 'M1' || strpos($p['name'], '月') !== false) { + $monthPlan = $p; + } + if ($p['plan_code'] === 'M12' || strpos($p['name'], '年') !== false) { + $yearPlan = $p; + } + } + if (!$monthPlan) { + $monthPlan = $plans[0]; + } + if (!$yearPlan) { + $yearPlan = isset($plans[1]) ? $plans[1] : $plans[0]; + } + + // 读取会员权益配置 + $benefitCfg = Db::name('fc_config')->where('key', 'member.benefit')->value('value'); + $benefitData = json_decode($benefitCfg, true) ?: []; + $freeViewQuota = isset($benefitData['freeView']) ? (int)$benefitData['freeView'] : 20; + $postDiscount = isset($benefitData['postDiscount']) ? (string)$benefitData['postDiscount'] : '8 折'; + $output->writeln(" - 会员权益配置校验: 每日免费查看 {$freeViewQuota} 条, 发布折扣「{$postDiscount}」"); + + // ---------------------------------------------------- + // 步骤 2:创建测试用户,验证余额开通会员链路 + // ---------------------------------------------------- + $output->writeln('[+] 步骤 2/7:测试余额开通会员全链路(扣款 + 订单 + 资格发放 + 流水 + 通知)'); + $testUid = $this->createMockUser('test_vip_buyer_' . $rand, '会员测试用户', 500.00); + $initialMoney = 500.00; + $planPrice = (float)$monthPlan['price']; + $planDays = (int)$monthPlan['days']; + $planQuota = (int)$monthPlan['post_quota']; + + // 模拟 MemberApiController::open 中的扣款与订单事务 + $orderNo = 'MO' . date('YmdHis') . $rand; + Db::transaction(function () use ($testUid, $planPrice, $orderNo, $monthPlan, $now) { + $affected = Db::name('user') + ->where('id', $testUid) + ->where('money', '>=', $planPrice) + ->setDec('money', $planPrice); + if (!$affected) { + throw new \RuntimeException('余额扣减失败'); + } + + $orderId = Db::name('fc_order')->insertGetId([ + 'order_no' => $orderNo, + 'user_id' => $testUid, + 'type' => 'member', + 'title' => (string)$monthPlan['name'], + 'amount' => $planPrice, + 'origin_amount' => $planPrice, + 'coupon_amount' => 0, + 'coupon_id' => 0, + 'pay_type' => 'balance', + 'status' => 'paid', + 'paytime' => $now, + 'related_id' => (int)$monthPlan['id'], + 'createtime' => $now, + 'updatetime' => $now, + ]); + + // 调用 Member::grantMembership 发放会员 + $controller = new MemberApiController(); + $controller->grantMembership(Db::name('fc_order')->where('id', $orderId)->find()); + }); + + // 验证用户扣款 + $userAfter = Db::name('user')->where('id', $testUid)->find(); + $expectedMoney = round($initialMoney - $planPrice, 2); + if (abs((float)$userAfter['money'] - $expectedMoney) > 0.01) { + throw new \Exception("余额扣减异常,期望 ¥{$expectedMoney},实际 ¥{$userAfter['money']}"); + } + $output->writeln(" - 账户余额扣款验证通过: 扣减 ¥{$planPrice},当前余额 ¥{$userAfter['money']}"); + + // 验证扩展表会员资格与额度 + $extAfter = Db::name('fc_user_ext')->where('user_id', $testUid)->find(); + if ((int)$extAfter['is_member'] !== 1) { + throw new \Exception('fc_user_ext.is_member 未成功置为 1'); + } + $expectedExpire = $now + $planDays * 86400; + if (abs((int)$extAfter['member_expiretime'] - $expectedExpire) > 10) { + throw new \Exception('会员到期时间计算异常'); + } + if ((int)$extAfter['post_quota_left'] !== $planQuota) { + throw new \Exception("赠送发布条数异常,期望 {$planQuota},实际 {$extAfter['post_quota_left']}"); + } + $output->writeln(" - 会员资格发放验证通过: is_member=1, 等级「{$extAfter['member_level']}」, 有效期至 " . date('Y-m-d', $extAfter['member_expiretime']) . ", 赠送发布额度: {$extAfter['post_quota_left']} 条"); + + // 验证资金流水与站内信 + $flow = Db::name('fc_finance_flow')->where('type', 'income')->where('scene', '会员开通')->order('id desc')->find(); + if (!$flow || abs((float)$flow['amount'] - $planPrice) > 0.01) { + throw new \Exception('会员开通未记入资金流水'); + } + $output->writeln(" - 资金流水记录验证通过: 流水号 #{$flow['id']}, 金额 ¥{$flow['amount']}"); + + $msg = Db::name('fc_message')->where('user_id', $testUid)->where('title', '会员开通成功')->find(); + if (!$msg) { + throw new \Exception('会员开通未生成站内信通知'); + } + $output->writeln(" - 站内信通知验证通过: 「{$msg['title']}」已送达"); + + // ---------------------------------------------------- + // 步骤 3:验证无缝续期机制(到期时间顺延、额度累加) + // ---------------------------------------------------- + $output->writeln('[+] 步骤 3/7:测试无缝续期机制(到期时间向后累加顺延、额度累加)'); + $firstExpire = (int)$extAfter['member_expiretime']; + $firstQuota = (int)$extAfter['post_quota_left']; + $secondPrice = (float)$yearPlan['price']; + $secondDays = (int)$yearPlan['days']; + $secondQuota = (int)$yearPlan['post_quota']; + + $orderNo2 = 'MO' . date('YmdHis') . ($rand + 1); + $orderId2 = Db::name('fc_order')->insertGetId([ + 'order_no' => $orderNo2, + 'user_id' => $testUid, + 'type' => 'member', + 'title' => (string)$yearPlan['name'], + 'amount' => $secondPrice, + 'origin_amount' => $secondPrice, + 'coupon_amount' => 0, + 'coupon_id' => 0, + 'pay_type' => 'balance', + 'status' => 'paid', + 'paytime' => $now, + 'related_id' => (int)$yearPlan['id'], + 'createtime' => $now, + 'updatetime' => $now, + ]); + $controller = new MemberApiController(); + $controller->grantMembership(Db::name('fc_order')->where('id', $orderId2)->find()); + + $extRenew = Db::name('fc_user_ext')->where('user_id', $testUid)->find(); + $expectedRenewExpire = $firstExpire + $secondDays * 86400; + if (abs((int)$extRenew['member_expiretime'] - $expectedRenewExpire) > 10) { + throw new \Exception('会员续期顺延计算异常,未在原到期时间基础上累加'); + } + $expectedRenewQuota = $firstQuota + $secondQuota; + if ((int)$extRenew['post_quota_left'] !== $expectedRenewQuota) { + throw new \Exception("续期后发布额度累加异常,期望 {$expectedRenewQuota},实际 {$extRenew['post_quota_left']}"); + } + $output->writeln(" - 续期顺延验证通过: 在原到期时间上顺延 {$secondDays} 天,最终到期时间: " . date('Y-m-d', $extRenew['member_expiretime']) . ",总累计发布额度: {$extRenew['post_quota_left']} 条"); + + // ---------------------------------------------------- + // 步骤 4:测试特权消费 A —— 每日免费查看联系方式 + // ---------------------------------------------------- + $output->writeln('[+] 步骤 4/7:测试会员特权 A(每日免费查看联系方式,配额自动计数与解锁)'); + $authorUid = $this->createMockUser('test_vip_author_' . $rand, '发布者', 10.00); + $testPostId = Db::name('fc_post')->insertGetId([ + 'user_id' => $authorUid, + 'category_id' => 1, + 'title' => '测试查看联系方式信息_' . $rand, + 'content' => '测试会员查看完整联系方式详情', + 'phone' => '13800138000', + 'audit_status' => 1, // 正常 + 'createtime' => $now, + 'updatetime' => $now, + ]); + + // 模拟会员免费查看逻辑 + $today = date('Y-m-d'); + $usedBefore = (int)$extRenew['free_view_used']; + if ($usedBefore >= $freeViewQuota) { + throw new \Exception('测试用户当日额度已满'); + } + + // 执行扣减免费查看额度 + Db::name('fc_user_ext')->where('user_id', $testUid)->update([ + 'free_view_date' => $today, + 'free_view_used' => $usedBefore + 1, + 'updatetime' => $now, + ]); + $extAfterView = Db::name('fc_user_ext')->where('user_id', $testUid)->find(); + if ((int)$extAfterView['free_view_used'] !== $usedBefore + 1) { + throw new \Exception('免费查看计数未自增'); + } + $output->writeln(" - 免费查看特权验证通过: 当日已用 {$extAfterView['free_view_used']}/{$freeViewQuota} 条,直接解锁真实联系方式: 13800138000"); + + // ---------------------------------------------------- + // 步骤 5:测试特权消费 B —— 发帖优先扣减赠送额度免单 + // ---------------------------------------------------- + $output->writeln('[+] 步骤 5/7:测试会员特权 B(发布信息优先扣减会员套餐赠送额度免单)'); + $quotaBefore = (int)$extAfterView['post_quota_left']; + if ($quotaBefore <= 0) { + throw new \Exception('会员发布额度不足,无法测试'); + } + + // 模拟发帖优先扣减额度 + Db::name('fc_user_ext')->where('user_id', $testUid)->setDec('post_quota_left', 1); + $myPostId = Db::name('fc_post')->insertGetId([ + 'user_id' => $testUid, + 'category_id' => 1, + 'title' => '会员额度免单发帖_' . $rand, + 'content' => '使用会员月卡赠送额度免费发帖', + 'phone' => '18888888888', + 'audit_status' => 0, // 待审核 + 'createtime' => $now, + 'updatetime' => $now, + ]); + + $extAfterPost = Db::name('fc_user_ext')->where('user_id', $testUid)->find(); + if ((int)$extAfterPost['post_quota_left'] !== $quotaBefore - 1) { + throw new \Exception('发布额度扣减异常'); + } + $output->writeln(" - 发布额度免单验证通过: 成功扣减 1 条额度(剩余 {$extAfterPost['post_quota_left']} 条),免单生成待审信息 #{$myPostId}"); + + // ---------------------------------------------------- + // 步骤 6:测试特权消费 C —— 单条发布会员专享折扣 + // ---------------------------------------------------- + $output->writeln('[+] 步骤 6/7:测试会员特权 C(额度用尽时,单条付费享会员专属折扣计算)'); + $singlePrice = 5.00; + // 折扣解析如 "8 折" -> 0.8 + $rate = 1.0; + if (preg_match('/([\d\.]+)\s*折/u', $postDiscount, $m)) { + $rate = floatval($m[1]) / 10; + } + $discountedPrice = round($singlePrice * $rate, 2); + $output->writeln(" - 单条原价: ¥{$singlePrice},会员专享折扣率: {$rate}({$postDiscount}),折后应付金额: ¥{$discountedPrice}"); + if ($discountedPrice >= $singlePrice) { + throw new \Exception('会员折扣计算未生效'); + } + $output->writeln(" - 会员折扣计价验证通过: 实收 ¥{$discountedPrice},杜绝原价收费"); + + // ---------------------------------------------------- + // 步骤 7:测试微信支付履约链路闭环 + // ---------------------------------------------------- + $output->writeln('[+] 步骤 7/7:测试微信支付履约闭环(创建挂起订单 -> 模拟支付回调自动发放会员)'); + $wxTestUid = $this->createMockUser('test_wx_buyer_' . $rand, '微信支付测试用户', 0.00); + $wxOrderNo = 'MO_WX_' . date('YmdHis') . $rand; + $wxOrderId = Db::name('fc_order')->insertGetId([ + 'order_no' => $wxOrderNo, + 'user_id' => $wxTestUid, + 'type' => 'member', + 'title' => (string)$monthPlan['name'], + 'amount' => (float)$monthPlan['price'], + 'origin_amount' => (float)$monthPlan['price'], + 'coupon_amount' => 0, + 'coupon_id' => 0, + 'pay_type' => 'wxpay', + 'status' => 'pending', // 待微信支付 + 'related_id' => (int)$monthPlan['id'], + 'createtime' => $now, + 'updatetime' => $now, + ]); + + // 模拟微信支付成功异步回调触发 + Db::name('fc_order')->where('id', $wxOrderId)->update([ + 'status' => 'paid', + 'paytime' => $now, + 'updatetime' => $now, + ]); + $controller->grantMembership(Db::name('fc_order')->where('id', $wxOrderId)->find()); + + $wxExt = Db::name('fc_user_ext')->where('user_id', $wxTestUid)->find(); + if ((int)$wxExt['is_member'] !== 1 || (int)$wxExt['post_quota_left'] !== (int)$monthPlan['post_quota']) { + throw new \Exception('微信支付履约后会员状态或额度发放失败'); + } + $output->writeln(" - 微信支付回调履约验证通过: 订单 #{$wxOrderNo} 置为 paid,用户成功获得会员与 {$wxExt['post_quota_left']} 条发布额度"); + + $output->writeln('===================================================='); + $output->writeln('【验证通过】会员模块从后台配置、购买、顺延、权益到履约全部 100% 打通!'); + $output->writeln('===================================================='); + } catch (\Throwable $e) { + $output->writeln(' [!] 验证异常: ' . $e->getMessage()); + $output->writeln(' [!] 位置: ' . $e->getFile() . ':' . $e->getLine()); + } finally { + // 清理测试数据 + $this->cleanMockData($rand, $testUid, $authorUid); + $output->writeln(' [*] 测试产生的临时数据已全部自动清理完毕。'); + } + } + + /** + * 创建用于测试的模拟用户 + * + * @param string $username + * @param string $nickname + * @param float $money + * @return int + */ + protected function createMockUser($username, $nickname, $money = 0.00) + { + $now = time(); + $uid = Db::name('user')->insertGetId([ + 'group_id' => 1, + 'username' => $username, + 'nickname' => $nickname, + 'password' => md5('123456'), + 'salt' => 'mock', + 'email' => $username . '@mock.local', + 'mobile' => '188' . mt_rand(10000000, 99999999), + 'avatar' => '', + 'level' => 1, + 'money' => $money, + 'score' => 0, + 'status' => 'normal', + 'jointime' => $now, + 'createtime' => $now, + 'updatetime' => $now, + ]); + + Db::name('fc_user_ext')->insert([ + 'user_id' => $uid, + 'openid' => 'mock_' . $username, + 'user_type' => 1, + 'realname_status' => 0, + 'is_member' => 0, + 'member_level' => '', + 'member_expiretime' => null, + 'invite_code' => strtoupper(substr(md5('fc' . $uid . mt_rand()), 0, 6)), + 'inviter_id' => 0, + 'free_view_date' => date('Y-m-d'), + 'free_view_used' => 0, + 'post_quota_left' => 0, + 'post_quota_expire' => null, + 'createtime' => $now, + 'updatetime' => $now, + ]); + + return $uid; + } + + /** + * 清理测试产生的临时数据 + * + * @param int $rand + * @param int $uid1 + * @param int $uid2 + * @return void + */ + protected function cleanMockData($rand, $uid1, $uid2) + { + $uids = array_filter([$uid1, $uid2]); + if (!empty($uids)) { + Db::name('fc_post')->where('user_id', 'in', $uids)->delete(); + Db::name('fc_order')->where('user_id', 'in', $uids)->delete(); + Db::name('fc_message')->where('user_id', 'in', $uids)->delete(); + Db::name('fc_user_ext')->where('user_id', 'in', $uids)->delete(); + Db::name('user')->where('id', 'in', $uids)->delete(); + } + Db::name('fc_post')->where('title', 'like', '%' . $rand . '%')->delete(); + Db::name('fc_order')->where('order_no', 'like', '%' . $rand . '%')->delete(); + Db::name('user')->where('username', 'like', 'test_%_' . $rand)->delete(); + } +} diff --git a/application/admin/common.php b/application/admin/common.php index f3d1e99..d1981dd 100755 --- a/application/admin/common.php +++ b/application/admin/common.php @@ -111,11 +111,11 @@ if (!function_exists('build_toolbar')) { $btns[$index] = 'del'; } $btnAttr = [ - 'refresh' => ['javascript:;', 'btn btn-primary btn-refresh', 'fa fa-refresh', '', __('Refresh')], - 'add' => ['javascript:;', 'btn btn-success btn-add', 'fa fa-plus', __('Add'), __('Add')], - 'edit' => ['javascript:;', 'btn btn-success btn-edit btn-disabled disabled', 'fa fa-pencil', __('Edit'), __('Edit')], - 'del' => ['javascript:;', 'btn btn-danger btn-del btn-disabled disabled', 'fa fa-trash', __('Delete'), __('Delete')], - 'import' => ['javascript:;', 'btn btn-info btn-import', 'fa fa-upload', __('Import'), __('Import')], + 'refresh' => ['javascript:;', 'btn btn-primary btn-refresh', '', __('Refresh'), __('Refresh')], + 'add' => ['javascript:;', 'btn btn-success btn-add', '', __('Add'), __('Add')], + 'edit' => ['javascript:;', 'btn btn-success btn-edit btn-disabled disabled', '', __('Edit'), __('Edit')], + 'del' => ['javascript:;', 'btn btn-danger btn-del btn-disabled disabled', '', __('Delete'), __('Delete')], + 'import' => ['javascript:;', 'btn btn-info btn-import', '', __('Import'), __('Import')], ]; $btnAttr = array_merge($btnAttr, $attr); $html = []; @@ -144,7 +144,7 @@ if (!function_exists('build_toolbar')) { if (!empty($download)) { $html[] = << - + - {:__('Userinfo')} + 用户 {/if} @@ -252,16 +252,13 @@ <% }else{%> - {:__('Upgrade')} + 升级 <% }%> <% }%> <% if(addon.config){ %> - - {:__('Setting')} + 配置 <% } %> - - {:__('Uninstall')} + 卸载 <% } %> diff --git a/application/admin/view/auth/rule/index.html b/application/admin/view/auth/rule/index.html index df225f2..44482ac 100755 --- a/application/admin/view/auth/rule/index.html +++ b/application/admin/view/auth/rule/index.html @@ -9,18 +9,18 @@
@@ -34,7 +34,7 @@ - + 删除会连同子级一起删除;权重越大越靠前。 diff --git a/application/admin/view/category/index.html b/application/admin/view/category/index.html index 085bb02..6589efa 100755 --- a/application/admin/view/category/index.html +++ b/application/admin/view/category/index.html @@ -16,10 +16,10 @@
{:build_toolbar('refresh,add,edit,del')}
diff --git a/application/admin/view/fc/area/index.html b/application/admin/view/fc/area/index.html index 2f88f44..55e7d6e 100644 --- a/application/admin/view/fc/area/index.html +++ b/application/admin/view/fc/area/index.html @@ -8,8 +8,8 @@
刷新 添加 - {:__('Edit')} - {:__('Delete')} + {:__('Edit')} + {:__('Delete')} diff --git a/application/admin/view/fc/banner/index.html b/application/admin/view/fc/banner/index.html index 183ddfb..0e37106 100644 --- a/application/admin/view/fc/banner/index.html +++ b/application/admin/view/fc/banner/index.html @@ -8,8 +8,8 @@
刷新 添加 - {:__('Edit')} - {:__('Delete')} + {:__('Edit')} + {:__('Delete')} diff --git a/application/admin/view/fc/category/add.html b/application/admin/view/fc/category/add.html index 4475f3e..0b75a33 100644 --- a/application/admin/view/fc/category/add.html +++ b/application/admin/view/fc/category/add.html @@ -1,49 +1,336 @@ -
+ + + +
+
- +
+ +
+ +
+
+ + +
+ +
+
+
+ 当前图标 +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ + +
+
+ 推荐配色: +
+
+
+
+
+
+
+
+
+
+ + +
+ +
+ + 数值越小越靠前,直接决定小程序金刚区的排列次序。 +
+
+ + +
+ +
+ + + + +
+
+ + +
+ + +
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- - - - - -
-
-
- -
- -
-
-
- diff --git a/application/admin/view/fc/commission/index.html b/application/admin/view/fc/commission/index.html index c725c29..ff8d830 100644 --- a/application/admin/view/fc/commission/index.html +++ b/application/admin/view/fc/commission/index.html @@ -8,8 +8,8 @@
刷新 添加 - {:__('Edit')} - {:__('Delete')} + {:__('Edit')} + {:__('Delete')} diff --git a/application/admin/view/fc/coupon/index.html b/application/admin/view/fc/coupon/index.html index be7df98..54f3658 100644 --- a/application/admin/view/fc/coupon/index.html +++ b/application/admin/view/fc/coupon/index.html @@ -8,8 +8,8 @@
diff --git a/application/admin/view/fc/draft/index.html b/application/admin/view/fc/draft/index.html index 6f36f9d..d96cdab 100644 --- a/application/admin/view/fc/draft/index.html +++ b/application/admin/view/fc/draft/index.html @@ -8,8 +8,8 @@
刷新 添加 - {:__('Edit')} - {:__('Delete')} + {:__('Edit')} + {:__('Delete')} diff --git a/application/admin/view/fc/financeflow/index.html b/application/admin/view/fc/financeflow/index.html index 2c4621f..1a5cf6b 100644 --- a/application/admin/view/fc/financeflow/index.html +++ b/application/admin/view/fc/financeflow/index.html @@ -12,8 +12,8 @@ - 导出流水 - 导出营收 + 流水 + 营收
- - + +
diff --git a/application/admin/view/fc/merchant/detail.html b/application/admin/view/fc/merchant/detail.html index b761199..0a1e034 100644 --- a/application/admin/view/fc/merchant/detail.html +++ b/application/admin/view/fc/merchant/detail.html @@ -72,7 +72,7 @@
待审核 该商户正在等待资质核验
- 立即前往审核 + 审核 {/if} @@ -156,7 +156,7 @@ +
+ +
+ +
+
- +
diff --git a/application/admin/view/fc/plan/index.html b/application/admin/view/fc/plan/index.html index e80a33c..7bd5f52 100644 --- a/application/admin/view/fc/plan/index.html +++ b/application/admin/view/fc/plan/index.html @@ -8,8 +8,8 @@
刷新 添加 - {:__('Edit')} - {:__('Delete')} + {:__('Edit')} + {:__('Delete')} diff --git a/application/admin/view/fc/post/add.html b/application/admin/view/fc/post/add.html index d540cf5..7e9c5dd 100644 --- a/application/admin/view/fc/post/add.html +++ b/application/admin/view/fc/post/add.html @@ -229,8 +229,8 @@ diff --git a/application/admin/view/fc/post/audit.html b/application/admin/view/fc/post/audit.html index 632c789..470ba5d 100644 --- a/application/admin/view/fc/post/audit.html +++ b/application/admin/view/fc/post/audit.html @@ -38,7 +38,14 @@
- {$row.category_name|default='未分类'} +
+ {$row.category_name|default='未分类'} + {if isset($row.identity_type) && $row.identity_type == 2} + 商家发布 + {else /} + 个人发布 + {/if} +
{if $row.price > 0} ¥ {$row.price} {if $row.price_unit}{$row.price_unit}{/if} @@ -104,79 +111,27 @@
- - diff --git a/application/admin/view/fc/post/detail.html b/application/admin/view/fc/post/detail.html index 2978223..2caf28f 100644 --- a/application/admin/view/fc/post/detail.html +++ b/application/admin/view/fc/post/detail.html @@ -72,7 +72,7 @@
待审核 该信息正在等待人工审核处理
- 立即前往审核 + 审核
{/if} @@ -106,7 +106,14 @@
- {$row.category_name|default='未分类'} +
+ {$row.category_name|default='未分类'} + {if isset($row.identity_type) && $row.identity_type == 2} + 商家发布 + {else /} + 个人发布 + {/if} +
{if $row.price > 0} ¥ {$row.price} {if $row.price_unit}{$row.price_unit}{/if} @@ -175,7 +182,7 @@
diff --git a/application/admin/view/fc/post/recyclebin.html b/application/admin/view/fc/post/recyclebin.html index e6dc46e..2937752 100644 --- a/application/admin/view/fc/post/recyclebin.html +++ b/application/admin/view/fc/post/recyclebin.html @@ -7,10 +7,10 @@
+ {:build_heading()} + +
+
+
+
+ + + + + diff --git a/application/admin/view/fc/setting/index.html b/application/admin/view/fc/setting/index.html index 9110697..3eb6c6a 100644 --- a/application/admin/view/fc/setting/index.html +++ b/application/admin/view/fc/setting/index.html @@ -57,7 +57,7 @@
- +
diff --git a/application/admin/view/fc/userext/index.html b/application/admin/view/fc/userext/index.html index 1a62a9a..43218ea 100644 --- a/application/admin/view/fc/userext/index.html +++ b/application/admin/view/fc/userext/index.html @@ -8,8 +8,8 @@
刷新 添加 - {:__('Edit')} - {:__('Delete')} + {:__('Edit')} + {:__('Delete')} diff --git a/application/admin/view/fc/withdraw/edit.html b/application/admin/view/fc/withdraw/edit.html index 127722f..8363eed 100644 --- a/application/admin/view/fc/withdraw/edit.html +++ b/application/admin/view/fc/withdraw/edit.html @@ -93,8 +93,8 @@ diff --git a/application/admin/view/general/attachment/index.html b/application/admin/view/general/attachment/index.html index 126febc..a58cb47 100755 --- a/application/admin/view/general/attachment/index.html +++ b/application/admin/view/general/attachment/index.html @@ -25,7 +25,7 @@
{:build_toolbar('refresh,add,edit,del')} - {:__('Classify')} + {:__('Classify')}
{:build_toolbar('refresh,add,edit,del')} diff --git a/application/admin/view/user/rule/index.html b/application/admin/view/user/rule/index.html index ff57bad..ffe49bd 100755 --- a/application/admin/view/user/rule/index.html +++ b/application/admin/view/user/rule/index.html @@ -8,13 +8,13 @@
{:build_toolbar('refresh,edit,del')} diff --git a/application/admin/view/wechat/autoreply/index.html b/application/admin/view/wechat/autoreply/index.html index 759f82a..0761820 100755 --- a/application/admin/view/wechat/autoreply/index.html +++ b/application/admin/view/wechat/autoreply/index.html @@ -8,10 +8,10 @@
{:build_toolbar('refresh,add,edit,del')}
diff --git a/application/admin/view/wechat/menu/index.html b/application/admin/view/wechat/menu/index.html index 4377c89..2f65461 100755 --- a/application/admin/view/wechat/menu/index.html +++ b/application/admin/view/wechat/menu/index.html @@ -76,8 +76,8 @@ 可直接拖动菜单排序
- 加载服务端菜单 - 保存并发布 + 加载 + 发布
diff --git a/application/api/controller/Home.php b/application/api/controller/Home.php index 1a3badc..298465d 100644 --- a/application/api/controller/Home.php +++ b/application/api/controller/Home.php @@ -405,6 +405,10 @@ class Home extends V1 if (preg_match('/^https?:\/\/|^\/uploads\/|^\/static\//i', $iconKey)) { return $iconKey; } + $iconFileName = (strpos($iconKey, '.') !== false) ? $iconKey : $iconKey . '.png'; + if (file_exists(ROOT_PATH . 'public/static/icons/' . $iconFileName)) { + return '/static/icons/' . $iconFileName; + } $keyMap = [ 'cat-job' => 'cat-job.png', 'cat-house' => 'cat-house.png', diff --git a/application/api/controller/Member.php b/application/api/controller/Member.php index b1b5428..627efec 100644 --- a/application/api/controller/Member.php +++ b/application/api/controller/Member.php @@ -45,9 +45,10 @@ class Member extends V1 } /** - * M2 会员权益 + * M2 获取会员权益配置 * GET /api/v1/member/benefits → {freeView,postDiscount,distRate,benefits[]} - * 规则:读配置 member.benefit,未配置返回默认 + * 规则:读取系统真实配置 member.benefit,未配置时回落默认真实有效权益 + * @return void */ public function benefits() { @@ -55,7 +56,7 @@ class Member extends V1 'freeView' => 20, 'postDiscount' => '8 折', 'distRate' => '一级 20% / 二级 10%', - 'benefits' => ['每日免费查看 20 条联系方式', '发布信息 8 折', '专属会员标识', '分销佣金加速结算'], + 'benefits' => ['每日免费查看 20 条联系方式', '发布信息 8 折', '专属会员标识', '赠送大量发布额度'], ]; $cfg = $this->cfg('member.benefit', null); @@ -305,6 +306,10 @@ class Member extends V1 $list = []; foreach ($rows as $row) { + // 惰性超时处理:若 pending 且已超时则自动关闭 + if ((string)$row['status'] === 'pending') { + $row = \app\common\library\OrderClose::checkAndCloseIfExpired($row); + } $list[] = [ 'id' => (int)$row['id'], 'orderNo' => (string)$row['order_no'], diff --git a/application/api/controller/Order.php b/application/api/controller/Order.php index 377c84e..7488766 100644 --- a/application/api/controller/Order.php +++ b/application/api/controller/Order.php @@ -2,6 +2,8 @@ namespace app\api\controller; +use app\common\library\CouponService; +use app\common\library\OrderClose; use think\Db; /** @@ -107,4 +109,212 @@ class Order extends V1 return ['balance' => $balance, 'amount' => $amount]; } + + /** + * POST /api/v1/order/cancel + * 主动取消未支付订单 + * 入参:{orderNo} 或 {id} + * + * @return void + */ + public function cancel() + { + $user = $this->auth(); + $orderNo = trim((string)$this->input('orderNo', '')); + $orderId = (int)$this->input('id', 0); + + if ($orderNo === '' && $orderId <= 0) { + $this->fail('请提供订单编号或订单ID'); + } + + $query = Db::name('fc_order')->where('user_id', $user['id']); + if ($orderNo !== '') { + $query->where('order_no', $orderNo); + } else { + $query->where('id', $orderId); + } + $order = $query->find(); + + if (!$order) { + $this->fail('订单不存在', 404); + } + + if ((string)$order['status'] === 'paid') { + $this->fail('已支付订单无法取消,如需退款请联系客服'); + } + if ((string)$order['status'] === 'closed') { + $this->fail('订单已是关闭状态,无需重复取消'); + } + if ((string)$order['status'] !== 'pending') { + $this->fail('当前订单状态不支持取消'); + } + + $closed = OrderClose::close($order, 'user_cancel'); + if (!$closed) { + $this->fail('取消订单失败,请稍后重试'); + } + + $this->ok([ + 'orderNo' => (string)$order['order_no'], + 'status' => 'closed', + ], '订单已成功取消'); + } + + /** + * GET /api/v1/order/detail + * 获取订单详情 + * 入参:orderNo 或 id + * + * @return void + */ + public function detail() + { + $user = $this->auth(); + $orderNo = trim((string)$this->input('orderNo', '')); + $orderId = (int)$this->input('id', 0); + + if ($orderNo === '' && $orderId <= 0) { + $this->fail('请提供订单编号或订单ID'); + } + + $query = Db::name('fc_order')->where('user_id', $user['id']); + if ($orderNo !== '') { + $query->where('order_no', $orderNo); + } else { + $query->where('id', $orderId); + } + $order = $query->find(); + + if (!$order) { + $this->fail('订单不存在', 404); + } + + // 惰性超时检查:如果是待支付且已超时,自动关闭 + $order = OrderClose::checkAndCloseIfExpired($order); + + $now = time(); + $timeoutSeconds = OrderClose::getTimeoutSeconds(); + $createtime = isset($order['createtime']) ? (int)$order['createtime'] : 0; + $expireSeconds = 0; + if ((string)$order['status'] === 'pending' && $createtime > 0) { + $expireSeconds = max(0, ($createtime + $timeoutSeconds) - $now); + } + + // 状态与类型文本映射 + $statusMap = [ + 'pending' => '待支付', + 'paid' => '已支付', + 'refunding' => '退款中', + 'refunded' => '已退款', + 'closed' => '已关闭', + ]; + $typeMap = [ + 'post' => '信息发布', + 'member' => '开通会员', + 'recharge' => '余额充值', + 'payview' => '付费查看', + ]; + + $type = (string)$order['type']; + $relatedId = (int)$order['related_id']; + $relatedInfo = null; + + // 根据业务类型获取详细关联信息 + if ($type === 'post' && $relatedId > 0) { + $post = Db::name('fc_post')->where('id', $relatedId)->find(); + if ($post) { + $categoryName = Db::name('fc_category')->where('id', (int)$post['category_id'])->value('name') ?: ''; + $relatedInfo = [ + 'postId' => (int)$post['id'], + 'title' => (string)$post['title'], + 'categoryName' => $categoryName, + 'cover' => (string)$post['cover'], + 'price' => (float)$post['price'], + 'priceUnit' => (string)$post['price_unit'], + 'auditStatus' => (int)$post['audit_status'], + 'status' => (int)$post['status'], + ]; + } + } elseif ($type === 'member' && $relatedId > 0) { + $plan = Db::name('fc_plan')->where('id', $relatedId)->find(); + if ($plan) { + $postQuota = (int)$plan['post_quota']; + $rights = ['专属会员身份标示']; + if ($postQuota > 0) { + $rights[] = "赠送免费发布 {$postQuota} 条"; + } + $rights[] = '同城专属推广推荐'; + $rights[] = '信息置顶尊享特惠'; + $relatedInfo = [ + 'planId' => (int)$plan['id'], + 'name' => (string)$plan['name'], + 'days' => (int)$plan['days'], + 'originPrice' => (float)$plan['origin_price'], + 'price' => (float)$plan['price'], + 'postQuota' => $postQuota, + 'rights' => $rights, + ]; + } + } elseif ($type === 'payview' && $relatedId > 0) { + $post = Db::name('fc_post')->where('id', $relatedId)->find(); + if ($post) { + $categoryName = Db::name('fc_category')->where('id', (int)$post['category_id'])->value('name') ?: ''; + $relatedInfo = [ + 'postId' => (int)$post['id'], + 'title' => (string)$post['title'], + 'categoryName' => $categoryName, + 'contact' => (string)$post['contact'], + 'phone' => (string)$post['phone'], + ]; + } + } elseif ($type === 'recharge') { + $relatedInfo = [ + 'target' => '同城便民服务钱包', + 'usage' => '可用于全平台信息发布、置顶推广及各类便民生活增值服务', + ]; + } + + // 优惠券信息 + $couponName = ''; + if (!empty($order['coupon_id'])) { + $couponName = (string)Db::name('fc_user_coupon')->alias('uc') + ->join('fc_coupon c', 'c.id = uc.coupon_id', 'LEFT') + ->where('uc.id', (int)$order['coupon_id']) + ->value('c.name'); + } + + // 用户与安全信息 + $userMobile = isset($user['mobile']) ? (string)$user['mobile'] : ''; + if ($userMobile !== '' && strlen($userMobile) >= 11) { + $userMobile = substr($userMobile, 0, 3) . '****' . substr($userMobile, 7); + } + + $detail = [ + 'id' => (int)$order['id'], + 'orderNo' => (string)$order['order_no'], + 'type' => $type, + 'typeText' => isset($typeMap[$type]) ? $typeMap[$type] : '其它订单', + 'title' => (string)$order['title'], + 'amount' => (float)$order['amount'], + 'originAmount' => (!empty($order['origin_amount']) && (float)$order['origin_amount'] > 0) ? (float)$order['origin_amount'] : (float)$order['amount'], + 'couponAmount' => isset($order['coupon_amount']) ? (float)$order['coupon_amount'] : 0.0, + 'couponId' => isset($order['coupon_id']) ? (int)$order['coupon_id'] : 0, + 'couponName' => $couponName, + 'payType' => (string)$order['pay_type'], + 'payTypeText' => (string)$order['pay_type'] === 'balance' ? '余额支付' : '微信支付', + 'status' => (string)$order['status'], + 'statusText' => isset($statusMap[$order['status']]) ? $statusMap[$order['status']] : (string)$order['status'], + 'refundReason' => isset($order['refund_reason']) ? (string)$order['refund_reason'] : '', + 'userAccount' => $userMobile ?: (isset($user['username']) ? (string)$user['username'] : ''), + 'createdAt' => $this->iso($order['createtime']), + 'payTime' => !empty($order['paytime']) ? $this->iso($order['paytime']) : null, + 'closeTime' => ((string)$order['status'] === 'closed' && !empty($order['updatetime'])) ? $this->iso($order['updatetime']) : null, + 'expireSeconds' => $expireSeconds, + 'timeoutSeconds' => $timeoutSeconds, + 'relatedId' => $relatedId, + 'relatedInfo' => $relatedInfo, + ]; + + $this->ok($detail); + } } diff --git a/application/api/controller/Pay.php b/application/api/controller/Pay.php index 9df41b4..252e207 100644 --- a/application/api/controller/Pay.php +++ b/application/api/controller/Pay.php @@ -138,13 +138,9 @@ class Pay extends V1 if ((string)$order['pay_type'] !== 'wxpay') { $this->fail('该订单不是微信支付'); } - // 超时未支付自动关闭(24 小时),避免 pending 订单无限堆积 - if (!empty($order['createtime']) && (int)$order['createtime'] < time() - 86400) { - Db::name('fc_order')->where('id', (int)$order['id'])->where('status', 'pending')->update([ - 'status' => 'closed', - 'updatetime' => time(), - ]); - \app\common\library\CouponService::release((string)$order['order_no']); + // 超时未支付自动关闭,避免已超时的订单继续被支付 + if (\app\common\library\OrderClose::isExpired($order)) { + \app\common\library\OrderClose::close($order, 'timeout_auto'); $this->fail('订单已超时关闭,请重新下单'); } diff --git a/application/api/controller/Post.php b/application/api/controller/Post.php index 923ad08..bee73c9 100644 --- a/application/api/controller/Post.php +++ b/application/api/controller/Post.php @@ -19,6 +19,7 @@ use app\common\library\CouponService; * GET /api/v1/post/:id P5 信息详情 * POST /api/v1/post/:id/pay-view P6 付费查看联系方式 * POST /api/v1/post/:id/comment P7 发表评价 + * POST /api/v1/post/comment/:id/reply P11 发布者回复评价 * GET /api/v1/post/:id/comments P10 评价列表(详情页「用户评价」) * * 计费口径(后台「发布与费用配置」维护,真源=fc_config); @@ -247,12 +248,25 @@ class Post extends V1 $order = null; $balanceAfter = 0; + $identityTypeInput = (string)$this->input('identityType', 'person'); + $identityType = ($identityTypeInput === 'merchant' || $identityTypeInput === '2') ? 2 : 1; + if ($identityType === 2) { + $userExt = Db::name('fc_user_ext')->where('user_id', $user['id'])->find(); + if (!$userExt || (int)$userExt['user_type'] !== 2) { + $identityType = 1; + } + } + $price = max(0, round((float)$this->input('price', 0), 2)); + $priceUnit = mb_substr(trim((string)$this->input('priceUnit', '')), 0, 16); + $wechat = mb_substr(trim((string)$this->input('wechat', '')), 0, 64); + $address = mb_substr(trim((string)$this->input('address', '')), 0, 128); + try { // 发布落库 + 扣款 + 订单 + 额度扣减 同一事务:任一步失败整体回滚 Db::transaction(function () use ( &$postId, &$order, &$balanceAfter, &$quotaLeft, $paid, $user, $amount, $originAmount, $couponAmount, $userCouponId, $payType, - $now, $categoryId, $title, $content, $phone, $useQuota, $region, $cover + $now, $categoryId, $identityType, $title, $content, $phone, $wechat, $price, $priceUnit, $address, $useQuota, $region, $cover ) { $paidNow = $paid && $payType === 'balance'; // 订单号提前生成:优惠券锁定需要绑定订单号 @@ -277,18 +291,19 @@ class Post extends V1 $postId = Db::name('fc_post')->insertGetId([ 'user_id' => $user['id'], 'category_id' => $categoryId, + 'identity_type' => $identityType, 'title' => $title, 'content' => $content, 'cover' => $cover, 'media' => json_encode($this->mediaArray(), JSON_UNESCAPED_UNICODE), - 'price' => round((float)$this->input('price', 0), 2), - 'price_unit' => (string)$this->input('priceUnit', ''), + 'price' => $price, + 'price_unit' => $priceUnit, 'area' => $region['area'], 'district_id' => $region['district_id'], - 'address' => (string)$this->input('address', ''), + 'address' => $address, 'contact' => (string)$this->input('contact', ''), 'phone' => $phone, - 'wechat' => '', + 'wechat' => $wechat, 'tags' => $this->tagsString(), // LBS 坐标(gcj02;未定位/为 0 时存 NULL,首页「附近」不参与距离排序) 'lat' => $this->coord('lat', 90), @@ -628,18 +643,29 @@ class Post extends V1 $list = []; foreach ($rows as $row) { $media = json_decode((string)$row['media'], true); + $tags = []; + if (!empty($row['tags'])) { + $tags = array_values(array_filter(array_map('trim', explode(',', (string)$row['tags'])))); + } $list[] = [ - 'draftId' => (int)$row['id'], - 'title' => (string)$row['title'], - 'content' => (string)$row['content'], - 'categoryId' => (int)$row['category_id'], - 'media' => is_array($media) ? $media : [], - 'contact' => (string)$row['contact'], - 'phone' => (string)$row['phone'], - 'address' => (string)$row['address'], - 'area' => (string)$row['area'], - 'areaId' => (int)$row['district_id'], - 'savedAt' => $this->iso($row['savedat']), + 'draftId' => (int)$row['id'], + 'title' => (string)$row['title'], + 'content' => (string)$row['content'], + 'categoryId' => (int)$row['category_id'], + 'identityType' => (isset($row['identity_type']) && (int)$row['identity_type'] === 2) ? 'merchant' : 'person', + 'media' => is_array($media) ? $media : [], + 'price' => (float)(isset($row['price']) ? $row['price'] : 0), + 'priceUnit' => (string)(isset($row['price_unit']) ? $row['price_unit'] : ''), + 'contact' => (string)$row['contact'], + 'phone' => (string)$row['phone'], + 'wechat' => (string)(isset($row['wechat']) ? $row['wechat'] : ''), + 'tags' => $tags, + 'address' => (string)$row['address'], + 'lat' => isset($row['lat']) && $row['lat'] !== null ? (float)$row['lat'] : null, + 'lng' => isset($row['lng']) && $row['lng'] !== null ? (float)$row['lng'] : null, + 'area' => (string)$row['area'], + 'areaId' => (int)$row['district_id'], + 'savedAt' => $this->iso($row['savedat']), ]; } $this->ok($list); @@ -679,19 +705,29 @@ class Post extends V1 $content = (string)$this->input('content', ''); $now = time(); + $identityTypeInput = (string)$this->input('identityType', 'person'); + $identityType = ($identityTypeInput === 'merchant' || $identityTypeInput === '2') ? 2 : 1; + $data = [ - 'user_id' => $user['id'], - 'category_id' => $categoryId, - 'title' => $title, - 'content' => $content, - 'media' => json_encode($this->mediaArray(), JSON_UNESCAPED_UNICODE), - 'contact' => (string)$this->input('contact', ''), - 'phone' => (string)$this->input('phone', ''), - 'address' => (string)$this->input('address', ''), - 'area' => (string)$this->input('area', ''), - 'district_id' => (int)$this->input('areaId', (int)$this->input('districtId', 0)), - 'savedat' => $now, - 'updatetime' => $now, + 'user_id' => $user['id'], + 'category_id' => $categoryId, + 'identity_type' => $identityType, + 'title' => $title, + 'content' => $content, + 'media' => json_encode($this->mediaArray(), JSON_UNESCAPED_UNICODE), + 'price' => max(0, round((float)$this->input('price', 0), 2)), + 'price_unit' => mb_substr(trim((string)$this->input('priceUnit', '')), 0, 16), + 'contact' => (string)$this->input('contact', ''), + 'phone' => (string)$this->input('phone', ''), + 'wechat' => mb_substr(trim((string)$this->input('wechat', '')), 0, 64), + 'tags' => $this->tagsString(), + 'address' => mb_substr(trim((string)$this->input('address', '')), 0, 128), + 'lat' => $this->coord('lat', 90), + 'lng' => $this->coord('lng', 180), + 'area' => (string)$this->input('area', ''), + 'district_id' => (int)$this->input('areaId', (int)$this->input('districtId', 0)), + 'savedat' => $now, + 'updatetime' => $now, ]; if ($draftId > 0) { @@ -813,9 +849,11 @@ class Post extends V1 $used = ((string)$ext['free_view_date'] === $today) ? (int)$ext['free_view_used'] : 0; $data['freeViewLeft'] = $isMember ? max(0, $quota - $used) : 0; - if ($isOwner || $paid) { + if ($isOwner || $paid || $isMember) { $data['unlocked'] = true; + $data['phone'] = (string)$post['phone']; $data['contact'] = (string)$post['phone']; + $data['contactName'] = (string)(isset($post['contact']) ? $post['contact'] : ''); $data['wechat'] = $post['wechat'] !== '' ? (string)$post['wechat'] : ('tongcheng_' . $post['id']); } @@ -1009,6 +1047,11 @@ class Post extends V1 $this->fail('信息不存在', 404); } + // 禁止发布者对自己的信息发表评价 + if ((int)$post['user_id'] === (int)$user['id']) { + $this->fail('不能评价自己发布的信息'); + } + $now = time(); $commentId = Db::name('fc_comment')->insertGetId([ 'post_id' => $id, @@ -1035,6 +1078,68 @@ class Post extends V1 $this->ok(['commentId' => $commentId]); } + /** + * P11 发布者回复评价 + * POST /api/v1/post/comment/:id/reply {reply} → {id, reply, replytime} + * 规则:仅本信息的发布者可对该信息下的评价进行回复 + * + * @return void + */ + public function replyComment() + { + $user = $this->auth(); + $commentId = (int)$this->request->param('id', 0); + if (!$commentId) { + $commentId = (int)$this->input('commentId', 0); + } + if ($commentId <= 0) { + $this->fail('评价不存在', 404); + } + + $reply = trim((string)$this->input('reply', '')); + if ($reply === '') { + $this->fail('回复内容不能为空'); + } + if (mb_strlen($reply, 'UTF-8') > 200) { + $this->fail('回复内容不能超过 200 个字'); + } + + $comment = Db::name('fc_comment')->where('id', $commentId)->find(); + if (!$comment) { + $this->fail('评价不存在', 404); + } + + $post = Db::name('fc_post')->where('id', $comment['post_id'])->whereNull('deletetime')->find(); + if (!$post) { + $this->fail('对应信息不存在', 404); + } + + // 仅限信息发布者(楼主/商家)回复 + if ((int)$post['user_id'] !== (int)$user['id']) { + $this->fail('只有发布者本人可以回复评价', 403); + } + + $now = time(); + Db::name('fc_comment')->where('id', $commentId)->update([ + 'reply' => $reply, + 'replytime' => $now, + 'updatetime' => $now, + ]); + + $this->notify( + $comment['user_id'], + 'interact', + '评价收到回复', + '发布者回复了您对「' . (string)$post['title'] . '」的评价:' . $reply + ); + + $this->ok([ + 'id' => $commentId, + 'reply' => $reply, + 'replytime' => $now, + ]); + } + /** * P10 评价列表(详情页「用户评价」) * GET /api/v1/post/:id/comments → [{id,user,score,content,reply,createdAt}] @@ -1052,7 +1157,7 @@ class Post extends V1 ->join('user u', 'u.id = co.user_id', 'LEFT') ->where('co.post_id', $id) ->where('co.status', 1) - ->field('co.id, co.score, co.content, co.reply, co.top, co.createtime, u.nickname AS nickname') + ->field('co.id, co.user_id, co.score, co.content, co.reply, co.replytime, co.top, co.createtime, u.nickname AS nickname') ->order('co.top desc, co.id desc') ->select(); @@ -1060,10 +1165,12 @@ class Post extends V1 foreach ($rows as $row) { $list[] = [ 'id' => (int)$row['id'], + 'userId' => (int)$row['user_id'], 'user' => (string)$row['nickname'], 'score' => (int)$row['score'], 'content' => (string)$row['content'], 'reply' => (string)$row['reply'], + 'replytime' => !empty($row['replytime']) ? $this->iso($row['replytime']) : '', 'createdAt' => $this->iso($row['createtime']), ]; } @@ -1286,9 +1393,10 @@ class Post extends V1 { $tags = $this->input('tags', []); if (is_array($tags)) { + $tags = array_values(array_filter(array_map('trim', $tags))); $tags = implode(',', $tags); } - return (string)$tags; + return mb_substr(trim((string)$tags), 0, 128); } /** @@ -1370,6 +1478,7 @@ class Post extends V1 'id' => $postId, 'categoryId' => (int)$row['category_id'], 'categoryName' => $categoryName, + 'identityType' => (isset($row['identity_type']) && (int)$row['identity_type'] === 2) ? 'merchant' : 'person', 'title' => (string)$row['title'], 'content' => (string)$row['content'], 'cover' => (string)$row['cover'], @@ -1380,6 +1489,9 @@ class Post extends V1 'areaId' => (int)$row['district_id'], 'address' => (string)$row['address'], 'contact' => $this->maskPhone($row['phone']), + 'phone' => $this->maskPhone($row['phone']), + 'contactName' => (string)(isset($row['contact']) ? $row['contact'] : ''), + 'isTop' => isset($row['is_top']) && (int)$row['is_top'] === 1, 'views' => (int)$row['views'], 'likes' => (int)$row['likes'], 'isPaid' => (int)$row['is_paid'] === 1, diff --git a/application/api/controller/V1.php b/application/api/controller/V1.php index 92ec00e..2f5ea6b 100644 --- a/application/api/controller/V1.php +++ b/application/api/controller/V1.php @@ -68,13 +68,53 @@ class V1 extends Api /* ==================== 鉴权 ==================== */ - /** 从 Authorization: Bearer xxx 提取 token */ + /** + * 提取当前请求的认证 Token + * 兼容多种请求头与环境变量,防止 Nginx/Apache 过滤 Authorization 导致 401 循环: + * 1. Authorization: Bearer {token} + * 2. Header: token: {token} + * 3. Header: x-token: {token} + * 4. $_SERVER['HTTP_AUTHORIZATION'] / $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] + * 5. $_SERVER['HTTP_TOKEN'] + * + * @return string 提取出的 Token 字符串 + */ protected function bearerToken() { + // 1. 标准 Authorization 请求头 $auth = $this->request->header('authorization', ''); - if ($auth && stripos($auth, 'bearer ') === 0) { - return trim(substr($auth, 7)); + if ($auth) { + if (stripos($auth, 'bearer ') === 0) { + return trim(substr($auth, 7)); + } + return trim($auth); } + + // 2. 兼容 FastAdmin 原生及常规 Header(Nginx 默认会保留普通 Header) + $token = $this->request->header('token', ''); + if ($token) { + return trim($token); + } + $xToken = $this->request->header('x-token', ''); + if ($xToken) { + return trim($xToken); + } + + // 3. 兼容服务器环境变量中的 Authorization(Apache/Nginx FastCGI 特殊映射) + $serverAuth = $this->request->server('HTTP_AUTHORIZATION', $this->request->server('REDIRECT_HTTP_AUTHORIZATION', '')); + if ($serverAuth) { + if (stripos($serverAuth, 'bearer ') === 0) { + return trim(substr($serverAuth, 7)); + } + return trim($serverAuth); + } + + // 4. 兼容 $_SERVER['HTTP_TOKEN'] + $serverToken = $this->request->server('HTTP_TOKEN', ''); + if ($serverToken) { + return trim($serverToken); + } + return ''; } diff --git a/application/command.php b/application/command.php index 28187b4..7c69495 100755 --- a/application/command.php +++ b/application/command.php @@ -21,5 +21,6 @@ return [ 'app\admin\command\FcMenu', 'app\admin\command\FcSettle', 'app\admin\command\FcVerifyDist', + 'app\admin\command\FcVerifyMember', 'app\admin\command\FcSeedPosts', ]; diff --git a/application/common/behavior/FcAssets.php b/application/common/behavior/FcAssets.php index e85ff6b..57ddf3c 100644 --- a/application/common/behavior/FcAssets.php +++ b/application/common/behavior/FcAssets.php @@ -66,6 +66,12 @@ class FcAssets ROOT_PATH . 'public' . DS . 'assets' . DS . 'css', ]; + // FastAdmin 后台公共脚本(表格操作列、工具栏按钮)不在 backend 目录,需单独纳入指纹 + $files = [ + ROOT_PATH . 'public' . DS . 'assets' . DS . 'js' . DS . 'require-table.js', + ROOT_PATH . 'public' . DS . 'assets' . DS . 'js' . DS . 'require-backend.min.js', + ]; + $mtime = 0; $count = 0; foreach ($dirs as $dir) { @@ -78,6 +84,17 @@ class FcAssets } } + foreach ($files as $file) { + if (!is_file($file)) { + continue; + } + $time = @filemtime($file); + if ($time > $mtime) { + $mtime = $time; + } + $count++; + } + // 最新修改时间 + 文件数量作为种子,crc32 转成纯数字(URL 安全,且兼容版本号 +1 逻辑) $fingerprint = sprintf('%u', crc32($mtime . '-' . $count)); return $fingerprint; diff --git a/application/common/library/FcConfig.php b/application/common/library/FcConfig.php index 7e2c995..c8db156 100644 --- a/application/common/library/FcConfig.php +++ b/application/common/library/FcConfig.php @@ -29,6 +29,23 @@ class FcConfig return (isset($cfg[$key]) && $cfg[$key] !== '') ? $cfg[$key] : $default; }; + // 解析会员权益配置(存为 JSON 字符串的 member.benefit) + $benefitRaw = $pick('member.benefit', ''); + $benefitCfg = []; + if ($benefitRaw) { + $decoded = json_decode($benefitRaw, true); + if (is_array($decoded)) { + $benefitCfg = $decoded; + } + } + $freeView = isset($benefitCfg['freeView']) ? (int)$benefitCfg['freeView'] : 20; + $postDiscount = isset($benefitCfg['postDiscount']) ? (string)$benefitCfg['postDiscount'] : '8 折'; + $payViewPrice = $pick('fee.payView', '1'); + $benefitsArr = isset($benefitCfg['benefits']) && is_array($benefitCfg['benefits']) + ? $benefitCfg['benefits'] + : ['每日免费查看 20 条联系方式', '发布信息 8 折', '专属会员标识', '赠送大量发布额度']; + $benefitsText = implode("\n", $benefitsArr); + return [ 'fc_miniapp' => [ 'name' => 'fc_miniapp', @@ -70,6 +87,17 @@ class FcConfig self::item('fc_post_yearQuota', '年卡条数', 'number', $pick('post.yearQuota', '500'), '年卡包含的发布条数', 'required;number'), ], ], + 'fc_member' => [ + 'name' => 'fc_member', + 'title' => '会员配置', + 'active' => false, + 'list' => [ + self::item('fc_member_freeView', '每日免费查看条数', 'number', $freeView, '会员每日可免费查看联系方式条数(默认 20 条)', 'required;number'), + self::item('fc_member_postDiscount', '单条发布折扣', 'string', $postDiscount, '付费发布时会员专享折扣,如「8折」或「8.5折」(默认 8 折)', 'required'), + self::item('fc_fee_payView', '非会员查看价格', 'number', $payViewPrice, '非会员单条解锁联系方式费用(元,默认 1 元)', 'required;number'), + self::item('fc_member_benefits', '会员特权清单', 'text', $benefitsText, '小程序端展示的特权清单,每行一条(支持后台自由增删与修改)'), + ], + ], 'fc_dist' => [ 'name' => 'fc_dist', 'title' => '分销配置', @@ -113,6 +141,9 @@ class FcConfig if ($has('fc_post_')) { self::savePublish($row); } + if ($has('fc_member_') || $has('fc_fee_')) { + self::saveMember($row); + } if ($has('fc_dist_')) { self::saveDistribution($row); } @@ -254,6 +285,58 @@ class FcConfig ]); } + /** + * 会员权益与联系方式查看费用配置 + * + * @param array $row 系统配置页提交的 row 数组 + * @return void + * @throws \Exception + */ + protected static function saveMember(array $row) + { + $freeView = isset($row['fc_member_freeView']) ? max(0, (int)$row['fc_member_freeView']) : 20; + $postDiscount = isset($row['fc_member_postDiscount']) ? trim((string)$row['fc_member_postDiscount']) : '8 折'; + if ($postDiscount === '') { + $postDiscount = '8 折'; + } + $payView = isset($row['fc_fee_payView']) ? max(0, round((float)$row['fc_fee_payView'], 2)) : 1; + + $benefitsRaw = isset($row['fc_member_benefits']) ? trim((string)$row['fc_member_benefits']) : ''; + $benefits = []; + if ($benefitsRaw !== '') { + $lines = preg_split('/[\r\n]+/', $benefitsRaw); + foreach ($lines as $line) { + $line = trim($line); + if ($line !== '') { + $benefits[] = $line; + } + } + } + if (empty($benefits)) { + $benefits = [ + '每日免费查看 ' . $freeView . ' 条联系方式', + '发布信息 ' . $postDiscount . ' 专属优惠', + '专属会员标识', + '赠送大量发布额度', + ]; + } + + $current = []; + $raw = Db::name('fc_config')->where('key', 'member.benefit')->value('value'); + if ($raw) { + $decoded = json_decode($raw, true); + if (is_array($decoded)) { + $current = $decoded; + } + } + $current['freeView'] = $freeView; + $current['postDiscount'] = $postDiscount; + $current['benefits'] = $benefits; + + self::put('member.benefit', $current, 'member', '会员权益配置'); + self::put('fee.payView', $payView, 'fee', '非会员单条查看联系方式价格'); + } + /** * 分销参数 * diff --git a/application/common/library/OrderClose.php b/application/common/library/OrderClose.php new file mode 100644 index 0000000..824b334 --- /dev/null +++ b/application/common/library/OrderClose.php @@ -0,0 +1,167 @@ +where('key', 'order.timeout_minutes')->value('value'); + if ($minutes === null || $minutes === '') { + $minutes = Db::name('fc_config')->where('key', 'trade.order_timeout')->value('value'); + } + $val = (int)$minutes; + if ($val > 0) { + return $val * 60; + } + return self::DEFAULT_TIMEOUT_SECONDS; + } + + /** + * 判断订单是否超时 + * + * @param array $order fc_order 行 + * @param int|null $now 当前时间戳 + * @return bool + */ + public static function isExpired(array $order, $now = null) + { + if (empty($order) || (string)$order['status'] !== 'pending') { + return false; + } + $now = $now ?: time(); + $createtime = isset($order['createtime']) ? (int)$order['createtime'] : 0; + if ($createtime <= 0) { + return false; + } + $timeout = self::getTimeoutSeconds(); + return ($now - $createtime) >= $timeout; + } + + /** + * 关闭订单(原子操作,支持主动取消或超时自动关闭) + * + * @param array $order fc_order 行(至少含 id、order_no、user_id、type、status、related_id) + * @param string $reason 关闭原因:user_cancel(用户主动取消)、timeout_auto(超时自动关闭)、admin_cancel(后台取消) + * @return bool 是否成功关闭 + */ + public static function close(array $order, $reason = 'user_cancel') + { + $orderId = isset($order['id']) ? (int)$order['id'] : 0; + $orderNo = isset($order['order_no']) ? (string)$order['order_no'] : ''; + if ($orderId <= 0 || $orderNo === '') { + return false; + } + + $now = time(); + $closed = false; + + try { + Db::transaction(function () use ($orderId, $orderNo, $order, $reason, $now, &$closed) { + // 条件更新,保证只有 pending 状态的订单才能转为 closed,天然防并发与重复关闭 + $affected = Db::name('fc_order') + ->where('id', $orderId) + ->where('status', 'pending') + ->update([ + 'status' => 'closed', + 'refund_reason' => ($reason === 'user_cancel' ? '用户主动取消' : ($reason === 'timeout_auto' ? '超时未支付自动关闭' : '管理员关闭')), + 'updatetime' => $now, + ]); + + if (!$affected) { + $closed = false; + return; + } + + $closed = true; + + // 1. 释放优惠券(若有) + CouponService::release($orderNo); + + // 2. 关联业务的回收处理 + $type = isset($order['type']) ? (string)$order['type'] : ''; + $relatedId = isset($order['related_id']) ? (int)$order['related_id'] : 0; + + // 若是未付款的发布信息,将关联的草稿或帖子置为下架/关闭,避免未付款直接审核显示 + if ($type === 'post' && $relatedId > 0) { + Db::name('fc_post') + ->where('id', $relatedId) + ->where('user_id', (int)$order['user_id']) + ->where('audit_status', 0) // 仅当尚未审核通过时 + ->update([ + 'status' => 0, // 设为下架/关闭 + 'updatetime' => $now, + ]); + } + + // 3. 发送站内消息通知 + $userId = isset($order['user_id']) ? (int)$order['user_id'] : 0; + if ($userId > 0) { + $title = $reason === 'user_cancel' ? '订单已取消' : '订单已超时关闭'; + $orderTitle = isset($order['title']) ? (string)$order['title'] : '同城订单'; + $content = $reason === 'user_cancel' + ? "您的订单【{$orderTitle}】(订单号:{$orderNo})已主动取消。" + : "您的订单【{$orderTitle}】(订单号:{$orderNo})因超过支付时效已自动关闭,锁定的优惠券已退回。"; + + Db::name('fc_message')->insert([ + 'user_id' => $userId, + 'type' => 'system', + 'title' => $title, + 'content' => $content, + 'isread' => 0, + 'readtime' => null, + 'createtime' => $now, + ]); + } + }); + } catch (\Throwable $e) { + Log::record("订单关闭失败:订单 {$orderNo},原因:{$e->getMessage()}", 'error'); + return false; + } + + return $closed; + } + + /** + * 惰性检查并在超时时代为关闭 + * + * @param array $order fc_order 行 + * @return array 最新订单数据 + */ + public static function checkAndCloseIfExpired(array $order) + { + if (empty($order) || (string)$order['status'] !== 'pending') { + return $order; + } + + if (self::isExpired($order)) { + if (self::close($order, 'timeout_auto')) { + $order['status'] = 'closed'; + $order['updatetime'] = time(); + } + } + + return $order; + } +} diff --git a/application/route.php b/application/route.php index 5b71e2a..91e6db6 100755 --- a/application/route.php +++ b/application/route.php @@ -51,7 +51,8 @@ return [ 'api/v1/home/locate' => 'api/Home/locate', /* =============== 三、用户端 · 信息发布与详情(长路径优先) =============== */ - 'api/v1/post/:id/pay-view' => 'api/Post/payView', + 'api/v1/post/:id/pay-view' => 'api/Post/payView', + 'api/v1/post/comment/:id/reply' => 'api/Post/replyComment', 'api/v1/post/:id/comment' => 'api/Post/comment', 'api/v1/post/:id/comments' => 'api/Post/comments', 'api/v1/post/create' => 'api/Post/create', @@ -99,6 +100,8 @@ return [ * · 本组均为静态路径,与上面的 message/:id(\d+)及下面的 post/:id 无交叉。 */ 'api/v1/order/recharge' => 'api/Order/recharge', + 'api/v1/order/cancel' => 'api/Order/cancel', + 'api/v1/order/detail' => 'api/Order/detail', 'api/v1/pay/prepay' => 'api/Pay/prepay', 'api/v1/pay/status' => 'api/Pay/status', 'api/v1/pay/notify' => 'api/Pay/notifyCallback', diff --git a/cookie.txt b/cookie.txt new file mode 100644 index 0000000..aebdc87 --- /dev/null +++ b/cookie.txt @@ -0,0 +1,5 @@ +# Netscape HTTP Cookie File +# https://curl.se/docs/http-cookies.html +# This file was generated by libcurl! Edit at your own risk. + +127.0.0.1 FALSE / FALSE 0 PHPSESSID nmpg9honta1817mb0b59kks3e9 diff --git a/docs/测试用例.md b/docs/测试用例.md new file mode 100644 index 0000000..2440fa1 --- /dev/null +++ b/docs/测试用例.md @@ -0,0 +1,297 @@ +# 同城分类信息与便民服务平台 · 全功能系统测试用例文档 + +> **版本**:v1.3.0 +> **编写时间**:2026-10-23 +> **适用范围**:Uni-app 微信小程序端、FastAdmin PC 管理端、ThinkPHP 5.0.29 后端 API 接口契约、MySQL 5.7 业务数据流 +> **遵循规范**:`docs/05-后端实现规范.md`、`docs/06-后台字段中文化规范.md`、`AGENTS.md` 业务与春绿主题设计规范 + +--- + +## 一、 测试概述与环境准备 + +### 1.1 系统架构与测试范围 +本平台定位为基于 LBS 地理位置的**同城分类信息与本地生活便民服务平台**,涵盖招聘求职、房产租售、二手闲置、车辆买卖、本地生活等业务。系统包含两大终端: +1. **用户端(Uni-app 微信小程序)**:春绿(Spring Green)设计主题,提供浏览、搜索、发布、支付、会员、分销、互动等功能。 +2. **管理端(FastAdmin PC 后台)**:提供审核、用户/商家治理、财务资金流水、分销提现、系统参数与发布模式配置等功能。 + +### 1.2 测试环境与账号矩阵 + +| 角色 | 账号标识 / OpenID | 初始状态 / 权限 | 用途 | +| :--- | :--- | :--- | :--- | +| **超级管理员** | `admin` | 超级管理员权限(ID: 1),全功能菜单 | 平台审核、参数配置、财务审批、置顶管理 | +| **普通个人用户 A** | `wx_user_test_01` | 未开通会员,余额 0,未认证商家 | 测试基础登录、免费发帖、付费查看、开通会员 | +| **普通个人用户 B** | `wx_user_test_02` | 由用户 A 邀请注册(带 invite_code) | 测试二级分销关系绑定、消费佣金结算 | +| **已认证商家用户** | `wx_merchant_01` | 商家认证通过(`user_type=2`),店铺正常 | 测试商家发帖、展示营业执照与认证标识 | +| **VIP 会员用户** | `wx_vip_user_01` | 已开通月卡套餐,拥有发布额度与免费查看次数 | 测试会员免单发帖、会员查看、单条折扣计算 | + +### 1.3 核心业务规则对应索引(BR-01 ~ BR-16) + +| 规则编号 | 核心业务规则要求 | 触发端点 / 对应断言 | +| :--- | :--- | :--- | +| **BR-01** | 商家身份切换必须上传营业执照完成资质认证 | `POST /auth/switch-type` 缺执照报「商家用户需上传营业执照完成资质认证」 | +| **BR-02** | 手机号格式必须满足 `^1\d{10}$` 正则表达式 | `POST /auth/bind-phone` 格式错误报「手机号格式不正确」 | +| **BR-03** | 发布信息必填 categoryId、title、content,标题≤40字,内容≤500字 | `POST /post/create` 缺项或超长时报错 | +| **BR-04** | 新发布信息默认状态为待审核(audit_status=0),审核通过方可公开展示 | 前台列表过滤 `audit_status=1 AND status=1 AND deletetime IS NULL` | +| **BR-05** | 付费查看仅支持 `wxpay`(微信)与 `balance`(余额) | `POST /post/:id/pay-view` 非法方式报「支付方式不支持」 | +| **BR-06** | 评价评分必须在 1-5 之间,且评价内容不可为空 | `POST /post/:id/comment` 报「评分需在 1-5 之间」/「评价内容不能为空」 | +| **BR-07** | 开通会员套餐 planId 不能为空 | `POST /member/open` 缺项报「请选择会员套餐」 | +| **BR-08** | 一级分佣与二级分佣比例总和不能超过 100% | `POST /admin/dist/setting` 超标报「一级+二级分佣比例不能超过 100%」 | +| **BR-09** | 提现金额需 `>0` 且 `≤可提现余额`;管理端驳回必须填写驳回原因 | 提现超额报「提现金额超过可提现余额」;驳回缺原因报「驳回需填写原因」 | +| **BR-10** | 订单退款金额必须 `> 0` | `POST /admin/order/:id/refund` 金额非法报「退款金额需大于 0」 | +| **BR-11** | 审核操作状态仅支持 1(通过)和 2(驳回) | 审核提交非法值报「审核状态仅支持通过(1)/驳回(2)」 | +| **BR-12** | 新建管理角色必须填写角色名称并至少分配一项权限节点 | `POST /admin/role` 报「请填写角色名称」/「请至少分配一项权限」 | +| **BR-13** | 发布计费受控:套餐额度 → 每日免费条数 → 单条付费,三者皆无拒绝发布 | 免费模式全免;付费模式逐级核销扣除 | +| **BR-14** | 发布金额服务端定价,客户端传入的 amount/paid 被忽略,防篡改 | `POST /post/create` 金额强制由后台 `post.*` 配置计算 | +| **BR-15** | 置顶优先:置顶(`is_top=1`)信息在首页与「我的发布」中排序绝对置顶 | 列表按 `is_top DESC, updatetime DESC, id DESC` 排序 | +| **BR-16** | 会员单条付费按折扣计算:`round(singlePrice × discountRate, 2)` | 会员享受单条发帖优惠,由服务端严格折算 | + +--- + +## 二、 核心业务闭环端到端测试(E2E Flows) + +### E2E-01:同城信息发布与全链路审核流转测试 + +```mermaid +flowchart TD + A[用户填写发布内容] --> B[选择服务区域与分类] + B --> C{系统发布模式} + C -->|free 免费模式| D[生成待审核信息 audit=0] + C -->|paid 付费模式| E{是否扣减额度/支付} + E -->|扣减会员条数/单条付款| D + D --> F[管理后台待审列表 AU1] + F -->|管理员审核通过| G[状态变更为 audit=1, status=1] + G --> H[分类 post_count +1] + G --> I[发布者收到系统审核通过通知] + G --> J[前台首页/分类信息流可见] + F -->|管理员驳回并填写原因| K[状态变更为 audit=2] + K --> L[发布者收到驳回通知及原因] +``` + +- **用例编号**:`TC-E2E-001` +- **优先级**:P0 +- **测试步骤**: + 1. 普通用户登录小程序,进入发布页面 `pages/post/create`。 + 2. 填写标题《九成新家用洗衣机转让》,选择分类「二手闲置」,填写价格 450 元,上传 2 张实物图,选择所在服务区域「云岩区」,填写联系电话。 + 3. 点击提交,验证前端防重复提交状态,请求 `POST /api/v1/post/create`。 + 4. 检查响应数据中 `auditStatus` 是否为 0(待审核),信息不出现在小程序公共列表。 + 5. 登录 PC 管理端,进入「信息管理 → 信息列表」或调用 `GET /api/v1/admin/posts/pending`,确认该记录在待审核列表中。 + 6. 管理员点击「审核通过」(`POST /api/v1/admin/post/:id/audit`,`auditStatus=1`)。 + 7. 刷新小程序首页信息流 `GET /api/v1/home/feed`,验证新发布信息正确展示在信息流顶部;进入通知中心,验证收到「信息审核通过」系统通知。 + 8. 测试驳回分支:发布另一条信息,后台选择驳回并输入原因「含违规引流广告」,验证发布者收到包含驳回原因的站内通知。 + +--- + +### E2E-02:电话脱敏与付费查看联系方式闭环 + +- **用例编号**:`TC-E2E-002` +- **优先级**:P0 +- **测试步骤**: + 1. 用户 A 发布一条信息,联系电话为 `13800138000`。 + 2. 用户 B 未登录访问该信息详情页 `GET /api/v1/post/:id`,断言返回的 contact 为脱敏手机号(例如 `138****8000`),无一键拨号明文。 + 3. 用户 B 登录,点击「查看完整联系方式」,弹出付费确认窗口(单次查看金额 1.00 元)。 + 4. 用户 B 选择微信支付或账户余额支付,调用 `POST /api/v1/post/:id/pay-view`。 + 5. 支付成功后,数据库 `fa_fc_pay_view` 写入解锁记录,扣除用户相应金额并记入 `fa_fc_finance_flow`。 + 6. 再次进入该信息详情页,断言接口返回 `unlocked: true`,页面直接展示完整明文电话并支持一键拨打,无需重复扣费。 + +--- + +### E2E-03:会员开通与发布额度核销闭环 + +- **用例编号**:`TC-E2E-003` +- **优先级**:P0 +- **测试步骤**: + 1. 用户进入「会员中心」`pages/member/index`,拉取套餐列表 `GET /api/v1/member/plans`。 + 2. 选择「月卡会员(含 30 条发布额度)」,点击开通 `POST /api/v1/member/open`。 + 3. 模拟微信支付成功回调 `POST /api/v1/pay/notify`。 + 4. 验证用户扩展表 `fa_fc_user_ext` 中 `is_member=1`、`member_expiretime` 增加 30 天,`post_quota_left` 增加 30 条。 + 5. 切换系统发布模式为付费模式(`post.mode=paid`)。 + 6. 用户发布新信息,系统自动匹配并扣除套餐额度(`post_quota_left` 变为 29),无需额外唤起支付。 + +--- + +### E2E-04:二级分销绑定与裂变佣金提现流转 + +- **用例编号**:`TC-E2E-004` +- **优先级**:P0 +- **测试步骤**: + 1. 用户 A 分享专属小程序邀请码 / 邀请链接给新用户 B。 + 2. 用户 B 通过带参数的场景值扫码登录,系统自动在 `fa_fc_dist_relation` 建立关系(A 为 B 的一级上线)。 + 3. 用户 B 在平台开通会员套餐(实付金额 100 元)。 + 4. 系统触发分销结算,根据后台配置的分佣比例(假设一级 20%),为用户 A 生成佣金明细 `fa_fc_commission`,金额 20 元,并增加 A 的可提现余额。 + 5. 用户 A 进入「分销中心」提交提现申请 `POST /api/v1/distribution/withdraw`,申请提现 20 元。 + 6. 管理后台「交易财务 → 提现审核」显示该笔申请(`status=auditing`)。 + 7. 管理员点击打款通过,调用微信商家转账接口完成转账,状态更新为 `paid`,记录资金流水。 + +--- + +## 三、 用户端模块测试用例(Uni-app 小程序) + +### 3.1 认证与账号模块(Module-AUTH) + +| 用例编号 | 功能点 | 测试场景与输入 | 预期输出与断言 | 优先级 | +| :--- | :--- | :--- | :--- | :--- | +| `TC-AUTH-01` | 微信授权登录 | 用户首次打开小程序授权登录,携带有效 `code` | 返回 JWT Token、用户信息(`userInfo` 结构对齐 §四),`fa_fc_user_ext` 自动生成扩展行 | P0 | +| `TC-AUTH-02` | 登录入参校验 | 调用 `POST /auth/wx-login`,传入空参数 `{}` | HTTP 200,返回业务失败 `{"code":1, "message":"缺少微信授权 code"}` | P1 | +| `TC-AUTH-03` | 手机号快捷获取 | 点击 `'; + }).join('') + + '' + + '' + + ''; + + Layer.open({ + type: 1, + title: options.title || '填写原因', + area: ['460px', 'auto'], + content: html, + btn: [options.okText || '确定', '取消'], + yes: function (index, layero) { + var preset = $.trim(layero.find('.fc-reason__item.active').data('reason') || ''); + var extra = $.trim(layero.find('.fc-reason__text').val()); + var reason = preset ? (extra ? preset + ':' + extra : preset) : extra; + if (!reason) { + Toastr.warning('请选择或填写原因'); + return; + } + Layer.close(index); + callback(reason); + }, + success: function (layero) { + layero.find('.fc-reason__item').on('click', function () { + layero.find('.fc-reason__item').removeClass('active btn-success').addClass('btn-default'); + $(this).addClass('active btn-success').removeClass('btn-default'); + layero.find('.fc-reason__text').focus(); + }); + } + }); + }, + bindevent: function () { Form.api.bindevent($("form[role=form]")); } diff --git a/public/assets/js/backend/fc/postaudit.js b/public/assets/js/backend/fc/postaudit.js new file mode 100644 index 0000000..4f80940 --- /dev/null +++ b/public/assets/js/backend/fc/postaudit.js @@ -0,0 +1,344 @@ +define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], function ($, undefined, Backend, Table, Form, Format) { + + var Controller = { + /** + * 信息审核列表页面逻辑 + * + * 专职负责同城待审信息的核对、单条审核、一键通过/驳回与批量审核处理 + */ + index: function () { + // 初始化表格参数配置 + Table.api.init({ + extend: { + index_url: 'fc/postaudit/index' + location.search, + audit_url: 'fc/postaudit/audit', + table: 'fc_post', + } + }); + + var table = $("#table"); + var esc = Controller.api.esc; + var canAudit = table.data('operate-audit'); + + /** + * 打开审核工作台 + * + * @param {Object} row 行数据 + */ + function openAudit(row) { + Fast.api.open('fc/postaudit/detail?ids=' + row.id + '&mode=audit', '审核:' + esc(row.title), {area: ['860px', '88%']}); + } + + /** + * 打开只读详情 + * + * @param {Object} row 行数据 + */ + function openDetail(row) { + Fast.api.open('fc/postaudit/detail?ids=' + row.id + '&mode=view', '详情:' + esc(row.title), {area: ['860px', '88%']}); + } + + // 初始化表格 + table.bootstrapTable({ + url: $.fn.bootstrapTable.defaults.extend.index_url, + pk: 'id', + sortName: 'id', + sortOrder: 'desc', + escape: false, + clickToSelect: false, + commonSearch: true, + searchFormVisible: false, + fixedColumns: true, + fixedRightNumber: 1, + columns: [ + [ + {checkbox: true}, + { + field: 'id', + title: '序号', + operate: false, + sortable: false, + align: 'center', + width: '60px', + class: 'fc-serial', + table: table, + formatter: Format.serial + }, + { + field: 'cover', + title: '封面', + operate: false, + align: 'center', + width: '65px', + events: Table.api.events.image, + formatter: function (value, row) { + var img = value; + if (!img && row.media) { + try { + var raw = typeof row.media === 'string' ? row.media.trim() : ''; + if (raw.indexOf('[') === 0) { + var parsed = JSON.parse(raw); + img = parsed && parsed.length > 0 ? parsed[0] : ''; + } else if (raw) { + img = raw.split(',')[0]; + } + } catch (e) { + img = ''; + } + } + if (img) { + var fullUrl = Fast.api.cdnurl(img, true); + return ''; + } + return '无图'; + } + }, + { + field: 'title', + title: '标题', + operate: 'LIKE', + align: 'left', + events: { + 'click .btn-view-audit': function (e, value, row) { + e.stopPropagation(); + e.preventDefault(); + if (canAudit) { + openAudit(row); + } else { + openDetail(row); + } + } + }, + formatter: function (value, row) { + return '' + esc(value) + ''; + } + }, + { + field: 'category_id', + title: '分类', + addClass: 'selectpage', + extend: "data-source='fc/category/index' data-field='name'", + formatter: function (value, row) { + return row.category_name ? '' + esc(row.category_name) + '' : '-'; + } + }, + { + field: 'price', + title: '价格', + operate: 'BETWEEN', + align: 'right', + formatter: function (value, row) { + var unit = row.price_unit ? esc(row.price_unit) : ''; + if (value === null || value === '' || value === undefined || Number(value) === 0) { + return unit === '面议' + ? '面议' + : '免费'; + } + var num = Number(value).toLocaleString('zh-CN', {minimumFractionDigits: 2, maximumFractionDigits: 2}); + var suffix = (unit && unit !== '元') ? ' ' + unit + '' : ''; + return '¥' + num + '' + suffix; + } + }, + { + field: 'area', + title: '区域', + operate: 'LIKE', + formatter: function (value) { + return value ? esc(value) : '-'; + } + }, + { + field: 'user_id', + title: '发布用户', + addClass: 'selectpage', + extend: "data-source='user/user/index' data-field='nickname'", + formatter: function (value, row) { + return row.user_name ? ' ' + esc(row.user_name) : '-'; + } + }, + { + field: 'phone', + title: '联系电话', + operate: 'LIKE', + formatter: function (value, row) { + var contact = row.contact ? esc(row.contact) + ' ' : ''; + return value ? contact + esc(value) : '-'; + } + }, + { + field: 'audit_status', + title: '审核状态', + searchList: {0: '待审核', 1: '已通过', 2: '已驳回'}, + formatter: function (value) { + var map = { + 0: '待审核', + 1: '已通过', + 2: '已驳回' + }; + return map[value] || '未知'; + } + }, + { + field: 'createtime', + title: '提交时间', + operate: 'RANGE', + addclass: 'datetimerange', + autocomplete: false, + formatter: Table.api.formatter.datetime + }, + { + field: 'operate', + title: '操作', + align: 'center', + width: '100px', + table: table, + events: Table.api.events.operate, + formatter: Table.api.formatter.operate, + buttons: [ + { + name: 'audit', + text: '审核', + title: '进入审核工作台', + icon: '', + classname: 'btn btn-xs btn-success btn-click', + visible: function (row) { + return Number(row.audit_status) === 0 && canAudit; + }, + click: function (options, row) { + openAudit(row); + } + }, + { + name: 'detail', + text: '详情', + title: '查看详情', + icon: '', + classname: 'btn btn-xs btn-info btn-click', + click: function (options, row) { + openDetail(row); + } + } + ] + } + ] + ] + }); + + // 批量通过操作 + $('.toolbar').on('click', '.btn-batch-pass', function () { + var ids = Table.api.selectedids(table); + if (ids.length === 0) { + Toastr.warning('请先勾选要审核的信息'); + return; + } + Layer.confirm('确认批量通过选中的 ' + ids.length + ' 条信息并上架?', {title: '批量通过', btn: ['确认通过', '取消']}, function (index) { + Fast.api.ajax({ + url: 'fc/postaudit/pass', + data: {ids: ids.join(',')} + }, function () { + table.bootstrapTable('refresh'); + return false; + }); + Layer.close(index); + }); + }); + + // 批量驳回操作 + $('.toolbar').on('click', '.btn-batch-reject', function () { + var ids = Table.api.selectedids(table); + if (ids.length === 0) { + Toastr.warning('请先勾选要驳回的信息'); + return; + } + Controller.api.askReason({ + title: '批量驳回', + tip: '请选择或填写驳回原因,所选 ' + ids.length + ' 条信息发布者均会收到该通知。', + presets: ['内容不合规', '虚假信息', '违规联系方式', '重复发布', '类目选择错误'], + okText: '确认批量驳回' + }, function (reason) { + Fast.api.ajax({ + url: 'fc/postaudit/reject', + data: {ids: ids.join(','), reason: reason} + }, function () { + table.bootstrapTable('refresh'); + return false; + }); + }); + }); + + // 为表格绑定通用事件 + Table.api.bindevent(table); + + // 双击行直接打开审核工作台 + table.on('dbl-click-row.bs.table', function (e, row) { + if (canAudit) { + openAudit(row); + } else { + openDetail(row); + } + }); + }, + + api: { + /** + * HTML 转义防注入 + * + * @param {string} value + * @return {string} + */ + esc: function (value) { + return String(value === null || value === undefined ? '' : value).replace(/[&<>"']/g, function (c) { + return {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''}[c]; + }); + }, + + /** + * 填写原因弹窗组件(驳回共用) + * + * @param {Object} options {title, tip, presets, okText} + * @param {Function} callback function(reason) + */ + askReason: function (options, callback) { + var presets = options.presets || []; + var esc = Controller.api.esc; + var html = '
' + + '

' + esc(options.tip || '请选择或填写原因') + '

' + + '
' + + presets.map(function (item) { + return ''; + }).join('') + + '
' + + '' + + '
'; + + Layer.open({ + type: 1, + title: options.title || '填写原因', + area: ['460px', 'auto'], + content: html, + btn: [options.okText || '确定', '取消'], + yes: function (index, layero) { + var preset = $.trim(layero.find('.fc-reason__item.active').data('reason') || ''); + var extra = $.trim(layero.find('.fc-reason__text').val()); + var reason = preset ? (extra ? preset + ':' + extra : preset) : extra; + if (!reason) { + Toastr.warning('请选择或填写原因'); + return; + } + Layer.close(index); + callback(reason); + }, + success: function (layero) { + layero.find('.fc-reason__item').on('click', function () { + layero.find('.fc-reason__item').removeClass('active btn-success').addClass('btn-default'); + $(this).addClass('active btn-success').removeClass('btn-default'); + layero.find('.fc-reason__text').focus(); + }); + } + }); + } + } + }; + + return Controller; +}); diff --git a/public/assets/js/backend/fc/userext.js b/public/assets/js/backend/fc/userext.js index bc6e178..4317dc6 100644 --- a/public/assets/js/backend/fc/userext.js +++ b/public/assets/js/backend/fc/userext.js @@ -70,7 +70,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'backend/format'], fu buttons: [ { name: 'recharge', - text: '余额充值', + text: '充值', title: '余额充值', classname: 'btn btn-xs btn-warning btn-click', click: function (options, row) { diff --git a/public/assets/js/backend/general/attachment.js b/public/assets/js/backend/general/attachment.js index a897ec0..f92e190 100755 --- a/public/assets/js/backend/general/attachment.js +++ b/public/assets/js/backend/general/attachment.js @@ -170,7 +170,7 @@ define(['jquery', 'bootstrap', 'backend', 'form', 'table', 'backend/format'], fu Fast.api.close($.extend({multiple: multiple}, row)); }, }, formatter: function () { - return ' ' + __('Choose') + ''; + return '' + __('Choose') + ''; } } ] diff --git a/public/assets/js/backend/wechat/response.js b/public/assets/js/backend/wechat/response.js index 389a7b7..b8b7152 100755 --- a/public/assets/js/backend/wechat/response.js +++ b/public/assets/js/backend/wechat/response.js @@ -63,7 +63,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'adminlte', 'backend/ Fast.api.close(row); }, }, formatter: function () { - return ' ' + __('Choose') + ''; + return '' + __('Choose') + ''; } } ] diff --git a/public/assets/js/require-backend.min.js b/public/assets/js/require-backend.min.js index 9e5e41d..f0f7757 100755 --- a/public/assets/js/require-backend.min.js +++ b/public/assets/js/require-backend.min.js @@ -71,7 +71,7 @@ this.$selectItem.filter(":enabled").prop("checked",t),this.updateRows(),t&&(e=th return"function"!=typeof this.options.renameFile?t.name:this.options.renameFile(t)}},{key:"getFallbackForm",value:function(){var t,i;if(t=this.getExistingFallback())return t;var n='
';this.options.dictFallbackText&&(n+="

".concat(this.options.dictFallbackText,"

")),n+='
');var o=e.createElement(n);return"FORM"!==this.element.tagName?(i=e.createElement('')),i.appendChild(o)):(this.element.setAttribute("enctype","multipart/form-data"),this.element.setAttribute("method",this.options.method)),null!=i?i:o}},{key:"getExistingFallback",value:function(){for(var t=0,e=["div","form"];t0){for(var n=["tb","gb","mb","kb","b"],o=0;o=Math.pow(this.options.filesizeBase,4-o)/10){e=t/Math.pow(this.options.filesizeBase,4-o),i=a;break}}e=Math.round(10*e)/10}return"".concat(e," ").concat(this.options.dictFileSizeUnits[i])}},{key:"_updateMaxFilesReachedClass",value:function(){return null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(this.getAcceptedFiles().length===this.options.maxFiles&&this.emit("maxfilesreached",this.files),this.element.classList.add("dz-max-files-reached")):this.element.classList.remove("dz-max-files-reached")}},{key:"drop",value:function(t){if(t.dataTransfer){this.emit("drop",t);for(var e=[],i=0;i0){var o=!0,a=!1,s=void 0;try{for(var r,l=n[Symbol.iterator]();!(o=(r=l.next()).done);o=!0){var c=r.value;c.isFile?c.file(function(t){if(!i.options.ignoreHiddenFiles||"."!==t.name.substring(0,1))return t.fullPath="".concat(e,"/").concat(t.name),i.addFile(t)}):c.isDirectory&&i._addFilesFromDirectory(c,"".concat(e,"/").concat(c.name))}}catch(t){a=!0,s=t}finally{try{o||null==l.return||l.return()}finally{if(a)throw s}}t()}return null},o)}()}},{key:"accept",value:function(t,i){this.options.maxFilesize&&t.size>1024*this.options.maxFilesize*1024?i(this.options.dictFileTooBig.replace("{{filesize}}",Math.round(t.size/1024/10.24)/100).replace("{{maxFilesize}}",this.options.maxFilesize)):e.isValidFile(t,this.options.acceptedFiles)?null!=this.options.maxFiles&&this.getAcceptedFiles().length>=this.options.maxFiles?(i(this.options.dictMaxFilesExceeded.replace("{{maxFiles}}",this.options.maxFiles)),this.emit("maxfilesexceeded",t)):this.options.accept.call(this,t,i):i(this.options.dictInvalidFileType)}},{key:"addFile",value:function(t){var i=this;t.upload={uuid:e.uuidv4(),progress:0,total:t.size,bytesSent:0,filename:this._renameFile(t)},this.files.push(t),t.status=e.ADDED,this.emit("addedfile",t),this._enqueueThumbnail(t),this.accept(t,function(e){e?(t.accepted=!1,i._errorProcessing([t],e)):(t.accepted=!0,i.options.autoQueue&&i.enqueueFile(t)),i._updateMaxFilesReachedClass()})}},{key:"enqueueFiles",value:function(t){var e=!0,i=!1,n=void 0;try{for(var o,a=t[Symbol.iterator]();!(e=(o=a.next()).done);e=!0){var s=o.value;this.enqueueFile(s)}}catch(t){i=!0,n=t}finally{try{e||null==a.return||a.return()}finally{if(i)throw n}}return null}},{key:"enqueueFile",value:function(t){var i=this;if(t.status!==e.ADDED||!0!==t.accepted)throw new Error("This file can't be queued because it has already been processed or was rejected.");if(t.status=e.QUEUED,this.options.autoProcessQueue)return setTimeout(function(){return i.processQueue()},0)}},{key:"_enqueueThumbnail",value:function(t){var e=this;if(this.options.createImageThumbnails&&t.type.match(/image.*/)&&t.size<=1024*this.options.maxThumbnailFilesize*1024)return this._thumbnailQueue.push(t),setTimeout(function(){return e._processThumbnailQueue()},0)}},{key:"_processThumbnailQueue",value:function(){var t=this;if(!this._processingThumbnail&&0!==this._thumbnailQueue.length){this._processingThumbnail=!0;var e=this._thumbnailQueue.shift();return this.createThumbnail(e,this.options.thumbnailWidth,this.options.thumbnailHeight,this.options.thumbnailMethod,!0,function(i){return t.emit("thumbnail",e,i),t._processingThumbnail=!1,t._processThumbnailQueue()})}}},{key:"removeFile",value:function(t){if(t.status===e.UPLOADING&&this.cancelUpload(t),this.files=m(this.files,t),this.emit("removedfile",t),0===this.files.length)return this.emit("reset")}},{key:"removeAllFiles",value:function(t){null==t&&(t=!1);var i=!0,n=!1,o=void 0;try{for(var a,s=this.files.slice()[Symbol.iterator]();!(i=(a=s.next()).done);i=!0){var r=a.value;(r.status!==e.UPLOADING||t)&&this.removeFile(r)}}catch(t){n=!0,o=t}finally{try{i||null==s.return||s.return()}finally{if(n)throw o}}return null}},{key:"resizeImage",value:function(t,i,n,o,a){var s=this;return this.createThumbnail(t,i,n,o,!0,function(i,n){if(null==n)return a(t);var o=s.options.resizeMimeType;null==o&&(o=t.type);var r=n.toDataURL(o,s.options.resizeQuality);return"image/jpeg"!==o&&"image/jpg"!==o||(r=b.restore(t.dataURL,r)),a(e.dataURItoBlob(r))})}},{key:"createThumbnail",value:function(t,e,i,n,o,a){var s=this,r=new FileReader;r.onload=function(){if(t.dataURL=r.result,"image/svg+xml"===t.type)return void(null!=a&&a(r.result));s.createThumbnailFromUrl(t,e,i,n,o,a)},r.readAsDataURL(t)}},{key:"displayExistingFile",value:function(t,e,i,n){var o=this,a=!(arguments.length>4&&void 0!==arguments[4])||arguments[4];if(this.emit("addedfile",t),this.emit("complete",t),a){var s=function(e){o.emit("thumbnail",t,e),i&&i()};t.dataURL=e,this.createThumbnailFromUrl(t,this.options.thumbnailWidth,this.options.thumbnailHeight,this.options.resizeMethod,this.options.fixOrientation,s,n)}else this.emit("thumbnail",t,e),i&&i()}},{key:"createThumbnailFromUrl",value:function(t,e,i,n,o,a,s){var r=this,l=document.createElement("img");return s&&(l.crossOrigin=s),l.onload=function(){var s=function(t){return t(1)};return"undefined"!=typeof EXIF&&null!==EXIF&&o&&(s=function(t){return EXIF.getData(l,function(){return t(EXIF.getTag(this,"Orientation"))})}),s(function(o){t.width=l.width,t.height=l.height;var s=r.options.resize.call(r,t,e,i,n),c=document.createElement("canvas"),d=c.getContext("2d");switch(c.width=s.trgWidth,c.height=s.trgHeight,o>4&&(c.width=s.trgHeight,c.height=s.trgWidth),o){case 2:d.translate(c.width,0),d.scale(-1,1);break;case 3:d.translate(c.width,c.height),d.rotate(Math.PI);break;case 4:d.translate(0,c.height),d.scale(1,-1);break;case 5:d.rotate(.5*Math.PI),d.scale(1,-1);break;case 6:d.rotate(.5*Math.PI),d.translate(0,-c.width);break;case 7:d.rotate(.5*Math.PI),d.translate(c.height,-c.width),d.scale(-1,1);break;case 8:d.rotate(-.5*Math.PI),d.translate(-c.height,0)}y(d,l,null!=s.srcX?s.srcX:0,null!=s.srcY?s.srcY:0,s.srcWidth,s.srcHeight,null!=s.trgX?s.trgX:0,null!=s.trgY?s.trgY:0,s.trgWidth,s.trgHeight);var u=c.toDataURL("image/png");if(null!=a)return a(u,c)})},null!=a&&(l.onerror=a),l.src=t.dataURL}},{key:"processQueue",value:function(){var t=this.options.parallelUploads,e=this.getUploadingFiles().length,i=e;if(!(e>=t)){var n=this.getQueuedFiles();if(n.length>0){if(this.options.uploadMultiple)return this.processFiles(n.slice(0,t-e));for(;i1?e-1:0),n=1;ni.options.chunkSize),t[0].upload.totalChunkCount=Math.ceil(o.size/i.options.chunkSize)}if(t[0].upload.chunked){var a=t[0],s=n[0],r=0;a.upload.chunks=[];var l=function(){for(var n=0;void 0!==a.upload.chunks[n];)n++;if(!(n>=a.upload.totalChunkCount)){r++;var o=n*i.options.chunkSize,l=Math.min(o+i.options.chunkSize,a.size),c={name:i._getParamName(0),data:s.webkitSlice?s.webkitSlice(o,l):s.slice(o,l),filename:a.upload.filename,chunkIndex:n};a.upload.chunks[n]={file:a,index:n,dataBlock:c,status:e.UPLOADING,progress:0,retries:0},i._uploadData(t,[c])}};if(a.upload.finishedChunkUpload=function(n,o){var s=!0;n.status=e.SUCCESS,i.options.chunkSuccess.call(i,n,a,o),n.dataBlock=null,n.xhr=null;for(var r=0;r=s;r?a++:a--)o[a]=e.charCodeAt(a);return new Blob([n],{type:i})};var m=function(t,e){return t.filter(function(t){return t!==e}).map(function(t){return t})},g=function(t){return t.replace(/[\-_](\w)/g,function(t){return t.charAt(1).toUpperCase()})};f.createElement=function(t){var e=document.createElement("div");return e.innerHTML=t,e.childNodes[0]},f.elementInside=function(t,e){if(t===e)return!0;for(;t=t.parentNode;)if(t===e)return!0;return!1},f.getElement=function(t,e){var i;if("string"==typeof t?i=document.querySelector(t):null!=t.nodeType&&(i=t),null==i)throw new Error("Invalid `".concat(e,"` option provided. Please provide a CSS selector or a plain HTML element."));return i},f.getElements=function(t,e){var i,n;if(t instanceof Array){n=[];try{var o=!0,a=!1,s=void 0;try{for(var r,l=t[Symbol.iterator]();!(o=(r=l.next()).done);o=!0)i=r.value,n.push(this.getElement(i,e))}catch(t){a=!0,s=t}finally{try{o||null==l.return||l.return()}finally{if(a)throw s}}}catch(t){n=null}}else if("string"==typeof t){n=[];var c=!0,d=!1,u=void 0;try{for(var h,p=document.querySelectorAll(t)[Symbol.iterator]();!(c=(h=p.next()).done);c=!0)i=h.value,n.push(i)}catch(t){d=!0,u=t}finally{try{c||null==p.return||p.return()}finally{if(d)throw u}}}else null!=t.nodeType&&(n=[t]);if(null==n||!n.length)throw new Error("Invalid `".concat(e,"` option provided. Please provide a CSS selector, a plain HTML element or a list of those."));return n},f.confirm=function(t,e,i){return window.confirm(t)?e():null!=i?i():void 0},f.isValidFile=function(t,e){if(!e)return!0;e=e.split(",");var i=t.type,n=i.replace(/\/.*$/,""),o=!0,a=!1,s=void 0;try{for(var r,l=e[Symbol.iterator]();!(o=(r=l.next()).done);o=!0){var c=r.value;if(c=c.trim(),"."===c.charAt(0)){if(-1!==t.name.toLowerCase().indexOf(c.toLowerCase(),t.name.length-c.length))return!0}else if(/\/\*$/.test(c)){if(n===c.replace(/\/.*$/,""))return!0}else if(i===c)return!0}}catch(t){a=!0,s=t}finally{try{o||null==l.return||l.return()}finally{if(a)throw s}}return!1},void 0!==t&&null!==t&&(t.fn.dropzone=function(t){return this.each(function(){return new f(this,t)})}),void 0!==h&&null!==h?h.exports=f:window.Dropzone=f,f.ADDED="added",f.QUEUED="queued",f.ACCEPTED=f.QUEUED,f.UPLOADING="uploading",f.PROCESSING=f.UPLOADING,f.CANCELED="canceled",f.ERROR="error",f.SUCCESS="success";var v=function(t){var e=(t.naturalWidth,t.naturalHeight),i=document.createElement("canvas");i.width=1,i.height=e;var n=i.getContext("2d");n.drawImage(t,0,0);for(var o=n.getImageData(1,0,1,e),a=o.data,s=0,r=e,l=e;l>s;){0===a[4*(l-1)+3]?r=l:s=l,l=r+s>>1}var c=l/e;return 0===c?1:c},y=function(t,e,i,n,o,a,s,r,l,c){var d=v(e);return t.drawImage(e,i,n,o,a,s,r,l,c/d)},b=function(){function t(){r(this,t)}return c(t,null,[{key:"initClass",value:function(){this.KEY_STR="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="}},{key:"encode64",value:function(t){for(var e="",i=void 0,n=void 0,o="",a=void 0,s=void 0,r=void 0,l="",c=0;;)if(i=t[c++],n=t[c++],o=t[c++],a=i>>2,s=(3&i)<<4|n>>4,r=(15&n)<<2|o>>6,l=63&o,isNaN(n)?r=l=64:isNaN(o)&&(l=64),e=e+this.KEY_STR.charAt(a)+this.KEY_STR.charAt(s)+this.KEY_STR.charAt(r)+this.KEY_STR.charAt(l),i=n=o="",a=s=r=l="",!(ct.length)break}return i}},{key:"decode64",value:function(t){var e=void 0,i=void 0,n="",o=void 0,a=void 0,s=void 0,r="",l=0,c=[],d=/[^A-Za-z0-9\+\/\=]/g;for(d.exec(t)&&console.warn("There were invalid base64 characters in the input text.\nValid base64 characters are A-Z, a-z, 0-9, '+', '/',and '='\nExpect errors in decoding."),t=t.replace(/[^A-Za-z0-9\+\/\=]/g,"");;)if(o=this.KEY_STR.indexOf(t.charAt(l++)),a=this.KEY_STR.indexOf(t.charAt(l++)),s=this.KEY_STR.indexOf(t.charAt(l++)),r=this.KEY_STR.indexOf(t.charAt(l++)),e=o<<2|a>>4,i=(15&a)<<4|s>>2,n=(3&s)<<6|r,c.push(e),64!==s&&c.push(i),64!==r&&c.push(n),e=i=n="",o=a=s=r="",!(l\';this.onerror=null;" class="img-responsive">'},events:{onInit:function(){},onUploadSuccess:function(e,i,n){var a=e.element,s=e.options.onUploadSuccess,r=void 0!==i.data?i.data:null;if(a){var l=t(a).data("input-id")?t(a).data("input-id"):"";if(l){var c=[],d=t("#"+l);t(a).data("multiple")&&""!==d.val()&&c.push(d.val());var u=Config.upload.fullmode?r.fullurl?r.fullurl:Fast.api.cdnurl(r.url):r.url;c.push(u),d.val(c.join(",")).trigger("change").trigger("validate")}var h=t(a).data("upload-success");if(h&&("function"!=typeof h&&"function"==typeof o.api.custom[h]&&(h=o.api.custom[h]),"function"==typeof h)){var p=h.call(a,r,i,e,n);if(!1===p)return}}if("function"==typeof s){var p=s.call(a,r,i,e,n);if(!1===p)return}},onUploadError:function(e,i,n){var a=e.element,s=e.options.onUploadError,r=void 0!==i.data?i.data:null;if(a){var l=t(a).data("upload-error");if(l&&("function"!=typeof l&&"function"==typeof o.api.custom[l]&&(l=o.api.custom[l]),"function"==typeof l)){var c=l.call(a,r,i,e,n);if(!1===c)return}}if("function"==typeof s){var c=s.call(a,r,i,e,n);if(!1===c)return}Toastr.error(i.msg.toString().replace(/(<([^>]+)>)/gi,"")+"(code:"+i.code+")")},onUploadResponse:function(e,i,n){try{var o="object"==typeof e?e:JSON.parse(e);o.hasOwnProperty("code")||t.extend(o,{code:-2,msg:e,data:null})}catch(t){var o={code:-1,msg:t.message,data:null}}return o},onUploadComplete:function(e,i){var n=e.element,a=e.options.onUploadComplete;if(n){var s=t(n).data("upload-complete");if(s&&("function"!=typeof s&&"function"==typeof o.api.custom[s]&&(s=o.api.custom[s]),"function"==typeof s)){var r=s.call(n,i);if(!1===r)return}}if("function"==typeof a){var r=a.call(n,i);if(!1===r)return}}},api:{upload:function(e,a,s,r){e=void 0===e?o.config.classname:e,t(e,o.config.container).each(function(){if(t(this).attr("initialized"))return!0;t(this).attr("initialized",!0);var e=this,l=t(this).prop("id")||t(this).prop("name")||i.uuidv4(),c=t(this).data("url"),d=t(this).data("maxsize"),u=t(this).data("maxcount"),h=t(this).data("mimetype"),p=t(this).data("multipart"),f=t(this).data("multiple"),m=t(e).data("input-id")?t(e).data("input-id"):"",g=t(e).data("preview-id")?t(e).data("preview-id"):"";c=c||Config.upload.uploadurl,c=Fast.api.fixurl(c);var v=Config.upload.chunksize||2097152,y=Config.upload.timeout||6e5;d=void 0!==d?d:Config.upload.maxsize,h=void 0!==h?h:Config.upload.mimetype,p=void 0!==p?p:Config.upload.multipart,f=void 0!==f?f:Config.upload.multiple,h=h.split(",").map(function(t){return t.indexOf("/")>-1?t:t&&"*"!==t&&"."!==t.charAt(0)?"."+t:t}).join(","),h="*"===h?null:h;var b=function(t){var e=t.toString().match(/^([0-9\.]+)(\w+)$/),i=e?parseFloat(e[1]):parseFloat(t),n=e?e[2].toLowerCase():"b",o={b:0,k:1,kb:1,m:2,mb:2,gb:3,g:3,tb:4,t:4},a=void 0!==o[n]?o[n]:0;return i*Math.pow(1024,a)/Math.pow(1024,2)}(d),w=t(this).data()||{};w=t.extend(!0,{},w,t(this).data("upload-options")||{}),delete w.success,delete w.url,p=t.isArray(p)?{}:p;var x=t(this).data("params")||{};void 0!==x.category||t(this).data("category");o.list[l]=new i(this,t.extend({url:c,params:function(e,i,n){var o=p;return n?t.extend({},o,{filesize:n.file.size,filename:n.file.name,chunkid:n.file.upload.uuid,chunkindex:n.index,chunkcount:n.file.upload.totalChunkCount,chunksize:this.options.chunkSize,chunkfilesize:n.dataBlock.data.size,width:n.file.width||0,height:n.file.height||0,type:n.file.type}):o},chunking:!1,chunkSize:v,maxFilesize:b,acceptedFiles:h,maxFiles:u&&parseInt(u)>1?u:f?null:1,timeout:y,parallelUploads:1,previewsContainer:!1,dictDefaultMessage:__("Drop files here to upload"),dictFallbackMessage:__("Your browser does not support drag'n'drop file uploads"),dictFallbackText:__("Please use the fallback form below to upload your files like in the olden days"),dictFileTooBig:__("File is too big (%sMiB), Max filesize: %sMiB","{{filesize}}","{{maxFilesize}}"),dictInvalidFileType:__("You can't upload files of this type"),dictResponseError:__("Server responded with %s code.","{{statusCode}}"),dictCancelUpload:__("Cancel upload"),dictUploadCanceled:__("Upload canceled"),dictCancelUploadConfirmation:__("Are you sure you want to cancel this upload?"),dictRemoveFile:__("Remove file"),dictMaxFilesExceeded:__("You can only upload a maximum of %s files","{{maxFiles}}"),init:function(){o.events.onInit.call(this),t(">i",this.element).addClass("dz-message"),this.options.elementHtml=t(this.element).html()},sending:function(t,e,i){void 0!==t.category&&i.append("category",t.category)},addedfile:function(e){ var i=t(this.element).data("params")||{},n=void 0!==i.category?i.category:t(this.element).data("category")||"";e.category="function"==typeof n?n.call(this,e):n},addedfiles:function(e){if(this.options.maxFiles&&(!this.options.maxFiles||this.options.maxFiles>1)&&this.options.inputId){var i=t("#"+this.options.inputId);if(i.length>0){var n=t.trim(i.val()),o=""===n?0:n.split(/\,/).length,a=this.options.maxFiles-o;if(0===a||e.length>a){e=Array.prototype.slice.call(e,a);for(var s=0;s"+(n&&void 0!==n.responseText?n.responseText:i)+"");a.find("style, title, script").remove();var s=a.text()||__("Network error"),r={code:0,data:null,msg:s};o.events.onUploadError(this,r,e)},uploadprogress:function(e,i,n){e.upload.chunked&&t(this.element).prop("disabled",!0).html(" "+__("Upload")+Math.floor(e.upload.bytesSent/e.size*100)+"%")},totaluploadprogress:function(e,i){this.getActiveFiles().length>0&&!this.options.chunking&&t(this.element).prop("disabled",!0).html(" "+__("Upload")+Math.floor(e)+"%")},queuecomplete:function(){o.events.onUploadComplete(this,this.files),this.removeAllFiles(!0),t(this.element).prop("disabled",!1).html(this.options.elementHtml)},chunkSuccess:function(t,e,i){},chunksUploaded:function(e,i){var n=this;Fast.api.ajax({url:this.options.url,data:t.extend({},p,{action:"merge",filesize:e.size,filename:e.name,chunkid:e.upload.uuid,chunkcount:e.upload.totalChunkCount})},function(t,e){return i(JSON.stringify(e)),!1},function(t,i){e.accepted=!1,n._errorProcessing([e],i.msg)})},onUploadSuccess:a,onUploadError:s,onUploadComplete:r},o.options,w)),g&&f&&require(["dragsort"],function(){t("#"+g).dragsort({dragSelector:"li a:not(.btn-trash)",dragEnd:function(){t("#"+g).trigger("fa.preview.change")},placeHolderTemplate:'
  • '})});var k=function(e){var i={},n=t("textarea[name='"+e+"']"),o=n.prev("ul");t.each(t("input,select,textarea",o).serializeArray(),function(t,e){var n=/\[?(\w+)\]?\[(\w+)\]$/g,o=n.exec(e.name);if(!o)return!0;isNaN(o[2])?(o[1]="x"+parseInt(o[1]),void 0===i[o[1]]&&(i[o[1]]={}),i[o[1]][o[2]]=e.value):i[t]=e.value});var a=[];t.each(i,function(t,e){a.push(e)}),n.val(JSON.stringify(a))};g&&m&&(t(document.body).on("keyup change","#"+m,function(i){var a=t("#"+m).val(),s=a.split(/\,/),r=t("#"+g);r.empty();var l=r.data("template")?r.data("template"):"",c=r.next().is("textarea")?r.next("textarea").val():"{}",d={};try{d=JSON.parse(c)}catch(i){}t.each(s,function(i,a){if(!a)return!0;var s=/[\.]?([a-zA-Z0-9]+)$/.exec(a);s=s?s[1]:"file";var c=t(e).data(),u=void 0!==c.cdnurl?Fast.api.cdnurl(a,c.cdnurl):Fast.api.cdnurl(a);a=Config.upload.fullmode?u:a;var h=d&&void 0!==d[i]?d[i]:null,p={url:a,fullurl:u,data:c,key:i,index:i,value:h,row:h,suffix:s},f=l?n(l,p):n.render(o.config.previewtpl,p);r.append(f)}),k(r.data("name"))}),t("#"+m).trigger("change")),g&&(t("#"+g).on("change keyup","input,textarea,select",function(){k(t(this).closest("ul").data("name"))}),t(document.body).on("fa.preview.change","#"+g,function(){var e=[];t("#"+g+" [data-url]").each(function(i,n){e.push(t(this).data("url"))}),m&&t("#"+m).val(e.join(",")),k(t("#"+g).data("name"))}),t(document.body).on("click","#"+g+" .btn-trash",function(){t(this).closest("li").remove(),t("#"+g).trigger("fa.preview.change")})),m&&(t("#"+m).closest("form").on("reset",function(){setTimeout(t.proxy(function(){t("#"+m,this).trigger("change")},this),0)}),t("body").on("paste drop","#"+m,function(e){var i=e.originalEvent,n=t(".plupload[data-input-id='"+t(this).attr("id")+"'],.faupload[data-input-id='"+t(this).attr("id")+"']");if("paste"===e.type&&i.clipboardData&&i.clipboardData.items){var a=i.clipboardData.items;if(!(1===a.length&&a[0].type.indexOf("text")>-1||2===a.length&&a[1].type.indexOf("text")>-1))return o.list[n.attr("id")].paste(i),!1}if("drop"===e.type&&i.dataTransfer&&i.dataTransfer.files)return o.list[n.attr("id")].drop(i),!1}))})},plupload:function(t,e,i,n){return o.api.upload(t,e,i,n)},faupload:function(t,e,i,n){return o.api.upload(t,e,i,n)},send:function(e,n,a,s){var r=Layer.msg(__("Uploading"),{offset:"t",time:0}),l="dropzone-"+i.uuidv4();t(' ',n)),e.push(i(' ',o)),e.push(""),e},s=function(e,n,o){var a=e.constructor===Array,s=[];return s.push(i('',o.options.formatCommonChoose())),t.each(e,function(t,e){e.constructor===Object?(t=e.id,e=e.name):t=a?e:t,s.push(i("",t==n.defaultValue?"selected":""))}),s},r=function(t){return!(!t.options.commonSearch||"server"!=t.options.sidePagination||!t.options.url)},l=function(i,n){var o={},a={},s="";return t("form.form-commonsearch .operate",i.$commonsearch).each(function(r){var l=t(this).data("name"),c=t(this).is("select")?t("option:selected",this).val():t(this).val().toUpperCase(),d=t("[name='"+l+"']",i.$commonsearch);if(0==d.length)return!0;var u=e[r],h=!i.options.searchFormTemplate&&u&&"function"==typeof u.process?u.process:null;if(d.length>1)if(/BETWEEN$/.test(c)){var p=t.trim(t("[name='"+l+"']:first",i.$commonsearch).val()),f=t.trim(t("[name='"+l+"']:last",i.$commonsearch).val());p.length||f.length?(h&&(p=h(p,"begin"),f=h(f,"end")),s=p+","+f):s="",t("[name='"+l+"']:first",i.$commonsearch).hasClass("datetimepicker")&&(c="RANGE")}else s=t("[name='"+l+"']:checked",i.$commonsearch).val(),s=h?h(s):s;else s=h?h(d.val()):d.val();if(n&&(""===s||null==s||t.isArray(s)&&0===s.length)&&!c.match(/null/i))return!0;o[l]=c,a[l]=s}),{op:o,filter:a}},c=function(e,i,n){return e.filter="Object"==typeof e.filter?e.filter:e.filter?JSON.parse(e.filter):{},e.op="Object"==typeof e.op?e.op:e.op?JSON.parse(e.op):{},e.filter=t.extend({},e.filter,i.filter),e.op=t.extend({},e.op,i.op),n&&t.each(e.filter,function(i,n){(""==n||null==n||t.isArray(n)&&0==n.length)&&!e.op[i].match(/null/i)&&(delete e.filter[i],delete e.op[i])}),e.filter=JSON.stringify(e.filter),e.op=JSON.stringify(e.op),e};t.extend(t.fn.bootstrapTable.defaults,{commonSearch:!1,titleForm:"Common search",actionForm:"",searchFormTemplate:"",searchFormVisible:!0,searchClass:"searchit",showSearch:!0,renderDefault:!0,onCommonSearch:function(t,e){return!1},onPostCommonSearch:function(t){return!1}}),t.extend(t.fn.bootstrapTable.defaults.icons,{commonSearchIcon:"glyphicon-search"}),t.extend(t.fn.bootstrapTable.Constructor.EVENTS,{"common-search.bs.table":"onCommonSearch","post-common-search.bs.table":"onPostCommonSearch"}),t.extend(t.fn.bootstrapTable.locales[t.fn.bootstrapTable.defaults.locale],{formatCommonSearch:function(){return"Common search"},formatCommonSubmitButton:function(){return"Search"},formatCommonResetButton:function(){return"Reset"},formatCommonCloseButton:function(){return"Close"},formatCommonChoose:function(){return"Choose"}}),t.extend(t.fn.bootstrapTable.defaults,t.fn.bootstrapTable.locales);var d=t.fn.bootstrapTable.Constructor,u=d.prototype.initHeader,h=d.prototype.initToolbar,p=d.prototype.load,f=d.prototype.initSearch;d.prototype.initHeader=function(){u.apply(this,Array.prototype.slice.apply(arguments)),this.$header.find("th[data-field]").each(function(e){var i=t(this).data();void 0!==i.width&&-1===i.width.toString().indexOf("%")&&(t(".th-inner",this).outerWidth(i.width),t(this).css("max-width",i.width))}),this.options.stateField=this.header.stateField},d.prototype.initToolbar=function(){if(h.apply(this,Array.prototype.slice.apply(arguments)),r(this)){var e=this,o=[];e.options.showSearch&&(o.push(i('
    ',this.options.buttonsAlign,this.options.buttonsAlign)),o.push(i('
    ")),e.$toolbar.find(".pull-right").length>0?t(o.join("")).insertBefore(e.$toolbar.find(".pull-right:first")):e.$toolbar.append(o.join("")),n(e.columns,e),e.$toolbar.find('button[name="commonSearch"]').off("click").on("click",function(){e.$commonsearch.toggleClass("hidden")}),e.$container.on("click","."+e.options.searchClass,function(){var i=t(this).data("value"),n=t(this).data("field"),o=e.$container.closest(".panel-intro").find("ul[data-field='"+n+"']");if(o.length>0)return void t('li a[data-value="'+i+'"][data-toggle="tab"]',o).trigger("click");var a=t("form [name='"+n+"']",e.$commonsearch);a.length>0&&(a.is("select")?t("option[value='"+i+"']",a).prop("selected",!0):a.length>1?t("form [name='"+n+"'][value='"+i+"']",e.$commonsearch).prop("checked",!0):a.val(i+""),a.trigger("change"),t("form",e.$commonsearch).trigger("submit"))});var a=e.options.queryParams;this.options.queryParams=function(t){return a(c(t,l(e,!0)))},this.trigger("post-common-search",e)}},d.prototype.onCommonSearch=function(){var t=l(this);this.trigger("common-search",this,t),this.options.pageNumber=1,this.refresh({})},d.prototype.load=function(t){p.apply(this,Array.prototype.slice.apply(arguments)),r(this)},d.prototype.initSearch=function(){if(f.apply(this,Array.prototype.slice.apply(arguments)),r(this)){var e=this,i=t.isEmptyObject(this.filterColumnsPartial)?null:this.filterColumnsPartial;this.data=i?t.grep(this.data,function(n,o){for(var a in i){var s=i[a].toLowerCase(),r=n[a];if(r=t.fn.bootstrapTable.utils.calculateObjectValue(e.header,e.header.formatters[t.inArray(a,e.header.fields)],[r,n,o],r),-1===t.inArray(a,e.header.fields)||"string"!=typeof r&&"number"!=typeof r||-1===(r+"").toLowerCase().indexOf(s))return!1}return!0}):this.data}}}(jQuery),define("bootstrap-table-commonsearch",["bootstrap-table"],function(t){return function(){return t.$.fn.bootstrapTable.defaults}}(this)),function(t){"use strict";t.extend(t.fn.bootstrapTable.defaults,{templateView:!1,templateFormatter:"itemtpl",templateParentClass:"row row-flex",templateTableClass:"table-template"});var e=t.fn.bootstrapTable.Constructor,i=e.prototype.initContainer,n=e.prototype.initBody,o=e.prototype.initRow;e.prototype.initContainer=function(){i.apply(this,Array.prototype.slice.apply(arguments));var t=this;t.options.templateView&&(t.options.cardView=!0)},e.prototype.initBody=function(){var e=this;t.extend(e.options,{showHeader:!e.options.templateView&&t.fn.bootstrapTable.defaults.showHeader,showFooter:!e.options.templateView&&t.fn.bootstrapTable.defaults.showFooter}),t(e.$el).toggleClass(e.options.templateTableClass,e.options.templateView),n.apply(this,Array.prototype.slice.apply(arguments)),e.options.templateView&&t("> *:not(.no-records-found)",e.$body).wrapAll(t("
    ").addClass(e.options.templateParentClass))},e.prototype.initRow=function(t,e,i,n){var a=this;if(!a.options.templateView)return o.apply(a,Array.prototype.slice.apply(arguments));var s="";if("function"==typeof a.options.templateFormatter)s=a.options.templateFormatter.call(a,t,e,i);else{s=require("template")(a.options.templateFormatter,{item:t,i:e,data:i})}return s}}(jQuery),define("bootstrap-table-template",["bootstrap-table","template"],function(t){return function(){return t.$.fn.bootstrapTable.defaults}}(this)),function(t){"use strict";var e=t.fn.bootstrapTable.utils.sprintf;t.extend(t.fn.bootstrapTable.defaults,{showJumpto:!1,exportOptions:{}}),t.extend(t.fn.bootstrapTable.locales,{formatJumpto:function(){return"GO"}}),t.extend(t.fn.bootstrapTable.defaults,t.fn.bootstrapTable.locales);var i=t.fn.bootstrapTable.Constructor,n=i.prototype.initPagination;i.prototype.initPagination=function(){if(this.showToolbar=this.options.showExport,n.apply(this,Array.prototype.slice.apply(arguments)),this.options.showJumpto){var i=this,o=this.$pagination.find("ul.pagination"),a=o.find("li.jumpto");a.length||(a=t(['
  • ','','","
  • "].join("")).appendTo(o),a.find("button").click(function(){i.selectPage(parseInt(a.find("input").val()))}))}}}(jQuery),define("bootstrap-table-jumpto",["bootstrap-table"],function(t){return function(){return t.$.fn.bootstrapTable.defaults}}(this)),function(t){"use strict";function e(t){var e=0,a=0,s=0,r=0;return"detail"in t&&(a=t.detail),"wheelDelta"in t&&(a=-t.wheelDelta/120),"wheelDeltaY"in t&&(a=-t.wheelDeltaY/120),"wheelDeltaX"in t&&(e=-t.wheelDeltaX/120),"axis"in t&&t.axis===t.HORIZONTAL_AXIS&&(e=a,a=0),s=e*i,r=a*i,"deltaY"in t&&(r=t.deltaY),"deltaX"in t&&(s=t.deltaX),(s||r)&&t.deltaMode&&(1===t.deltaMode?(s*=n,r*=n):(s*=o,r*=o)),s&&!e&&(e=s<1?-1:1),r&&!a&&(a=r<1?-1:1),{spinX:e,spinY:a,pixelX:s,pixelY:r}}var i=10,n=40,o=800,a=null,s=function(){if(null===a){var e=t("

    ").addClass("fixed-table-scroll-inner"),i=t("

    ").addClass("fixed-table-scroll-outer"),n=void 0,o=void 0;i.append(e),t("body").append(i),n=e[0].offsetWidth,i.css("overflow","scroll"),o=e[0].offsetWidth,n===o&&(o=i[0].clientWidth),i.remove(),a=n-o}return a},r=function(t){return t[0].scrollHeight>t[0].clientHeight?15:0};t.extend(t.fn.bootstrapTable.defaults,{fixedColumns:!1,fixedNumber:0,fixedRightNumber:0});var l=t.fn.bootstrapTable.Constructor,c=l.prototype.initBody,d=l.prototype.initContainer,u=l.prototype.trigger,h=l.prototype.hideLoading,p=l.prototype.updateSelected;l.prototype.fixedColumnsSupported=function(){var t=this;return t.options.fixedColumns&&!t.options.detailView&&!t.options.cardView},l.prototype.initFixedContainer=function(){this.options.fixedNumber&&(0==this.$tableContainer.find(".fixed-columns").length&&this.$tableContainer.append('
    '),this.$fixedColumns=this.$tableContainer.find(".fixed-columns")),this.options.fixedRightNumber&&(0==this.$tableContainer.find(".fixed-columns-right").length&&this.$tableContainer.append('
    '),this.$fixedColumnsRight=this.$tableContainer.find(".fixed-columns-right"))},l.prototype.initContainer=function(){d.apply(this,Array.prototype.slice.apply(arguments)),this.initFixedContainer()},l.prototype.initBody=function(){c.apply(this,Array.prototype.slice.apply(arguments)),this.fixedColumnsSupported()&&(this.options.showHeader&&this.options.height||(this.initFixedColumnsBody(),this.initFixedColumnsEvents()))},l.prototype.trigger=function(){var t=this;u.apply(this,Array.prototype.slice.apply(arguments)),"pre-body"===arguments[0]&&this.options.cardView&&this.$tableBody.css("height","auto"),"toggle"===arguments[0]&&(arguments[1]?(this.$tableBody.css("height","auto"),this.$fixedColumns&&this.$fixedColumns.hide(),this.$fixedColumnsRight&&this.$fixedColumnsRight.hide()):(this.$tableBody.css("height","100%"),this.$fixedColumns&&this.$fixedColumns.show(),this.$fixedColumnsRight&&this.$fixedColumnsRight.show(),this.$fixedHeaderRight&&this.$fixedHeaderRight.scrollLeft(this.$tableBody.find("table").width()),this.$fixedBodyRight&&this.$fixedBodyRight.scrollLeft(this.$tableBody.find("table").width()))),t.fixedColumnsSupported()&&("post-header"===arguments[0]?this.initFixedColumnsHeader():"scroll-body"===arguments[0]?(this.needFixedColumns&&this.options.fixedNumber&&this.$fixedBody&&this.$fixedBody.scrollTop(this.$tableBody.scrollTop()),this.needFixedColumns&&this.options.fixedRightNumber&&this.$fixedBodyRight&&this.$fixedBodyRight.scrollTop(this.$tableBody.scrollTop())):"load-success"===arguments[0]&&this.hideLoading())},l.prototype.updateSelected=function(){var e=this;p.apply(this,Array.prototype.slice.apply(arguments)),this.fixedColumnsSupported()&&this.$tableBody.find("tr").each(function(i,n){var o=t(n),a=o.data("index"),s=o.attr("class"),r='[name="'+e.options.selectItemName+'"]',l=o.find(r);if(void 0!==a){var c=function(t,i){var n=i.find('tr[data-index="'+a+'"]');n.attr("class",s),l.length&&n.find(r).prop("checked",l.prop("checked")),e.$selectAll.length&&t.add(i).find('[name="btSelectAll"]').prop("checked",e.$selectAll.prop("checked"))};e.$fixedBody&&e.options.fixedNumber&&c(e.$fixedHeader,e.$fixedBody),e.$fixedBodyRight&&e.options.fixedRightNumber&&c(e.$fixedHeaderRight,e.$fixedBodyRight)}})},l.prototype.hideLoading=function(){h.apply(this,Array.prototype.slice.apply(arguments)),this.needFixedColumns&&this.options.fixedNumber&&this.$fixedColumns.find(".fixed-table-loading").hide(),this.needFixedColumns&&this.options.fixedRightNumber&&this.$fixedColumnsRight.find(".fixed-table-loading").hide()},l.prototype.initFixedColumnsHeader=function(){var t=this;this.options.height?this.needFixedColumns=this.$tableHeader.outerWidth(!0)a.clientWidth?s():0,l=t(".fixed-table-pagination",e.$tableContainer).height();void 0!==e.options.height&&(l=0),i.css({height:"calc(100% - "+(l+r)+"px)"}),o.css({height:"calc(100% - "+n.height()+"px)",overflow:"hidden"})};return t(window).on("resize",r),r(),o};this.needFixedColumns&&this.options.fixedNumber&&(this.$fixedBody=i(this.$fixedColumns,this.$fixedHeader)),this.needFixedColumns&&this.options.fixedRightNumber&&(this.$fixedBodyRight=i(this.$fixedColumnsRight,this.$fixedHeaderRight),this.$fixedBodyRight.scrollLeft(this.$fixedBodyRight.find("table").width()),this.$fixedBodyRight.css("overflow-y","hidden"))},l.prototype.getFixedColumnsWidth=function(t){var e=this.getVisibleFields(),i=0,n=this.options.fixedNumber;t&&(e=e.reverse(),n=this.options.fixedRightNumber,this.$fixedColumnsRight.css("right",r(this.$tableBody)));for(var o=0;o-1,a=o?"DOMMouseScroll":"mousewheel",s=function(t,n){var o=e(t),a=Math.ceil(o.pixelY),s=i.$tableBody.scrollTop()+a;(a<0&&s>0||a>0&&s table > tbody > tr[data-index] > td").off("click dblclick").on("click dblclick",function(e){var n=t(this).closest("tr[data-index]").data("index");t(i.$selectItem[n]).closest("tr[data-index]").find(">td:eq("+t(this).index()+")").trigger("click")})),t("div.fixed-table-body").off("scroll"),this.$tableBody.off("scroll").on("scroll",function(t){i.$tableHeader.scrollLeft(0),i.$tableBody.scrollLeft()>0&&(i.$tableHeader.scrollLeft(i.$tableBody.scrollLeft()),i.options.showFooter&&!i.options.cardView&&i.$tableFooter.scrollLeft(i.$tableBody.scrollLeft()));var e=i.$tableBody.scrollTop();i.$fixedBody&&i.$fixedBody.scrollTop(e),i.$fixedBodyRight&&i.$fixedBodyRight.scrollTop(e)}), -this.needFixedColumns&&this.options.fixedRightNumber&&(this.$fixedBodyRight.find("tr").hover(function(t){n(t,!0)},function(t){n(t,!1)}),this.$fixedBodyRight[0].addEventListener(a,function(t){s(t,i.$fixedBodyRight[0])}),this.$fixedBodyRight.find('input[name="'+this.options.selectItemName+'"]').off("click").on("click",function(e){e.stopImmediatePropagation();var n=t(e.target).data("index");t(i.$selectItem[n]).trigger("click")}),this.$fixedBodyRight.find("> table > tbody > tr[data-index] > td").off("click dblclick").on("click dblclick",function(e){var n=t(this).closest("tr[data-index]").data("index");t(i.$selectItem[n]).closest("tr[data-index]").find(">td:eq("+t(this).index()+")").trigger("click")})),this.options.filterControl&&t(this.$fixedColumns).off("keyup change").on("keyup change",function(e){var n=t(e.target),o=n.val(),a=n.parents("th").data("field"),s=i.$header.find('th[data-field="'+a+'"]');if(n.is("input"))s.find("input").val(o);else if(n.is("select")){var r=s.find("select");r.find("option[selected]").removeAttr("selected"),r.find('option[value="'+o+'"]').attr("selected",!0)}i.triggerSearch()})}}(jQuery),define("bootstrap-table-fixed-columns",["bootstrap-table"],function(t){return function(){return t.$.fn.bootstrapTable.defaults}}(this)),define("table",["jquery","bootstrap","moment","moment/locale/zh-cn","bootstrap-table","bootstrap-table-lang","bootstrap-table-export","bootstrap-table-commonsearch","bootstrap-table-template","bootstrap-table-jumpto","bootstrap-table-fixed-columns"],function(t,e,i){var n={list:{},defaults:{url:"",sidePagination:"server",method:"get",toolbar:".toolbar",search:!0,cache:!1,commonSearch:!0,searchFormVisible:!1,titleForm:"",idTable:"commonTable",showExport:!0,exportDataType:"auto",exportTypes:["json","xml","csv","txt","doc","excel"],exportOptions:{fileName:"export_"+i().format("YYYY-MM-DD"),preventInjection:!1,mso:{onMsoNumberFormat:function(e,i,n){return isNaN(t(e).text())?"":"\\@"}},ignoreColumn:[0,"operate"]},pageSize:Config.pagesize||localStorage.getItem("pagesize")||10,pageList:[10,15,20,25,50,"All"],pagination:!0,clickToSelect:!0,dblClickToEdit:!0,singleSelect:!1,showRefresh:!1,showJumpto:!0,locale:"zh-cn"===Config.language?"zh-CN":"en-US",showToggle:!0,showColumns:!0,pk:"id",sortName:"id",sortOrder:"desc",paginationFirstText:__("First"),paginationPreText:__("Previous"),paginationNextText:__("Next"),paginationLastText:__("Last"),cardView:!1,iosCardView:!0,checkOnInit:!0,escape:!0,fixDropdownPosition:!0,dragCheckboxMultiselect:!0,selectedIds:[],selectedData:[],extend:{index_url:"",add_url:"",edit_url:"",del_url:"",import_url:"",multi_url:"",dragsort_url:"ajax/weigh"}},columnDefaults:{align:"center",valign:"middle"},config:{checkboxtd:"tbody>tr>td.bs-checkbox",toolbar:".toolbar",refreshbtn:".btn-refresh",addbtn:".btn-add",editbtn:".btn-edit",delbtn:".btn-del",importbtn:".btn-import",multibtn:".btn-multi",disabledbtn:".btn-disabled",editonebtn:".btn-editone",restoreonebtn:".btn-restoreone",destroyonebtn:".btn-destroyone",restoreallbtn:".btn-restoreall",destroyallbtn:".btn-destroyall",dragsortfield:"weigh"},button:{edit:{name:"edit",icon:"fa fa-pencil",title:__("Edit"),extend:'data-toggle="tooltip" data-container="body"',classname:"btn btn-xs btn-success btn-editone"},del:{name:"del",icon:"fa fa-trash",title:__("Del"),extend:'data-toggle="tooltip" data-container="body"',classname:"btn btn-xs btn-danger btn-delone"},dragsort:{name:"dragsort",icon:"fa fa-arrows",title:__("Drag to sort"),extend:'data-toggle="tooltip"',classname:"btn btn-xs btn-primary btn-dragsort"}},api:{init:function(e,i,o){e=e||{},i=i||{},o=o||{},t.fn.bootstrapTable.Constructor.prototype.getSelectItem=function(){return this.$selectItem};var a=t.fn.bootstrapTable.Constructor.prototype.onPageListChange;t.fn.bootstrapTable.Constructor.prototype.onPageListChange=function(){return a.apply(this,Array.prototype.slice.apply(arguments)),localStorage.setItem("pagesize",this.options.pageSize),!1},t.extend(!0,t.fn.bootstrapTable.defaults,n.defaults,e),t.extend(t.fn.bootstrapTable.columnDefaults,n.columnDefaults,i),t.extend(t.fn.bootstrapTable.locales[n.defaults.locale],{formatCommonSearch:function(){return __("Common search")},formatCommonSubmitButton:function(){return __("Search")},formatCommonResetButton:function(){return __("Reset")},formatCommonCloseButton:function(){return __("Close")},formatCommonChoose:function(){return __("Choose")},formatJumpto:function(){return __("Go")}},o),t.fn.bootstrapTable.defaults.iosCardView&&navigator.userAgent.match(/(iPod|iPhone|iPad)/)&&(n.defaults.cardView=!0,t.fn.bootstrapTable.defaults.cardView=!0),void 0!==e.exportTypes&&(t.fn.bootstrapTable.defaults.exportTypes=e.exportTypes)},bindevent:function(e){var i=e.closest(".bootstrap-table"),o=e.bootstrapTable("getOptions"),a=t(o.toolbar,i),s=t(".btn-selected-tips",i);0===s.length&&(s=t(' '+__("Multiple selection mode: %s checked","0")+"").appendTo(a)),s.off("click").on("click",function(t){e.trigger("uncheckbox"),e.bootstrapTable("refresh")}),e.on("uncheckbox",function(t,e,i){o.selectedIds=[],o.selectedData=[],s.tooltip("hide"),s.addClass("hide")}),e.on("load-error.bs.table",function(t,e,i){0!==i.status&&Toastr.error(__("Unknown data format"))}),e.on("load-success.bs.table",function(t,e){void 0===e.rows&&void 0!==e.code&&Toastr.error(e.msg)}),e.on("refresh.bs.table",function(e,i,o){t(n.config.refreshbtn,a).find(".fa").addClass("fa-spin"),t(".layui-layer-autocontent").remove()}),e.on("search.bs.table common-search.bs.table",function(t,i,n){e.trigger("uncheckbox")}),o.dblClickToEdit&&e.on("dbl-click-row.bs.table",function(e,i,o,a){t(n.config.editonebtn,o).trigger("click")}),e.on("pre-body.bs.table",function(e,i){o.maintainSelected&&t.each(i,function(e,i){i[o.stateField]=t.inArray(i[o.pk],o.selectedIds)>-1})}),e.on("post-body.bs.table",function(i,s){if(t(n.config.refreshbtn,a).find(".fa").removeClass("fa-spin"),t(n.config.checkboxtd+":first",e).find("input[type='checkbox'][data-index]").length>0){var r,l,c,d=!1,u=!1,h=function(i){if(d){var o=Math.min(i.pageX,r),a=Math.min(i.pageY,l),s=Math.abs(r-i.pageX),u=Math.abs(l-i.pageY);c.css({left:o+"px",top:a+"px",width:s+"px",height:u+"px"});var h={x:o,y:a,width:s,height:u};t(n.config.checkboxtd,e).each(function(){var e=t("input:checkbox",this),i=this.getBoundingClientRect();i.x+=document.documentElement.scrollLeft,i.y+=document.documentElement.scrollTop;var n=i.x,o=i.y,a=i.x+i.width,s=i.y+i.height,r=h.x,l=h.y,c=h.x+h.width,d=h.y+h.height;n<=c&&a>=r&&o<=d&&s>=l?t(this).hasClass("overlaped")||(t(this).addClass("overlaped"),e.trigger("click")):t(this).hasClass("overlaped")&&(t(this).removeClass("overlaped"),e.trigger("click"))})}},p=function(){return!1},f=function(){d&&(t(document).off("mousemove",h),t(document).off("selectstart",p),c.remove()),d=!1,u=!1,t(document.body).css({MozUserSelect:"",webkitUserSelect:""}).attr("unselectable","off")};t(n.config.checkboxtd,e).on("mousedown",function(e){if(2===e.button||t(e.target).is("input"))return!1;r=e.pageX,l=e.pageY,u=!0}).on("mousemove",function(i){u&&!d&&(d=!0,c=t("
    "),c.css({position:"absolute",width:0,height:0,border:"1px dashed blue",background:"#0029ff",left:i.pageX+"px",top:i.pageY+"px",opacity:.1}),c.appendTo(document.body),t(document.body).css({MozUserSelect:"none",webkitUserSelect:"none"}).attr("unselectable","on"),t(document).on("mousemove",h).on("mouseup",f).on("selectstart",p),o.dragCheckboxMultiselect&&t(n.config.checkboxtd,e).removeClass("overlaped"))})}});var r=o.exportDataType;if(e.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table post-body.bs.table",function(i){var l=[];t.each(e.bootstrapTable("getData"),function(t,e){l.push(void 0!==e[o.pk]?e[o.pk]:"")});var c=n.api.selectedids(e,!0),d=n.api.selecteddata(e,!0);o.maintainSelected?(o.selectedIds=o.selectedIds.filter(function(e,i,n){return-1===t.inArray(e,l)}).concat(c),o.selectedData=o.selectedData.filter(function(e,i,n){return-1===t.inArray(e[o.pk],l)}).concat(d),o.selectedIds.length>c.length?(t("b",s).text(o.selectedIds.length),s.removeClass("hide")):s.addClass("hide")):(o.selectedIds=c,o.selectedData=d),"auto"===r&&(o.exportDataType=c.length>0?"selected":"all",0===t(".export .exporttips").length&&t(".export .dropdown-menu").prepend("
  • "),t(".export .exporttips").html("导出记录:"+(c.length>0?"选中":"全部"))),t(n.config.disabledbtn,a).toggleClass("disabled",!o.selectedIds.length)}),e.on("common-search.bs.table",function(i,n,o){var a=t(".panel-heading [data-field]",e.closest(".panel-intro")),s=a.data("field"),r=t("li.active > a",a).data("value");o.filter&&void 0!==o.filter[s]&&o.filter[s]!=r&&(t("li",a).removeClass("active"),t("li > a[data-value='"+o.filter[s]+"']",a).parent().addClass("active"))}),t('.panel-heading [data-field] a[data-toggle="tab"]',e.closest(".panel-intro")).on("shown.bs.tab",function(i){var n=t(this).closest("[data-field]").data("field"),o=t(this).data("value"),a=t("[name='"+n+"']",e.closest(".bootstrap-table").find(".commonsearch-table"));return"SELECT"===a.prop("tagName")?t("option[value='"+o+"']",a).prop("selected",!0):a.val(o),e.trigger("uncheckbox"),e.bootstrapTable("getOptions").totalRows=0,e.bootstrapTable("refresh",{pageNumber:1}),!1}),t("form",e.closest(".bootstrap-table").find(".commonsearch-table")).on("reset",function(){setTimeout(function(){},0),t(".panel-heading [data-field] li",e.closest(".panel-intro")).removeClass("active"),t(".panel-heading [data-field] li:first",e.closest(".panel-intro")).addClass("active")}),a.on("click",n.config.refreshbtn,function(){e.bootstrapTable("refresh")}),a.on("click",n.config.addbtn,function(){var i=n.api.selectedids(e),a=o.extend.add_url;-1!==a.indexOf("{ids}")&&(a=n.api.replaceurl(a,{ids:i.length>0?i.join(","):0},e)),Fast.api.open(a,t(this).data("original-title")||t(this).attr("title")||__("Add"),t(this).data()||{})}),t(n.config.importbtn,a).length>0&&require(["upload"],function(i){i.api.upload(t(n.config.importbtn,a),function(t,i){Fast.api.ajax({url:o.extend.import_url,data:{file:t.url}},function(t,i){e.trigger("uncheckbox"),e.bootstrapTable("refresh")})})}),a.on("click",n.config.editbtn,function(){var i=this;if(!(n.api.selectedids(e).length>10)){var a=t(i).data("title")||t(i).attr("title")||__("Edit"),s=t(i).data()||{};delete s.title,t.each(n.api.selecteddata(e),function(i,r){var l=o.extend.edit_url;r=t.extend({},r||{},{ids:r[o.pk]}),l=n.api.replaceurl(l,r,e),Fast.api.open(l,"function"==typeof a?a.call(e,r):a,s)})}}),t(document).on("click",n.config.destroyallbtn,function(){var i=this;return Layer.confirm(__("Are you sure you want to truncate?"),function(){var n=t(i).data("url")?t(i).data("url"):t(i).attr("href");Fast.api.ajax(n,function(){Layer.closeAll(),e.trigger("uncheckbox"),e.bootstrapTable("refresh")},function(){Layer.closeAll()})}),!1}),t(document).on("click",n.config.restoreallbtn,function(){var i=this,n=t(i).data("url")?t(i).data("url"):t(i).attr("href");return Fast.api.ajax(n,function(){Layer.closeAll(),e.trigger("uncheckbox"),e.bootstrapTable("refresh")},function(){Layer.closeAll()}),!1}),t(document).on("click",n.config.restoreonebtn+","+n.config.destroyonebtn,function(){var i=this,a=t(i).data("url")?t(i).data("url"):t(i).attr("href"),s=n.api.getrowbyindex(e,t(i).data("row-index"));return Fast.api.ajax({url:a,data:{ids:s[o.pk]}},function(){e.trigger("uncheckbox"),e.bootstrapTable("refresh")}),!1}),a.on("click",n.config.multibtn,function(){var i=n.api.selectedids(e);n.api.multi(t(this).data("action"),i,e,this)}),a.on("click",n.config.delbtn,function(){var t=this,i=n.api.selectedids(e);Layer.confirm(__("Are you sure you want to delete the %s selected item?",i.length),{icon:3,title:__("Warning"),offset:0,shadeClose:!0,btn:[__("OK"),__("Cancel")]},function(o){n.api.multi("del",i,e,t),Layer.close(o)})}),require(["dragsort"],function(){t("tbody",e).dragsort({itemSelector:"tr:visible",dragSelector:"a.btn-dragsort",dragEnd:function(i,o){var a=t("a.btn-dragsort",this),s=e.bootstrapTable("getData"),r=s[parseInt(t(this).data("index"))],l=e.bootstrapTable("getOptions"),c=t.map(t("tbody tr:visible",e),function(e){return s[parseInt(t(e).data("index"))][l.pk]}),d=r[l.pk],u=void 0!==r.pid?r.pid:"",h={url:e.bootstrapTable("getOptions").extend.dragsort_url,data:{ids:c.join(","),changeid:d,pid:u,field:n.config.dragsortfield,orderway:l.sortOrder,table:l.extend.table,pk:l.pk}};Fast.api.ajax(h,function(i,n){var o=t(a).data("success")||t.noop;if("function"==typeof o&&!1===o.call(a,i,n))return!1;e.bootstrapTable("refresh")},function(i,n){var o=t(a).data("error")||t.noop;if("function"==typeof o&&!1===o.call(a,i,n))return!1;e.bootstrapTable("refresh")})},placeHolderTemplate:""})}),e.on("click","input[data-id][name='checkbox']",function(i){var n=t(this).data("id");e.bootstrapTable(t(this).prop("checked")?"checkBy":"uncheckBy",{field:o.pk,values:[n]})}),e.on("click","[data-id].btn-change",function(i){i.preventDefault();var o=t.proxy(function(){n.api.multi(t(this).data("action")?t(this).data("action"):"",[t(this).data("id")],e,this)},this);void 0!==t(this).data("confirm")?Layer.confirm(t(this).data("confirm"),function(t){o(),Layer.close(t)}):o()}),e.on("click","[data-id].btn-edit",function(i){i.preventDefault();var a=t(this).data("id"),s=n.api.getrowbyid(e,a);s.ids=a;var r=n.api.replaceurl(o.extend.edit_url,s,e);Fast.api.open(r,t(this).data("original-title")||t(this).attr("title")||__("Edit"),t(this).data()||{})}),e.on("click","[data-id].btn-del",function(i){i.preventDefault();var o=t(this).data("id"),a=this;Layer.confirm(__("Are you sure you want to delete this item?"),{icon:3,title:__("Warning"),shadeClose:!0,btn:[__("OK"),__("Cancel")]},function(t){n.api.multi("del",o,e,a),Layer.close(t)})}),e.on("mouseenter mouseleave",".autocontent",function(e){var i=t(".autocontent-item",this).get(0);i&&("mouseenter"===e.type?i.scrollWidth>i.offsetWidth&&0===t(".autocontent-caret",this).length&&t(this).append("
    "):t(".autocontent-caret",this).remove())}),e.on("click mouseenter",".autocontent-caret",function(e){var i=t(this).prev().hasClass("autocontent-hover");if(i||"mouseenter"!==e.type){var n=t(this).prev().text(),o=t(this).parent().get(0).getBoundingClientRect(),a=Layer.open({id:"autocontent",skin:"layui-layer-fast layui-layer-autocontent",title:!1,content:n,btn:!1,anim:!1,shade:0,isOutAnim:!1,area:"auto",maxWidth:450,maxHeight:350,offset:[o.y,o.x]});i&&t(document).one("mouseleave","#layui-layer"+a,function(){Layer.close(a)});var s=function(e){0===t(e.target).closest(".layui-layer").length&&(Layer.close(a),t(document).off("mousedown",s))};t(document).off("mousedown",s).on("mousedown",s)}}),o.fixDropdownPosition){var l=e.closest(".fixed-table-body");e.on("show.bs.dropdown fa.event.refreshdropdown",".btn-group",function(e){var i,n,o,a=t(".dropdown-menu",this),s=t(this),r=a.hasClass("pull-right")||a.hasClass("dropdown-menu-right");o="fixed",n=s.offset().top-t(window).scrollTop()+s.outerHeight(),n+a.outerHeight()>t(window).height()&&(n=s.offset().top-a.outerHeight()-5),i=r?s.offset().left+s.outerWidth()-a.outerWidth():s.offset().left,(i||n)&&a.css({position:o,left:i,top:n,right:"inherit"})});var c=function(){t(".btn-group.open",e).length>0&&"fixed"==t(".btn-group.open .dropdown-menu",e).css("position")&&t(".btn-group.open",e).trigger("fa.event.refreshdropdown")};t(window).on("scroll",function(){c()}),l.on("scroll",function(){c()})}var d=e.attr("id");return n.list[d]=e,e},multi:function(e,i,n,o){var a=n.bootstrapTable("getOptions"),s=o?t(o).data():{};i=t.isArray(i)?i.join(","):i,a={url:void 0!==s.url?s.url:"del"==e?a.extend.del_url:a.extend.multi_url,data:{action:e,ids:i,params:void 0!==s.params?"object"==typeof s.params?t.param(s.params):s.params:""}},Fast.api.ajax(a,function(e,i){n.trigger("uncheckbox");var a=t(o).data("success")||t.noop;if("function"==typeof a&&!1===a.call(o,e,i))return!1;n.bootstrapTable("refresh")},function(e,i){var n=t(o).data("error")||t.noop;if("function"==typeof n&&!1===n.call(o,e,i))return!1})},events:{operate:{"click .btn-editone":function(e,i,o,a){e.stopPropagation(),e.preventDefault();var s=t(this).closest("table"),r=s.bootstrapTable("getOptions"),l=o[r.pk];o=t.extend({},o||{},{ids:l});var c=r.extend.edit_url;Fast.api.open(n.api.replaceurl(c,o,s),t(this).data("original-title")||t(this).attr("title")||__("Edit"),t(this).data()||{})},"click .btn-delone":function(i,o,a,s){i.stopPropagation(),i.preventDefault();var r=this,l=t(r).offset().top-t(window).scrollTop(),c=t(r).offset().left-t(window).scrollLeft()-260;l+154>t(window).height()&&(l-=154),t(window).width()<480&&(l=c=e),Layer.confirm(__("Are you sure you want to delete this item?"),{icon:3,title:__("Warning"),offset:[l,c],shadeClose:!0,btn:[__("OK"),__("Cancel")]},function(e){var i=t(r).closest("table"),o=i.bootstrapTable("getOptions");n.api.multi("del",a[o.pk],i,r),Layer.close(e)})}},image:{"click .img-center":function(e,i,n,o){var a=[];i=null===i?"":i.toString();var s,r=""!=i?i.split(","):[];t.each(r,function(t,e){s=Fast.api.cdnurl(e),a.push({src:s,thumb:s.match(/^(\/|data:image\\)/)?s:s+Config.upload.thumbstyle})}),Layer.photos({photos:{start:t(this).parent().index(),data:a},anim:5})}}},formatter:{icon:function(t,e,i){return t=null===t?"":t.toString(),'-1?t:"fa fa-"+t)+'"> '+t},image:function(t,e,i){return n.api.formatter.images.call(this,t,e,i)},images:function(e,i,n){e=null==e||0===e.length?"":e.toString();var o,a=void 0!==this.classname?this.classname:"img-sm img-center",s=""!==e?-1===e.indexOf("data:image/")?e.split(","):[e]:[],r=[];return t.each(s,function(t,e){e=e||"/assets/img/blank.gif",o=Fast.api.cdnurl(e,!0),o=!Config.upload.thumbstyle||o.match(/^(\/|data:image\/)/)||o.indexOf(Config.upload.thumbstyle.substring(0,1))>-1?o:o+Config.upload.thumbstyle,r.push('')}),r.join(" ")},file:function(t,e,i){return n.api.formatter.files.call(this,t,e,i)},files:function(e,i,n){e=null==e||0===e.length?"":e.toString();var o,a,s=void 0!==this.classname?this.classname:"img-sm img-center",r=""!==e?-1===e.indexOf("data:image/")?e.split(","):[e]:[],l=[];return t.each(r,function(t,e){e=Fast.api.cdnurl(e,!0),o=/[\.]?([a-zA-Z0-9]+)$/.exec(e),o=o?o[1]:"file",a=Fast.api.fixurl("ajax/icon?suffix="+o),l.push('')}),l.join(" ")},content:function(t,i,n){var o=this.width!=e?this.width.toString().match(/^\d+$/)?this.width+"px":this.width:"250px";return"
    "+t+"
    "},status:function(e,i,o){var a={normal:"success",hidden:"gray",deleted:"danger",locked:"info"};return void 0!==this.custom&&(a=t.extend(a,this.custom)),this.custom=a,this.icon="fa fa-circle",n.api.formatter.normal.call(this,e,i,o)},normal:function(e,i,n){var o=["primary","success","danger","warning","info","gray","red","yellow","aqua","blue","navy","teal","olive","lime","fuchsia","purple","maroon"],a={};void 0!==this.custom&&(a=t.extend(a,this.custom)),e=i[this.field]||e,e=null==e||0===e.length?"":e.toString();var s="object"==typeof this.searchList?Object.keys(this.searchList):[],n=s.indexOf(e),r=e&&void 0!==a[e]?a[e]:null,l=n>-1?this.searchList[e]:null,c=void 0!==this.icon?this.icon:null;r||(r=n>-1&&void 0!==o[n]?o[n]:"primary"),l||(l=__(e.charAt(0).toUpperCase()+e.slice(1))),e=Fast.api.escape(e),l=Fast.api.escape(l);var d=''+(c?' ':"")+l+"";return 0!=this.operate&&(d=''+d+""),d},toggle:function(t,e,i){var n=this.table,o=n?n.bootstrapTable("getOptions"):{},a=o.pk||"id",s=void 0!==this.color?this.color:"success",r=void 0!==this.yes?this.yes:1,l=void 0!==this.no?this.no:0,c=void 0!==this.url?this.url:"",d="",u=!1;return void 0!==this.confirm&&(d="function"==typeof this.confirm?this.confirm.call(this,t,e,i):this.confirm),void 0!==this.disable&&(u="function"==typeof this.disable?this.disable.call(this,t,e,i):this.disable),""},url:function(t,e,i){return'
    '},search:function(t,i,n){var o=this.field;if(void 0!==this.customField){var a=this.customField.split(".").reduce(function(t,i){return null===t||t===e?"":t[i]},i);t=Fast.api.escape(a),o=this.customField}return''+t+""},addtabs:function(t,e,i){var o=n.api.replaceurl(this.url||"",e,this.table),a=this.atitle?this.atitle:__("Search %s",t);return''+t+""},dialog:function(t,e,i){var o=n.api.replaceurl(this.url||"",e,this.table),a=this.atitle?this.atitle:__("View %s",t);return''+t+""},flag:function(i,n,o){var a=this;i=null==i||0===i.length?"":i.toString();var s={index:"success",hot:"warning",recommend:"danger",new:"info"};void 0!==this.custom&&(s=t.extend(s,this.custom));var r=this.field;if(void 0!==this.customField){var l=this.customField.split(".").reduce(function(t,i){return null===t||t===e?"":t[i]},n);i=Fast.api.escape(l),r=this.customField}if("object"==typeof a.searchList&&"function"==typeof a.searchList.then&&t.when(a.searchList).done(function(e){e.data&&e.data.searchlist&&t.isArray(e.data.searchlist)?a.searchList=e.data.searchlist:e.constructor!==Array&&e.constructor!==Object||(a.searchList=e)}),"object"==typeof a.searchList&&void 0===a.custom){var c=0,d=Object.values(s);t.each(a.searchList,function(t,e){void 0===s[t]&&(s[t]=d[c],c=void 0===d[c+1]?0:c+1)})}var u,h,p,f=[],m=t.isArray(i)?i:""!=i?i.split(","):[];return t.each(m,function(t,e){if(""===(e=null==e||0===e.length?"":e.toString()))return!0;u=e&&void 0!==s[e]?s[e]:"primary",h=void 0!==a.searchList&&void 0!==a.searchList[e]?a.searchList[e]:__(e.charAt(0).toUpperCase()+e.slice(1)),e=Fast.api.escape(e),h=Fast.api.escape(h),p=''+h+"",a.operate?f.push(''+p+""):f.push(p)}),f.join(" ")},label:function(t,e,i){return n.api.formatter.flag.call(this,t,e,i)},datetime:function(t,e,n){var o=void 0===this.datetimeFormat?"YYYY-MM-DD HH:mm:ss":this.datetimeFormat;return isNaN(t)?t?i(t).format(o):__("None"):t?i(1e3*parseInt(t)).format(o):__("None")},operate:function(e,i,o){var a=this.table,s=a?a.bootstrapTable("getOptions"):{},r=t.extend([],this.buttons||[]),l=[];return r.forEach(function(t){l.push(t.name)}),""!==s.extend.dragsort_url&&-1===l.indexOf("dragsort")&&r.push(n.button.dragsort),""!==s.extend.edit_url&&-1===l.indexOf("edit")&&(n.button.edit.url=s.extend.edit_url,r.push(n.button.edit)),""!==s.extend.del_url&&-1===l.indexOf("del")&&r.push(n.button.del),n.api.buttonlink(this,r,e,i,o,"operate")},buttons:function(e,i,o){var a=t.extend([],this.buttons||[]);return n.api.buttonlink(this,a,e,i,o,"buttons")}},buttonlink:function(e,i,o,a,s,r){var l=e.table;e.clickToSelect=!1,r=void 0===r?"buttons":r;var c,d,u,h,p,f,m,g,v,y,b,w,x,k=l?l.bootstrapTable("getOptions"):{},_=[],C=e.fieldIndex,S={};if(t.each(i,function(t,e){if("operate"===r){if("dragsort"===e.name&&void 0===a[n.config.dragsortfield])return!0;if(["add","edit","del","multi","dragsort"].indexOf(e.name)>-1&&!k.extend[e.name+"_url"])return!0}var i=l.data(r+"-"+e.name);if(void 0===i||i){if(c="function"==typeof e.hidden?e.hidden.call(l,a,e):void 0!==e.hidden&&e.hidden)return!0;if(!(d="function"==typeof e.visible?e.visible.call(l,a,e):void 0===e.visible||e.visible))return!0;w=e.dropdown?e.dropdown:"",h=e.url?e.url:"",h="function"==typeof h?h.call(l,a,e):h?Fast.api.fixurl(n.api.replaceurl(h,a,l)):"javascript:;",p=e.classname?e.classname:w?"btn-"+name+"one":"btn-primary btn-"+name+"one",f=e.icon?e.icon:"",m="function"==typeof e.text?e.text.call(l,a,e):e.text?e.text:"",g="function"==typeof e.title?e.title.call(l,a,e):e.title?e.title:m,v=e.refresh?'data-refresh="'+e.refresh+'"':"",y="function"==typeof e.confirm?e.confirm.call(l,a,e):void 0!==e.confirm&&e.confirm,y=y?'data-confirm="'+y+'"':"",b="function"==typeof e.extend?e.extend.call(l,a,e):void 0!==e.extend?e.extend:"",u="function"==typeof e.disable?e.disable.call(l,a,e):void 0!==e.disable&&e.disable,u&&(p+=" disabled"),x=''+(m?" "+m:"")+"",w?(void 0===S[w]&&(S[w]=[]),S[w].push(x)):_.push(x)}}),!t.isEmptyObject(S)){var T=[];t.each(S,function(t,e){T.push('
    ")}),_.unshift(T.join(" "))}return _.join(" ")},replaceurl:function(t,i,n){var o=n?n.bootstrapTable("getOptions"):null,a=o?i[o.pk]:0;return i.ids=a||(void 0!==i.ids?i.ids:0),t=null==t||0===t.length?"":t.toString(),t=t.match(/(?=([?&]ids=)|(\/ids\/)|(\{ids}))/i)?t:t+(t.match(/(\?|&)+/)?"&ids=":"/ids/")+"{ids}",t=t.replace(/\{(.*?)\}/gi,function(t){t=t.substring(1,t.length-1);var n=t.split(".").reduce(function(t,i){return null===t||t===e?"":t[i]},i);return n=Fast.api.escape(n)})},selectedids:function(e,i){var n=e.bootstrapTable("getOptions");return!i&&n.maintainSelected?n.selectedIds:t.map(e.bootstrapTable("getSelections"),function(t){return t[n.pk]})},selecteddata:function(t,e){var i=t.bootstrapTable("getOptions");return!e&&i.maintainSelected?i.selectedData:t.bootstrapTable("getSelections")},toggleattr:function(e){t("input[type='checkbox']",e).trigger("click")},getrowdata:function(t,e){e=parseInt(e);var i=t.bootstrapTable("getData");return void 0!==i[e]?i[e]:null},getrowbyindex:function(t,e){return n.api.getrowdata(t,e)},getrowbyid:function(e,i){var o={},a=e.bootstrapTable("getOptions");return t.each(n.api.selecteddata(e),function(t,e){if(e[a.pk]==i)return o=e,!1}),o}}};return n}),function(t){t.fn.dragsort=function(e){if("destroy"==e)return void t(this.selector).trigger("dragsort-uninit");var i=t.extend({},t.fn.dragsort.defaults,e),n=[],o=null,a=null;return this.each(function(e,s){t(s).is("table")&&1==t(s).children().length&&t(s).children().is("tbody")&&(s=t(s).children().get(0));var r={draggedItem:null,placeHolderItem:null,pos:null,offset:null,offsetLimit:null,scroll:null,container:s,init:function(){i.tagName=""==i.tagName?0==t(this.container).children().length?"li":t(this.container).children().get(0).tagName.toLowerCase():i.tagName,""==i.itemSelector&&(i.itemSelector=i.tagName),""==i.dragSelector&&(i.dragSelector=i.tagName),""==i.placeHolderTemplate&&(i.placeHolderTemplate="<"+i.tagName+"> "),t(this.container).attr("data-listidx",e).mousedown(this.grabItem).bind("dragsort-uninit",this.uninit),this.styleDragHandlers(!0)},uninit:function(){var e=n[t(this).attr("data-listidx")];t(e.container).unbind("mousedown",e.grabItem).unbind("dragsort-uninit"),e.styleDragHandlers(!1)},getItems:function(){return t(this.container).children(i.itemSelector)},styleDragHandlers:function(e){this.getItems().map(function(){return t(this).is(i.dragSelector)?this:t(this).find(i.dragSelector).get()}).css("cursor",e?"pointer":"")},grabItem:function(e){var o=n[t(this).attr("data-listidx")],a=t(e.target).closest("[data-listidx] > "+i.tagName).get(0),s=o.getItems().filter(function(){return this==a}).length>0;if(!(1!=e.which||t(e.target).is(i.dragSelectorExclude)||t(e.target).closest(i.dragSelectorExclude).length>0)&&s&&(t(e.target).is(i.dragSelector)||t(e.target).closest(i.dragSelector).length)){e.preventDefault();for(var r=e.target;!t(r).is(i.dragSelector);){if(r==this)return;r=r.parentNode}t(r).attr("data-cursor",t(r).css("cursor")),t(r).css("cursor","move");var l=this,c=function(){o.dragStart.call(l,e),t(o.container).unbind("mousemove",c)};t(o.container).mousemove(c).mouseup(function(){t(o.container).unbind("mousemove",c),t(r).css("cursor",t(r).attr("data-cursor"))})}},dragStart:function(e){null!=o&&null!=o.draggedItem&&o.dropItem(),o=n[t(this).attr("data-listidx")],o.draggedItem=t(e.target).closest("[data-listidx] > "+i.tagName),o.draggedItem.attr("data-origpos",t(this).attr("data-listidx")+"-"+t(o.container).children().index(o.draggedItem));var a=parseInt(o.draggedItem.css("marginTop")),s=parseInt(o.draggedItem.css("marginLeft"));if(o.offset=o.draggedItem.offset(),o.offset.top=e.pageY-o.offset.top+(isNaN(a)?0:a)-1,o.offset.left=e.pageX-o.offset.left+(isNaN(s)?0:s)-1,!i.dragBetween){var r=0==t(o.container).outerHeight()?Math.max(1,Math.round(.5+o.getItems().length*o.draggedItem.outerWidth()/t(o.container).outerWidth()))*o.draggedItem.outerHeight():t(o.container).outerHeight();o.offsetLimit=t(o.container).offset(),o.offsetLimit.right=o.offsetLimit.left+t(o.container).outerWidth()-o.draggedItem.outerWidth(),o.offsetLimit.bottom=o.offsetLimit.top+r-o.draggedItem.outerHeight()}var l=o.draggedItem.height(),c=o.draggedItem.width();if("tr"==i.tagName?(o.draggedItem.children().each(function(){t(this).width(t(this).width())}),o.placeHolderItem=o.draggedItem.clone().attr("data-placeholder",!0),o.draggedItem.after(o.placeHolderItem),o.placeHolderItem.children().each(function(){t(this).html(" ")})):(o.draggedItem.after(i.placeHolderTemplate),o.placeHolderItem=o.draggedItem.next().css({height:l,width:c}).attr("data-placeholder",!0)),"td"==i.tagName){var d=o.draggedItem.closest("table").get(0);t("
    ").appendTo("body").children().append(o.draggedItem)}var u=o.draggedItem.attr("style");o.draggedItem.attr("data-origstyle",u||""),o.draggedItem.css({position:"absolute",opacity:.8,"z-index":999,height:l,width:c}),o.scroll={moveX:0,moveY:0,maxX:t(document).width()-t(window).width(),maxY:t(document).height()-t(window).height()},o.scroll.scrollY=window.setInterval(function(){if(i.scrollContainer!=window)return void t(i.scrollContainer).scrollTop(t(i.scrollContainer).scrollTop()+o.scroll.moveY);var e=t(i.scrollContainer).scrollTop();(o.scroll.moveY>0&&e0)&&(t(i.scrollContainer).scrollTop(e+o.scroll.moveY),o.draggedItem.css("top",o.draggedItem.offset().top+o.scroll.moveY+1))},10),o.scroll.scrollX=window.setInterval(function(){if(i.scrollContainer!=window)return void t(i.scrollContainer).scrollLeft(t(i.scrollContainer).scrollLeft()+o.scroll.moveX);var e=t(i.scrollContainer).scrollLeft() +this.needFixedColumns&&this.options.fixedRightNumber&&(this.$fixedBodyRight.find("tr").hover(function(t){n(t,!0)},function(t){n(t,!1)}),this.$fixedBodyRight[0].addEventListener(a,function(t){s(t,i.$fixedBodyRight[0])}),this.$fixedBodyRight.find('input[name="'+this.options.selectItemName+'"]').off("click").on("click",function(e){e.stopImmediatePropagation();var n=t(e.target).data("index");t(i.$selectItem[n]).trigger("click")}),this.$fixedBodyRight.find("> table > tbody > tr[data-index] > td").off("click dblclick").on("click dblclick",function(e){var n=t(this).closest("tr[data-index]").data("index");t(i.$selectItem[n]).closest("tr[data-index]").find(">td:eq("+t(this).index()+")").trigger("click")})),this.options.filterControl&&t(this.$fixedColumns).off("keyup change").on("keyup change",function(e){var n=t(e.target),o=n.val(),a=n.parents("th").data("field"),s=i.$header.find('th[data-field="'+a+'"]');if(n.is("input"))s.find("input").val(o);else if(n.is("select")){var r=s.find("select");r.find("option[selected]").removeAttr("selected"),r.find('option[value="'+o+'"]').attr("selected",!0)}i.triggerSearch()})}}(jQuery),define("bootstrap-table-fixed-columns",["bootstrap-table"],function(t){return function(){return t.$.fn.bootstrapTable.defaults}}(this)),define("table",["jquery","bootstrap","moment","moment/locale/zh-cn","bootstrap-table","bootstrap-table-lang","bootstrap-table-export","bootstrap-table-commonsearch","bootstrap-table-template","bootstrap-table-jumpto","bootstrap-table-fixed-columns"],function(t,e,i){var n={list:{},defaults:{url:"",sidePagination:"server",method:"get",toolbar:".toolbar",search:!0,cache:!1,commonSearch:!0,searchFormVisible:!1,titleForm:"",idTable:"commonTable",showExport:!0,exportDataType:"auto",exportTypes:["json","xml","csv","txt","doc","excel"],exportOptions:{fileName:"export_"+i().format("YYYY-MM-DD"),preventInjection:!1,mso:{onMsoNumberFormat:function(e,i,n){return isNaN(t(e).text())?"":"\\@"}},ignoreColumn:[0,"operate"]},pageSize:Config.pagesize||localStorage.getItem("pagesize")||10,pageList:[10,15,20,25,50,"All"],pagination:!0,clickToSelect:!0,dblClickToEdit:!0,singleSelect:!1,showRefresh:!1,showJumpto:!0,locale:"zh-cn"===Config.language?"zh-CN":"en-US",showToggle:!0,showColumns:!0,pk:"id",sortName:"id",sortOrder:"desc",paginationFirstText:__("First"),paginationPreText:__("Previous"),paginationNextText:__("Next"),paginationLastText:__("Last"),cardView:!1,iosCardView:!0,checkOnInit:!0,escape:!0,fixDropdownPosition:!0,dragCheckboxMultiselect:!0,selectedIds:[],selectedData:[],extend:{index_url:"",add_url:"",edit_url:"",del_url:"",import_url:"",multi_url:"",dragsort_url:"ajax/weigh"}},columnDefaults:{align:"center",valign:"middle"},config:{checkboxtd:"tbody>tr>td.bs-checkbox",toolbar:".toolbar",refreshbtn:".btn-refresh",addbtn:".btn-add",editbtn:".btn-edit",delbtn:".btn-del",importbtn:".btn-import",multibtn:".btn-multi",disabledbtn:".btn-disabled",editonebtn:".btn-editone",restoreonebtn:".btn-restoreone",destroyonebtn:".btn-destroyone",restoreallbtn:".btn-restoreall",destroyallbtn:".btn-destroyall",dragsortfield:"weigh"},button:{edit:{name:"edit",icon:"",text:__("Edit"),title:__("Edit"),extend:'data-toggle="tooltip" data-container="body"',classname:"btn btn-xs btn-success btn-editone"},del:{name:"del",icon:"",text:__("Del"),title:__("Del"),extend:'data-toggle="tooltip" data-container="body"',classname:"btn btn-xs btn-danger btn-delone"},dragsort:{name:"dragsort",icon:"",text:"排序",title:__("Drag to sort"),extend:'data-toggle="tooltip"',classname:"btn btn-xs btn-primary btn-dragsort"}},api:{init:function(e,i,o){e=e||{},i=i||{},o=o||{},t.fn.bootstrapTable.Constructor.prototype.getSelectItem=function(){return this.$selectItem};var a=t.fn.bootstrapTable.Constructor.prototype.onPageListChange;t.fn.bootstrapTable.Constructor.prototype.onPageListChange=function(){return a.apply(this,Array.prototype.slice.apply(arguments)),localStorage.setItem("pagesize",this.options.pageSize),!1},t.extend(!0,t.fn.bootstrapTable.defaults,n.defaults,e),t.extend(t.fn.bootstrapTable.columnDefaults,n.columnDefaults,i),t.extend(t.fn.bootstrapTable.locales[n.defaults.locale],{formatCommonSearch:function(){return __("Common search")},formatCommonSubmitButton:function(){return __("Search")},formatCommonResetButton:function(){return __("Reset")},formatCommonCloseButton:function(){return __("Close")},formatCommonChoose:function(){return __("Choose")},formatJumpto:function(){return __("Go")}},o),t.fn.bootstrapTable.defaults.iosCardView&&navigator.userAgent.match(/(iPod|iPhone|iPad)/)&&(n.defaults.cardView=!0,t.fn.bootstrapTable.defaults.cardView=!0),void 0!==e.exportTypes&&(t.fn.bootstrapTable.defaults.exportTypes=e.exportTypes)},bindevent:function(e){var i=e.closest(".bootstrap-table"),o=e.bootstrapTable("getOptions"),a=t(o.toolbar,i),s=t(".btn-selected-tips",i);0===s.length&&(s=t(' '+__("Multiple selection mode: %s checked","0")+"").appendTo(a)),s.off("click").on("click",function(t){e.trigger("uncheckbox"),e.bootstrapTable("refresh")}),e.on("uncheckbox",function(t,e,i){o.selectedIds=[],o.selectedData=[],s.tooltip("hide"),s.addClass("hide")}),e.on("load-error.bs.table",function(t,e,i){0!==i.status&&Toastr.error(__("Unknown data format"))}),e.on("load-success.bs.table",function(t,e){void 0===e.rows&&void 0!==e.code&&Toastr.error(e.msg)}),e.on("refresh.bs.table",function(e,i,o){t(n.config.refreshbtn,a).find(".fa").addClass("fa-spin"),t(".layui-layer-autocontent").remove()}),e.on("search.bs.table common-search.bs.table",function(t,i,n){e.trigger("uncheckbox")}),o.dblClickToEdit&&e.on("dbl-click-row.bs.table",function(e,i,o,a){t(n.config.editonebtn,o).trigger("click")}),e.on("pre-body.bs.table",function(e,i){o.maintainSelected&&t.each(i,function(e,i){i[o.stateField]=t.inArray(i[o.pk],o.selectedIds)>-1})}),e.on("post-body.bs.table",function(i,s){if(t(n.config.refreshbtn,a).find(".fa").removeClass("fa-spin"),t(n.config.checkboxtd+":first",e).find("input[type='checkbox'][data-index]").length>0){var r,l,c,d=!1,u=!1,h=function(i){if(d){var o=Math.min(i.pageX,r),a=Math.min(i.pageY,l),s=Math.abs(r-i.pageX),u=Math.abs(l-i.pageY);c.css({left:o+"px",top:a+"px",width:s+"px",height:u+"px"});var h={x:o,y:a,width:s,height:u};t(n.config.checkboxtd,e).each(function(){var e=t("input:checkbox",this),i=this.getBoundingClientRect();i.x+=document.documentElement.scrollLeft,i.y+=document.documentElement.scrollTop;var n=i.x,o=i.y,a=i.x+i.width,s=i.y+i.height,r=h.x,l=h.y,c=h.x+h.width,d=h.y+h.height;n<=c&&a>=r&&o<=d&&s>=l?t(this).hasClass("overlaped")||(t(this).addClass("overlaped"),e.trigger("click")):t(this).hasClass("overlaped")&&(t(this).removeClass("overlaped"),e.trigger("click"))})}},p=function(){return!1},f=function(){d&&(t(document).off("mousemove",h),t(document).off("selectstart",p),c.remove()),d=!1,u=!1,t(document.body).css({MozUserSelect:"",webkitUserSelect:""}).attr("unselectable","off")};t(n.config.checkboxtd,e).on("mousedown",function(e){if(2===e.button||t(e.target).is("input"))return!1;r=e.pageX,l=e.pageY,u=!0}).on("mousemove",function(i){u&&!d&&(d=!0,c=t("
    "),c.css({position:"absolute",width:0,height:0,border:"1px dashed blue",background:"#0029ff",left:i.pageX+"px",top:i.pageY+"px",opacity:.1}),c.appendTo(document.body),t(document.body).css({MozUserSelect:"none",webkitUserSelect:"none"}).attr("unselectable","on"),t(document).on("mousemove",h).on("mouseup",f).on("selectstart",p),o.dragCheckboxMultiselect&&t(n.config.checkboxtd,e).removeClass("overlaped"))})}});var r=o.exportDataType;if(e.on("check.bs.table uncheck.bs.table check-all.bs.table uncheck-all.bs.table post-body.bs.table",function(i){var l=[];t.each(e.bootstrapTable("getData"),function(t,e){l.push(void 0!==e[o.pk]?e[o.pk]:"")});var c=n.api.selectedids(e,!0),d=n.api.selecteddata(e,!0);o.maintainSelected?(o.selectedIds=o.selectedIds.filter(function(e,i,n){return-1===t.inArray(e,l)}).concat(c),o.selectedData=o.selectedData.filter(function(e,i,n){return-1===t.inArray(e[o.pk],l)}).concat(d),o.selectedIds.length>c.length?(t("b",s).text(o.selectedIds.length),s.removeClass("hide")):s.addClass("hide")):(o.selectedIds=c,o.selectedData=d),"auto"===r&&(o.exportDataType=c.length>0?"selected":"all",0===t(".export .exporttips").length&&t(".export .dropdown-menu").prepend("
  • "),t(".export .exporttips").html("导出记录:"+(c.length>0?"选中":"全部"))),t(n.config.disabledbtn,a).toggleClass("disabled",!o.selectedIds.length)}),e.on("common-search.bs.table",function(i,n,o){var a=t(".panel-heading [data-field]",e.closest(".panel-intro")),s=a.data("field"),r=t("li.active > a",a).data("value");o.filter&&void 0!==o.filter[s]&&o.filter[s]!=r&&(t("li",a).removeClass("active"),t("li > a[data-value='"+o.filter[s]+"']",a).parent().addClass("active"))}),t('.panel-heading [data-field] a[data-toggle="tab"]',e.closest(".panel-intro")).on("shown.bs.tab",function(i){var n=t(this).closest("[data-field]").data("field"),o=t(this).data("value"),a=t("[name='"+n+"']",e.closest(".bootstrap-table").find(".commonsearch-table"));return"SELECT"===a.prop("tagName")?t("option[value='"+o+"']",a).prop("selected",!0):a.val(o),e.trigger("uncheckbox"),e.bootstrapTable("getOptions").totalRows=0,e.bootstrapTable("refresh",{pageNumber:1}),!1}),t("form",e.closest(".bootstrap-table").find(".commonsearch-table")).on("reset",function(){setTimeout(function(){},0),t(".panel-heading [data-field] li",e.closest(".panel-intro")).removeClass("active"),t(".panel-heading [data-field] li:first",e.closest(".panel-intro")).addClass("active")}),a.on("click",n.config.refreshbtn,function(){e.bootstrapTable("refresh")}),a.on("click",n.config.addbtn,function(){var i=n.api.selectedids(e),a=o.extend.add_url;-1!==a.indexOf("{ids}")&&(a=n.api.replaceurl(a,{ids:i.length>0?i.join(","):0},e)),Fast.api.open(a,t(this).data("original-title")||t(this).attr("title")||__("Add"),t(this).data()||{})}),t(n.config.importbtn,a).length>0&&require(["upload"],function(i){i.api.upload(t(n.config.importbtn,a),function(t,i){Fast.api.ajax({url:o.extend.import_url,data:{file:t.url}},function(t,i){e.trigger("uncheckbox"),e.bootstrapTable("refresh")})})}),a.on("click",n.config.editbtn,function(){var i=this;if(!(n.api.selectedids(e).length>10)){var a=t(i).data("title")||t(i).attr("title")||__("Edit"),s=t(i).data()||{};delete s.title,t.each(n.api.selecteddata(e),function(i,r){var l=o.extend.edit_url;r=t.extend({},r||{},{ids:r[o.pk]}),l=n.api.replaceurl(l,r,e),Fast.api.open(l,"function"==typeof a?a.call(e,r):a,s)})}}),t(document).on("click",n.config.destroyallbtn,function(){var i=this;return Layer.confirm(__("Are you sure you want to truncate?"),function(){var n=t(i).data("url")?t(i).data("url"):t(i).attr("href");Fast.api.ajax(n,function(){Layer.closeAll(),e.trigger("uncheckbox"),e.bootstrapTable("refresh")},function(){Layer.closeAll()})}),!1}),t(document).on("click",n.config.restoreallbtn,function(){var i=this,n=t(i).data("url")?t(i).data("url"):t(i).attr("href");return Fast.api.ajax(n,function(){Layer.closeAll(),e.trigger("uncheckbox"),e.bootstrapTable("refresh")},function(){Layer.closeAll()}),!1}),t(document).on("click",n.config.restoreonebtn+","+n.config.destroyonebtn,function(){var i=this,a=t(i).data("url")?t(i).data("url"):t(i).attr("href"),s=n.api.getrowbyindex(e,t(i).data("row-index"));return Fast.api.ajax({url:a,data:{ids:s[o.pk]}},function(){e.trigger("uncheckbox"),e.bootstrapTable("refresh")}),!1}),a.on("click",n.config.multibtn,function(){var i=n.api.selectedids(e);n.api.multi(t(this).data("action"),i,e,this)}),a.on("click",n.config.delbtn,function(){var t=this,i=n.api.selectedids(e);Layer.confirm(__("Are you sure you want to delete the %s selected item?",i.length),{icon:3,title:__("Warning"),offset:0,shadeClose:!0,btn:[__("OK"),__("Cancel")]},function(o){n.api.multi("del",i,e,t),Layer.close(o)})}),require(["dragsort"],function(){t("tbody",e).dragsort({itemSelector:"tr:visible",dragSelector:"a.btn-dragsort",dragEnd:function(i,o){var a=t("a.btn-dragsort",this),s=e.bootstrapTable("getData"),r=s[parseInt(t(this).data("index"))],l=e.bootstrapTable("getOptions"),c=t.map(t("tbody tr:visible",e),function(e){return s[parseInt(t(e).data("index"))][l.pk]}),d=r[l.pk],u=void 0!==r.pid?r.pid:"",h={url:e.bootstrapTable("getOptions").extend.dragsort_url,data:{ids:c.join(","),changeid:d,pid:u,field:n.config.dragsortfield,orderway:l.sortOrder,table:l.extend.table,pk:l.pk}};Fast.api.ajax(h,function(i,n){var o=t(a).data("success")||t.noop;if("function"==typeof o&&!1===o.call(a,i,n))return!1;e.bootstrapTable("refresh")},function(i,n){var o=t(a).data("error")||t.noop;if("function"==typeof o&&!1===o.call(a,i,n))return!1;e.bootstrapTable("refresh")})},placeHolderTemplate:""})}),e.on("click","input[data-id][name='checkbox']",function(i){var n=t(this).data("id");e.bootstrapTable(t(this).prop("checked")?"checkBy":"uncheckBy",{field:o.pk,values:[n]})}),e.on("click","[data-id].btn-change",function(i){i.preventDefault();var o=t.proxy(function(){n.api.multi(t(this).data("action")?t(this).data("action"):"",[t(this).data("id")],e,this)},this);void 0!==t(this).data("confirm")?Layer.confirm(t(this).data("confirm"),function(t){o(),Layer.close(t)}):o()}),e.on("click","[data-id].btn-edit",function(i){i.preventDefault();var a=t(this).data("id"),s=n.api.getrowbyid(e,a);s.ids=a;var r=n.api.replaceurl(o.extend.edit_url,s,e);Fast.api.open(r,t(this).data("original-title")||t(this).attr("title")||__("Edit"),t(this).data()||{})}),e.on("click","[data-id].btn-del",function(i){i.preventDefault();var o=t(this).data("id"),a=this;Layer.confirm(__("Are you sure you want to delete this item?"),{icon:3,title:__("Warning"),shadeClose:!0,btn:[__("OK"),__("Cancel")]},function(t){n.api.multi("del",o,e,a),Layer.close(t)})}),e.on("mouseenter mouseleave",".autocontent",function(e){var i=t(".autocontent-item",this).get(0);i&&("mouseenter"===e.type?i.scrollWidth>i.offsetWidth&&0===t(".autocontent-caret",this).length&&t(this).append("
    "):t(".autocontent-caret",this).remove())}),e.on("click mouseenter",".autocontent-caret",function(e){var i=t(this).prev().hasClass("autocontent-hover");if(i||"mouseenter"!==e.type){var n=t(this).prev().text(),o=t(this).parent().get(0).getBoundingClientRect(),a=Layer.open({id:"autocontent",skin:"layui-layer-fast layui-layer-autocontent",title:!1,content:n,btn:!1,anim:!1,shade:0,isOutAnim:!1,area:"auto",maxWidth:450,maxHeight:350,offset:[o.y,o.x]});i&&t(document).one("mouseleave","#layui-layer"+a,function(){Layer.close(a)});var s=function(e){0===t(e.target).closest(".layui-layer").length&&(Layer.close(a),t(document).off("mousedown",s))};t(document).off("mousedown",s).on("mousedown",s)}}),o.fixDropdownPosition){var l=e.closest(".fixed-table-body");e.on("show.bs.dropdown fa.event.refreshdropdown",".btn-group",function(e){var i,n,o,a=t(".dropdown-menu",this),s=t(this),r=a.hasClass("pull-right")||a.hasClass("dropdown-menu-right");o="fixed",n=s.offset().top-t(window).scrollTop()+s.outerHeight(),n+a.outerHeight()>t(window).height()&&(n=s.offset().top-a.outerHeight()-5),i=r?s.offset().left+s.outerWidth()-a.outerWidth():s.offset().left,(i||n)&&a.css({position:o,left:i,top:n,right:"inherit"})});var c=function(){t(".btn-group.open",e).length>0&&"fixed"==t(".btn-group.open .dropdown-menu",e).css("position")&&t(".btn-group.open",e).trigger("fa.event.refreshdropdown")};t(window).on("scroll",function(){c()}),l.on("scroll",function(){c()})}var d=e.attr("id");return n.list[d]=e,e},multi:function(e,i,n,o){var a=n.bootstrapTable("getOptions"),s=o?t(o).data():{};i=t.isArray(i)?i.join(","):i,a={url:void 0!==s.url?s.url:"del"==e?a.extend.del_url:a.extend.multi_url,data:{action:e,ids:i,params:void 0!==s.params?"object"==typeof s.params?t.param(s.params):s.params:""}},Fast.api.ajax(a,function(e,i){n.trigger("uncheckbox");var a=t(o).data("success")||t.noop;if("function"==typeof a&&!1===a.call(o,e,i))return!1;n.bootstrapTable("refresh")},function(e,i){var n=t(o).data("error")||t.noop;if("function"==typeof n&&!1===n.call(o,e,i))return!1})},events:{operate:{"click .btn-editone":function(e,i,o,a){e.stopPropagation(),e.preventDefault();var s=t(this).closest("table"),r=s.bootstrapTable("getOptions"),l=o[r.pk];o=t.extend({},o||{},{ids:l});var c=r.extend.edit_url;Fast.api.open(n.api.replaceurl(c,o,s),t(this).data("original-title")||t(this).attr("title")||__("Edit"),t(this).data()||{})},"click .btn-delone":function(i,o,a,s){i.stopPropagation(),i.preventDefault();var r=this,l=t(r).offset().top-t(window).scrollTop(),c=t(r).offset().left-t(window).scrollLeft()-260;l+154>t(window).height()&&(l-=154),t(window).width()<480&&(l=c=e),Layer.confirm(__("Are you sure you want to delete this item?"),{icon:3,title:__("Warning"),offset:[l,c],shadeClose:!0,btn:[__("OK"),__("Cancel")]},function(e){var i=t(r).closest("table"),o=i.bootstrapTable("getOptions");n.api.multi("del",a[o.pk],i,r),Layer.close(e)})}},image:{"click .img-center":function(e,i,n,o){var a=[];i=null===i?"":i.toString();var s,r=""!=i?i.split(","):[];t.each(r,function(t,e){s=Fast.api.cdnurl(e),a.push({src:s,thumb:s.match(/^(\/|data:image\\)/)?s:s+Config.upload.thumbstyle})}),Layer.photos({photos:{start:t(this).parent().index(),data:a},anim:5})}}},formatter:{icon:function(t,e,i){return t=null===t?"":t.toString(),'-1?t:"fa fa-"+t)+'"> '+t},image:function(t,e,i){return n.api.formatter.images.call(this,t,e,i)},images:function(e,i,n){e=null==e||0===e.length?"":e.toString();var o,a=void 0!==this.classname?this.classname:"img-sm img-center",s=""!==e?-1===e.indexOf("data:image/")?e.split(","):[e]:[],r=[];return t.each(s,function(t,e){e=e||"/assets/img/blank.gif",o=Fast.api.cdnurl(e,!0),o=!Config.upload.thumbstyle||o.match(/^(\/|data:image\/)/)||o.indexOf(Config.upload.thumbstyle.substring(0,1))>-1?o:o+Config.upload.thumbstyle,r.push('')}),r.join(" ")},file:function(t,e,i){return n.api.formatter.files.call(this,t,e,i)},files:function(e,i,n){e=null==e||0===e.length?"":e.toString();var o,a,s=void 0!==this.classname?this.classname:"img-sm img-center",r=""!==e?-1===e.indexOf("data:image/")?e.split(","):[e]:[],l=[];return t.each(r,function(t,e){e=Fast.api.cdnurl(e,!0),o=/[\.]?([a-zA-Z0-9]+)$/.exec(e),o=o?o[1]:"file",a=Fast.api.fixurl("ajax/icon?suffix="+o),l.push('')}),l.join(" ")},content:function(t,i,n){var o=this.width!=e?this.width.toString().match(/^\d+$/)?this.width+"px":this.width:"250px";return"
    "+t+"
    "},status:function(e,i,o){var a={normal:"success",hidden:"gray",deleted:"danger",locked:"info"};return void 0!==this.custom&&(a=t.extend(a,this.custom)),this.custom=a,this.icon="fa fa-circle",n.api.formatter.normal.call(this,e,i,o)},normal:function(e,i,n){var o=["primary","success","danger","warning","info","gray","red","yellow","aqua","blue","navy","teal","olive","lime","fuchsia","purple","maroon"],a={};void 0!==this.custom&&(a=t.extend(a,this.custom)),e=i[this.field]||e,e=null==e||0===e.length?"":e.toString();var s="object"==typeof this.searchList?Object.keys(this.searchList):[],n=s.indexOf(e),r=e&&void 0!==a[e]?a[e]:null,l=n>-1?this.searchList[e]:null,c=void 0!==this.icon?this.icon:null;r||(r=n>-1&&void 0!==o[n]?o[n]:"primary"),l||(l=__(e.charAt(0).toUpperCase()+e.slice(1))),e=Fast.api.escape(e),l=Fast.api.escape(l);var d=''+(c?' ':"")+l+"";return 0!=this.operate&&(d=''+d+""),d},toggle:function(t,e,i){var n=this.table,o=n?n.bootstrapTable("getOptions"):{},a=o.pk||"id",s=void 0!==this.color?this.color:"success",r=void 0!==this.yes?this.yes:1,l=void 0!==this.no?this.no:0,c=void 0!==this.url?this.url:"",d="",u=!1;return void 0!==this.confirm&&(d="function"==typeof this.confirm?this.confirm.call(this,t,e,i):this.confirm),void 0!==this.disable&&(u="function"==typeof this.disable?this.disable.call(this,t,e,i):this.disable),""},url:function(t,e,i){return'
    '},search:function(t,i,n){var o=this.field;if(void 0!==this.customField){var a=this.customField.split(".").reduce(function(t,i){return null===t||t===e?"":t[i]},i);t=Fast.api.escape(a),o=this.customField}return''+t+""},addtabs:function(t,e,i){var o=n.api.replaceurl(this.url||"",e,this.table),a=this.atitle?this.atitle:__("Search %s",t);return''+t+""},dialog:function(t,e,i){var o=n.api.replaceurl(this.url||"",e,this.table),a=this.atitle?this.atitle:__("View %s",t);return''+t+""},flag:function(i,n,o){var a=this;i=null==i||0===i.length?"":i.toString();var s={index:"success",hot:"warning",recommend:"danger",new:"info"};void 0!==this.custom&&(s=t.extend(s,this.custom));var r=this.field;if(void 0!==this.customField){var l=this.customField.split(".").reduce(function(t,i){return null===t||t===e?"":t[i]},n);i=Fast.api.escape(l),r=this.customField}if("object"==typeof a.searchList&&"function"==typeof a.searchList.then&&t.when(a.searchList).done(function(e){e.data&&e.data.searchlist&&t.isArray(e.data.searchlist)?a.searchList=e.data.searchlist:e.constructor!==Array&&e.constructor!==Object||(a.searchList=e)}),"object"==typeof a.searchList&&void 0===a.custom){var c=0,d=Object.values(s);t.each(a.searchList,function(t,e){void 0===s[t]&&(s[t]=d[c],c=void 0===d[c+1]?0:c+1)})}var u,h,p,f=[],m=t.isArray(i)?i:""!=i?i.split(","):[];return t.each(m,function(t,e){if(""===(e=null==e||0===e.length?"":e.toString()))return!0;u=e&&void 0!==s[e]?s[e]:"primary",h=void 0!==a.searchList&&void 0!==a.searchList[e]?a.searchList[e]:__(e.charAt(0).toUpperCase()+e.slice(1)),e=Fast.api.escape(e),h=Fast.api.escape(h),p=''+h+"",a.operate?f.push(''+p+""):f.push(p)}),f.join(" ")},label:function(t,e,i){return n.api.formatter.flag.call(this,t,e,i)},datetime:function(t,e,n){var o=void 0===this.datetimeFormat?"YYYY-MM-DD HH:mm:ss":this.datetimeFormat;return isNaN(t)?t?i(t).format(o):__("None"):t?i(1e3*parseInt(t)).format(o):__("None")},operate:function(e,i,o){var a=this.table,s=a?a.bootstrapTable("getOptions"):{},r=t.extend([],this.buttons||[]),l=[];return r.forEach(function(t){l.push(t.name)}),""!==s.extend.dragsort_url&&-1===l.indexOf("dragsort")&&r.push(n.button.dragsort),""!==s.extend.edit_url&&-1===l.indexOf("edit")&&(n.button.edit.url=s.extend.edit_url,r.push(n.button.edit)),""!==s.extend.del_url&&-1===l.indexOf("del")&&r.push(n.button.del),n.api.buttonlink(this,r,e,i,o,"operate")},buttons:function(e,i,o){var a=t.extend([],this.buttons||[]);return n.api.buttonlink(this,a,e,i,o,"buttons")}},buttonlink:function(e,i,o,a,s,r){var l=e.table;e.clickToSelect=!1,r=void 0===r?"buttons":r;var c,d,u,h,p,f,m,g,v,y,b,w,x,k=l?l.bootstrapTable("getOptions"):{},_=[],C=e.fieldIndex,S={};if(t.each(i,function(t,e){if("operate"===r){if("dragsort"===e.name&&void 0===a[n.config.dragsortfield])return!0;if(["add","edit","del","multi","dragsort"].indexOf(e.name)>-1&&!k.extend[e.name+"_url"])return!0}var i=l.data(r+"-"+e.name);if(void 0===i||i){if(c="function"==typeof e.hidden?e.hidden.call(l,a,e):void 0!==e.hidden&&e.hidden)return!0;if(!(d="function"==typeof e.visible?e.visible.call(l,a,e):void 0===e.visible||e.visible))return!0;w=e.dropdown?e.dropdown:"",h=e.url?e.url:"",h="function"==typeof h?h.call(l,a,e):h?Fast.api.fixurl(n.api.replaceurl(h,a,l)):"javascript:;",p=e.classname?e.classname:w?"btn-"+name+"one":"btn-primary btn-"+name+"one",f=e.icon?e.icon:"",m="function"==typeof e.text?e.text.call(l,a,e):e.text?e.text:"",g="function"==typeof e.title?e.title.call(l,a,e):e.title?e.title:m,v=e.refresh?'data-refresh="'+e.refresh+'"':"",y="function"==typeof e.confirm?e.confirm.call(l,a,e):void 0!==e.confirm&&e.confirm,y=y?'data-confirm="'+y+'"':"",b="function"==typeof e.extend?e.extend.call(l,a,e):void 0!==e.extend?e.extend:"",u="function"==typeof e.disable?e.disable.call(l,a,e):void 0!==e.disable&&e.disable,u&&(p+=" disabled"),x=''+(f?'':"")+(m?(f?" ":"")+m:"")+"",w?(void 0===S[w]&&(S[w]=[]),S[w].push(x)):_.push(x)}}),!t.isEmptyObject(S)){var T=[];t.each(S,function(t,e){T.push('
    ")}),_.unshift(T.join(" "))}return _.join(" ")},replaceurl:function(t,i,n){var o=n?n.bootstrapTable("getOptions"):null,a=o?i[o.pk]:0;return i.ids=a||(void 0!==i.ids?i.ids:0),t=null==t||0===t.length?"":t.toString(),t=t.match(/(?=([?&]ids=)|(\/ids\/)|(\{ids}))/i)?t:t+(t.match(/(\?|&)+/)?"&ids=":"/ids/")+"{ids}",t=t.replace(/\{(.*?)\}/gi,function(t){t=t.substring(1,t.length-1);var n=t.split(".").reduce(function(t,i){return null===t||t===e?"":t[i]},i);return n=Fast.api.escape(n)})},selectedids:function(e,i){var n=e.bootstrapTable("getOptions");return!i&&n.maintainSelected?n.selectedIds:t.map(e.bootstrapTable("getSelections"),function(t){return t[n.pk]})},selecteddata:function(t,e){var i=t.bootstrapTable("getOptions");return!e&&i.maintainSelected?i.selectedData:t.bootstrapTable("getSelections")},toggleattr:function(e){t("input[type='checkbox']",e).trigger("click")},getrowdata:function(t,e){e=parseInt(e);var i=t.bootstrapTable("getData");return void 0!==i[e]?i[e]:null},getrowbyindex:function(t,e){return n.api.getrowdata(t,e)},getrowbyid:function(e,i){var o={},a=e.bootstrapTable("getOptions");return t.each(n.api.selecteddata(e),function(t,e){if(e[a.pk]==i)return o=e,!1}),o}}};return n}),function(t){t.fn.dragsort=function(e){if("destroy"==e)return void t(this.selector).trigger("dragsort-uninit");var i=t.extend({},t.fn.dragsort.defaults,e),n=[],o=null,a=null;return this.each(function(e,s){t(s).is("table")&&1==t(s).children().length&&t(s).children().is("tbody")&&(s=t(s).children().get(0));var r={draggedItem:null,placeHolderItem:null,pos:null,offset:null,offsetLimit:null,scroll:null,container:s,init:function(){i.tagName=""==i.tagName?0==t(this.container).children().length?"li":t(this.container).children().get(0).tagName.toLowerCase():i.tagName,""==i.itemSelector&&(i.itemSelector=i.tagName),""==i.dragSelector&&(i.dragSelector=i.tagName),""==i.placeHolderTemplate&&(i.placeHolderTemplate="<"+i.tagName+"> "),t(this.container).attr("data-listidx",e).mousedown(this.grabItem).bind("dragsort-uninit",this.uninit),this.styleDragHandlers(!0)},uninit:function(){var e=n[t(this).attr("data-listidx")];t(e.container).unbind("mousedown",e.grabItem).unbind("dragsort-uninit"),e.styleDragHandlers(!1)},getItems:function(){return t(this.container).children(i.itemSelector)},styleDragHandlers:function(e){this.getItems().map(function(){return t(this).is(i.dragSelector)?this:t(this).find(i.dragSelector).get()}).css("cursor",e?"pointer":"")},grabItem:function(e){var o=n[t(this).attr("data-listidx")],a=t(e.target).closest("[data-listidx] > "+i.tagName).get(0),s=o.getItems().filter(function(){return this==a}).length>0;if(!(1!=e.which||t(e.target).is(i.dragSelectorExclude)||t(e.target).closest(i.dragSelectorExclude).length>0)&&s&&(t(e.target).is(i.dragSelector)||t(e.target).closest(i.dragSelector).length)){e.preventDefault();for(var r=e.target;!t(r).is(i.dragSelector);){if(r==this)return;r=r.parentNode}t(r).attr("data-cursor",t(r).css("cursor")),t(r).css("cursor","move");var l=this,c=function(){o.dragStart.call(l,e),t(o.container).unbind("mousemove",c)};t(o.container).mousemove(c).mouseup(function(){t(o.container).unbind("mousemove",c),t(r).css("cursor",t(r).attr("data-cursor"))})}},dragStart:function(e){null!=o&&null!=o.draggedItem&&o.dropItem(),o=n[t(this).attr("data-listidx")],o.draggedItem=t(e.target).closest("[data-listidx] > "+i.tagName),o.draggedItem.attr("data-origpos",t(this).attr("data-listidx")+"-"+t(o.container).children().index(o.draggedItem));var a=parseInt(o.draggedItem.css("marginTop")),s=parseInt(o.draggedItem.css("marginLeft"));if(o.offset=o.draggedItem.offset(),o.offset.top=e.pageY-o.offset.top+(isNaN(a)?0:a)-1,o.offset.left=e.pageX-o.offset.left+(isNaN(s)?0:s)-1,!i.dragBetween){var r=0==t(o.container).outerHeight()?Math.max(1,Math.round(.5+o.getItems().length*o.draggedItem.outerWidth()/t(o.container).outerWidth()))*o.draggedItem.outerHeight():t(o.container).outerHeight();o.offsetLimit=t(o.container).offset(),o.offsetLimit.right=o.offsetLimit.left+t(o.container).outerWidth()-o.draggedItem.outerWidth(),o.offsetLimit.bottom=o.offsetLimit.top+r-o.draggedItem.outerHeight()}var l=o.draggedItem.height(),c=o.draggedItem.width();if("tr"==i.tagName?(o.draggedItem.children().each(function(){t(this).width(t(this).width())}),o.placeHolderItem=o.draggedItem.clone().attr("data-placeholder",!0),o.draggedItem.after(o.placeHolderItem),o.placeHolderItem.children().each(function(){t(this).html(" ")})):(o.draggedItem.after(i.placeHolderTemplate),o.placeHolderItem=o.draggedItem.next().css({height:l,width:c}).attr("data-placeholder",!0)),"td"==i.tagName){var d=o.draggedItem.closest("table").get(0);t("
    ").appendTo("body").children().append(o.draggedItem)}var u=o.draggedItem.attr("style");o.draggedItem.attr("data-origstyle",u||""),o.draggedItem.css({position:"absolute",opacity:.8,"z-index":999,height:l,width:c}),o.scroll={moveX:0,moveY:0,maxX:t(document).width()-t(window).width(),maxY:t(document).height()-t(window).height()},o.scroll.scrollY=window.setInterval(function(){if(i.scrollContainer!=window)return void t(i.scrollContainer).scrollTop(t(i.scrollContainer).scrollTop()+o.scroll.moveY);var e=t(i.scrollContainer).scrollTop();(o.scroll.moveY>0&&e0)&&(t(i.scrollContainer).scrollTop(e+o.scroll.moveY),o.draggedItem.css("top",o.draggedItem.offset().top+o.scroll.moveY+1))},10),o.scroll.scrollX=window.setInterval(function(){if(i.scrollContainer!=window)return void t(i.scrollContainer).scrollLeft(t(i.scrollContainer).scrollLeft()+o.scroll.moveX);var e=t(i.scrollContainer).scrollLeft() ;(o.scroll.moveX>0&&e0)&&(t(i.scrollContainer).scrollLeft(e+o.scroll.moveX),o.draggedItem.css("left",o.draggedItem.offset().left+o.scroll.moveX+1))},10),t(n).each(function(t,e){e.createDropTargets(),e.buildPositionTable()}),o.setPos(e.pageX,e.pageY),t(document).bind("mousemove",o.swapItems),t(document).bind("mouseup",o.dropItem),i.scrollContainer!=window&&t(window).bind("wheel",o.wheel)},setPos:function(e,n){var a=n-this.offset.top,s=e-this.offset.left;i.dragBetween||(a=Math.min(this.offsetLimit.bottom,Math.max(a,this.offsetLimit.top)),s=Math.min(this.offsetLimit.right,Math.max(s,this.offsetLimit.left)));var r=this.draggedItem.offsetParent().not("body").offset();if(null!=r&&(a-=r.top,s-=r.left),i.scrollContainer==window)n-=t(window).scrollTop(),e-=t(window).scrollLeft(),n=Math.max(0,n-t(window).height()+5)+Math.min(0,n-5),e=Math.max(0,e-t(window).width()+5)+Math.min(0,e-5);else{var l=t(i.scrollContainer),c=l.offset();n=Math.max(0,n-l.height()-c.top)+Math.min(0,n-c.top),e=Math.max(0,e-l.width()-c.left)+Math.min(0,e-c.left)}o.scroll.moveX=0==e?0:e*i.scrollSpeed/Math.abs(e),o.scroll.moveY=0==n?0:n*i.scrollSpeed/Math.abs(n),this.draggedItem.css({top:a,left:s})},wheel:function(e){if(o&&i.scrollContainer!=window){var n=t(i.scrollContainer),a=n.offset();if(e=e.originalEvent,e.clientX>a.left&&e.clientXa.top&&e.clientY0?s.before(o.draggedItem):0==a[1]?t(n[a[0]].container).prepend(o.draggedItem):t(n[a[0]].container).append(o.draggedItem)}return o.draggedItem.removeAttr("data-origpos"),o.draggedItem=null,t(document).unbind("mousemove",o.swapItems),t(document).unbind("mouseup",o.dropItem),i.scrollContainer!=window&&t(window).unbind("wheel",o.wheel),!1}},swapItems:function(e){if(null==o.draggedItem)return!1;o.setPos(e.pageX,e.pageY);for(var s=o.findPos(e.pageX,e.pageY),r=o,l=0;-1==s&&i.dragBetween&&lo.draggedItem.offset().top||a.left>o.draggedItem.offset().left?t(r.pos[s].elm).before(o.placeHolderItem):t(r.pos[s].elm).after(o.placeHolderItem),d.each(function(){var e=c().eq(this.idx).get(0);this!=e&&c().index(this)t&&this.pos[i].tope)return i;return-1},createDropTargets:function(){i.dragBetween&&t(n).each(function(){var e=t(this.container).find("[data-placeholder]"),n=t(this.container).find("[data-droptarget]");e.length>0&&n.length>0?n.remove():0==e.length&&0==n.length&&("td"==i.tagName?t(i.placeHolderTemplate).attr("data-droptarget",!0).appendTo(this.container):t(this.container).append(o.placeHolderItem.removeAttr("data-placeholder").clone().attr("data-droptarget",!0)),o.placeHolderItem.attr("data-placeholder",!0))})}};r.init(),n.push(r)}),this},t.fn.dragsort.defaults={tagName:"",itemSelector:"",dragSelector:"",dragSelectorExclude:"input, textarea",dragEnd:function(){},dragBetween:!1,placeHolderTemplate:"",scrollContainer:window,scrollSpeed:5}}(jQuery),define("dragsort",function(){}),function(t){t.fn.addtabs=function(e){var i=t(this);e=t.extend({content:"",close:!0,monitor:"body",nav:".nav-addtabs",tab:".tab-addtabs",iframeUse:!0,simple:!1,iframeHeight:t(window).height()-50,iframeForceRefresh:!1,iframeForceRefreshTable:!1,callback:function(){}},e||{});var n=t(e.nav),o=t(e.tab);history.pushState&&t(window).on("popstate",function(i){var n=i.originalEvent.state;n&&t("a[addtabs="+n.id+"]",e.monitor).data("pushstate",!0).trigger("click")}),t(e.monitor).on("click","[addtabs]",function(i){if(0!==t(this).attr("url").indexOf("javascript:")){t(this).is("a")&&i.preventDefault();var n=t(this).attr("addtabs"),o=t(this).attr("title")?t(this).attr("title"):t.trim(t(this).text()),s=t(this).attr("url"),r=e.content?e.content:t(this).attr("content"),l="1"===t(this).attr("ajax")||"true"===t(this).attr("ajax"),c={url:s,title:o,id:n,content:r,ajax:l};if(document.title=o,history.pushState&&!t(this).data("pushstate")){var d=-1===s.indexOf("ref=addtabs")?s+(s.indexOf("?")>-1?"&":"?")+"ref=addtabs":s;try{window.history.pushState(c,o,d)}catch(i){}}t(this).data("pushstate",null),a.call(this,{id:n,title:t(this).attr("title")?t(this).attr("title"):t(this).html(),content:r,url:s,ajax:l})}}),n.on("click",".close-tab",function(){var e=t(this).prev("a").attr("aria-controls");return s(e),!1}),n.on("dblclick","li[role=presentation]",function(){t(this).find(".close-tab").trigger("click")}),n.on("click","li[role=presentation]",function(){t("a[addtabs="+t("a",this).attr("node-id")+"]").trigger("click")}),t(window).resize(function(){if("object"==typeof e.nav){var i=0;n.siblings().each(function(){i+=t(this).outerWidth()}),n.width(n.parent().width()-i)}else t("#nav").width(t("#header").find("> .navbar").width()-t(".sidebar-toggle").outerWidth()-t(".navbar-custom-menu").outerWidth()-20);r()});var a=function(i){var a,s,l,c;a=i.id,s="tab_"+i.id,l="con_"+i.id,c=i.url,c+=i.url.indexOf("?")>-1?"&addtabs=1":"?addtabs=1",e.simple&&(n.find("[role='presentation']").remove(),o.find("[role='tabpanel']").remove());var d=t("#"+s,n),u=t("#"+l,o);if(n.find("[role='presentation']").removeClass("active"),o.find("[role='tabpanel']").removeClass("active"),0===d.length){if(d=t('"),e.close&&t("li",n).length>0&&d.append(' '),0===u.length){if(u=t('
    '),i.content)u.append(i.content);else if(e.iframeUse&&!i.ajax){var h=e.iframeHeight;u.append('
    ')}else t.get(c,function(t){u.append(t)});o.append(u)}e.simple||(t(".tabdrop li",n).length>0?t(".tabdrop ul",n).append(d):n.append(d))}else if(e.iframeForceRefresh)t("#"+l+" iframe").attr("src",function(t,e){return e});else if(e.iframeForceRefreshTable)try{t("#"+l+" iframe").contents().find(".btn-refresh:not([data-force-refresh=false])").length>0&&t("#"+l+" iframe")[0].contentWindow.$(".btn-refresh:not([data-force-refresh=false])").trigger("click")}catch(t){}sessionStorage.setItem("addtabs",t(this).prop("outerHTML")),d.addClass("active"),u.addClass("active"),r()},s=function(a){var s="tab_"+a,l="con_"+a,c=t("#"+s,n),d=t("#"+l,o);if(i.find("li.active").not(".tabdrop").attr("id")===s){var u=c.prev().not(".tabdrop"),h=c.next().not(".tabdrop");u.length>0?u.find("a").trigger("click"):h.length>0?h.find("a").trigger("click"):t(">li:not(.tabdrop):last > a",n).trigger("click")}c.remove(),d.remove(),r(),e.callback()},r=function(){n.refreshAddtabs()}},t.fn.refreshAddtabs=function(){var e=t(this),i=t(".tabdrop",e);0===i.length&&(i=t(''),i.prependTo(e)),e.parent().is(".tabs-below")&&i.addClass("dropup");var n=0,o=e.width()-65,a=0,s=e.append(i.find("li")).find(">li").not(".tabdrop"),r=0;s.each(function(){r+=t(this).outerWidth(!0)}),e.width()o&&(i.find("ul").append(t(this)),n++)}),n>0&&(i.removeClass("hide"),1===i.find(".active").length?i.addClass("active"):i.removeClass("active"))):i.addClass("hide")}}(jQuery),define("addtabs",function(){}),function(t){"use strict";function e(e){return this.each(function(){var i=t(this),n=i.data(d.dataKey),o=t.extend({},c,i.data(),n&&n.option,"object"==typeof e&&e);n||i.data(d.dataKey,n=new d(this,o))})}function i(e){return t(e).closest("div.sp_container").find("input.sp_input")}function n(){return this.each(function(){var t=i(this),e=t.data(d.dataKey);e&&(e.prop.init_set=!0,e.clearAll(e),e.prop.init_set=!1)})}function o(){return this.each(function(){var t=i(this),e=t.data(d.dataKey);e&&e.elem.hidden.val()&&e.setInitRecord(!0)})}function a(e){return this.each(function(){if(e&&t.isArray(e)){var n=i(this),o=n.data(d.dataKey);o&&(o.clearAll(o),o.option.data=e)}})}function s(e){var n=!1;return this.each(function(){var o=i(this),a=o.data(d.dataKey);a&&("undefined"!==t.type(e)?a.disabled(a,e):n=a.disabled(a))}),n}function r(){var e="";return this.each(function(){var n=i(this),o=n.data(d.dataKey);if(o)if(o.option.multiple){var a=[];o.elem.element_box.find("li.selected_tag").each(function(e,i){a.push(t(i).text())}),e+=a.toString()}else e+=o.elem.combo_input.val()}),e}function l(){var e=[];return this.each(function(){var n=i(this),o=n.data(d.dataKey);if(o)if(o.option.multiple)o.elem.element_box.find("li.selected_tag").each(function(i,n){e.push(t(n).data("dataObj"))});else{var a=o.elem.combo_input.data("dataObj");a&&e.push(a)}}),e}var c={data:void 0,lang:"cn",multiple:!1,pagination:!0,dropButton:!0,listSize:10,multipleControlbar:!0,maxSelectLimit:0,selectToCloseList:!1,initRecord:void 0,dbTable:"tbl",keyField:"id",showField:"name",searchField:void 0,escape:!0,andOr:"OR",separator:",",orderBy:void 0,pageSize:10,params:void 0,formatItem:void 0,autoFillResult:!1,autoSelectFirst:!1,noResultClean:!0,selectOnly:!1,inputDelay:.5,eSelect:void 0,eOpen:void 0,eAjaxSuccess:void 0,eTagRemove:void 0,eClear:void 0},d=function(e,i){t.each({data:"source",keyField:"primaryKey",showField:"field",pageSize:"perPage"},function(t,e){void 0!==i[e]&&(i[t]=i[e],delete i[e])}),this.setOption(i),this.setLanguage(),this.setCssClass(),this.setProp(),this.setElem(e),this.setButtonAttrDefault(),this.setInitRecord(),this.eDropdownButton(),this.eInput(),this.eWhole()};d.version="2.19",d.dataKey="selectPageObject",d.prototype.setOption=function(e){e.searchField=e.searchField||e.showField,e.andOr=e.andOr.toUpperCase(),"AND"!==e.andOr&&"OR"!==e.andOr&&(e.andOr="AND");for(var i=["searchField"],n=0;n/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/`/g,"`"):t},d.prototype.strToArray=function(t){return t?t.replace(/[\s ]+/g,"").split(","):""},d.prototype.setOrderbyOption=function(e,i){var n=[],o=[];if("object"==typeof e)for(var a=0;a#item_text#',textKey:"#item_text#",valueKey:"#item_value#"},page:{current:"page_num",total:"page_count"},msg:{maxSelectLimit:"max_selected_limit"}}},d.prototype.elementRealSize=function(e,i){var n,o,a,s={absolute:!1,clone:!1,includeMargin:!1,display:"block"},r=s,l=e.eq(0),c=[],d="";n=function(){a=l.parents().addBack().filter(":hidden"),d+="visibility: hidden !important; display: "+r.display+" !important; ",!0===r.absolute&&(d+="position: absolute !important;"),a.each(function(){var e=t(this),i=e.attr("style");c.push(i),e.attr("style",i?i+";"+d:d)})},o=function(){a.each(function(e){var i=t(this),n=c[e];void 0===n?i.removeAttr("style"):i.attr("style",n)})},n();var u=/(outer)/.test(i)?l[i](r.includeMargin):l[i]();return o(),u},d.prototype.setElem=function(e){var i={},n=this.option,o=this.css_class,a=this.message,s=t(e),r=s.css("width"),l=s.outerWidth();r.indexOf("%")>-1||s.parent().length>0&&s.parent().width()==l?l="100%":(l<=0&&(l=this.elementRealSize(s,"outerWidth")),l<150&&(l=150)),i.combo_input=s.attr({autocomplete:"off"}).addClass(o.input).wrap("
    "),n.selectOnly&&i.combo_input.prop("readonly",!0),i.container=i.combo_input.parent().addClass(o.container),i.combo_input.prop("disabled")&&(n.multiple?i.container.addClass(o.disabled):i.combo_input.addClass(o.input_off)),i.container.width(l),i.button=t("
    ").addClass(o.button),i.dropdown=t(''),i.clear_btn=t("
    ").html(t("").addClass("spfont sp-close")).addClass(o.clear_btn).attr("title",a.clear),n.dropButton||i.clear_btn.addClass(o.align_right),i.element_box=t("