Browse Source

clash for marge dev

md0726
刘嘉炜 4 months ago
parent
commit
a06dced7ef
  1. 3
      src/App.vue
  2. 138
      src/components/period_select.vue
  3. 5
      src/js/api.js
  4. 12
      src/js/server.js
  5. 3
      src/main.js
  6. 4
      src/manifest.json
  7. 201
      src/pages.json
  8. 385
      src/pages/index/index.vue
  9. 6
      src/pages/login/login.vue
  10. 243
      src/pages/menu/forth.vue
  11. 32
      src/pages/menu/second.vue
  12. 117
      src/pages/menu/third.vue
  13. 4
      src/pages/merchant_login/merchant_login.vue
  14. 2
      src/pages/order_search/order_search.vue
  15. 3
      src/pages/web_view/web_view.vue
  16. 175
      src/pages/write_off/douyin/poi_list.vue
  17. 237
      src/pages/write_off/mall/list/list.vue
  18. 131
      src/pages/write_off/menu/menu.vue
  19. 70
      src/pages/write_off/null/null.vue
  20. 496
      src/pages/write_off/number_of_people/number_of_people.vue
  21. 364
      src/pages/write_off/operate/operate.vue
  22. 339
      src/pages/write_off/search_result/search_result.vue
  23. BIN
      src/static/images/code_null.png
  24. BIN
      src/static/images/countdown_bg.png
  25. BIN
      src/static/images/icon/arrow_b2.png
  26. BIN
      src/static/images/icon/index/green_bg_circle.png
  27. BIN
      src/static/images/icon/index/tab_12.png
  28. BIN
      src/static/images/icon/index/tab_8.png
  29. BIN
      src/static/images/icon/scan_code_btn.png
  30. BIN
      src/static/images/icon/selected_ring.png
  31. BIN
      src/static/images/icon/write_off_fail.png
  32. BIN
      src/static/images/order_null.png
  33. BIN
      src/static/images/scan_null.png
  34. BIN
      src/static/images/tab/ta_1.png
  35. BIN
      src/static/images/tab/ta_2.png
  36. BIN
      src/static/images/tab/ta_3.png
  37. BIN
      src/static/images/tab/ta_4.png
  38. BIN
      src/static/images/tab/tab_1.png
  39. BIN
      src/static/images/tab/tab_2.png
  40. BIN
      src/static/images/tab/tab_3.png
  41. BIN
      src/static/images/tab/tab_4.png
  42. BIN
      src/static/images/third_pages/banner.png
  43. BIN
      src/static/images/third_pages/bg.png
  44. BIN
      src/static/images/third_pages/tab_a.png
  45. BIN
      src/static/images/third_pages/tab_b.png
  46. BIN
      src/static/images/write_off/mall.png
  47. BIN
      src/static/images/write_off/order.png
  48. BIN
      src/static/images/write_off/people.png
  49. BIN
      src/static/images/write_off/site.png
  50. 74
      src/store/actions.js
  51. 22
      src/store/index.js
  52. 13
      src/store/mutations.js
  53. 188
      src/subpackage/authorization/components/login.vue
  54. 25
      src/subpackage/authorization/components/user_info/iconfont.css
  55. 133
      src/subpackage/authorization/components/user_info/impower.vue
  56. 357
      src/subpackage/authorization/components/user_info/tuniaoui-wx-user-info.vue
  57. 9
      src/subpackage/authorization/js/api.js
  58. 10
      src/subpackage/authorization/js/server.js
  59. 16
      src/subpackage/authorization/pages/index.vue
  60. 0
      src/subpackage/authorization/static/images/author_modal.png
  61. 4
      src/subpackage/blacklist/pages/abnormal_list/abnormal_list.vue
  62. 41
      src/subpackage/menu/components/bottom_logo.vue
  63. 87
      src/subpackage/menu/components/mine/header.vue
  64. 63
      src/subpackage/menu/components/mine/line_tab.vue
  65. 16
      src/subpackage/menu/pages/index.vue
  66. BIN
      src/subpackage/menu/static/images/arrow_b2.png
  67. BIN
      src/subpackage/menu/static/images/bot_logo.png
  68. BIN
      src/subpackage/menu/static/images/mine_tab/0.png
  69. BIN
      src/subpackage/menu/static/images/mine_tab/1.png
  70. BIN
      src/subpackage/menu/static/images/mine_tab/2.png
  71. BIN
      src/subpackage/menu/static/images/mine_tab/3.png
  72. BIN
      src/subpackage/menu/static/images/mine_tab/4.png
  73. BIN
      src/subpackage/menu/static/images/mine_tab/5.png
  74. BIN
      src/subpackage/menu/static/images/mine_tab/6.png
  75. 59
      src/subpackage/message/components/detail/answer_item.vue
  76. 51
      src/subpackage/message/components/detail/image_flow.vue
  77. 60
      src/subpackage/message/components/detail/info.vue
  78. 37
      src/subpackage/message/components/edit/fixed_button.vue
  79. 195
      src/subpackage/message/components/edit/info_edit.vue
  80. 189
      src/subpackage/message/components/edit/upload_img.vue
  81. 33
      src/subpackage/message/components/fixed_button.vue
  82. 85
      src/subpackage/message/components/message_item.vue
  83. 16
      src/subpackage/message/js/api.js
  84. 9
      src/subpackage/message/js/server.js
  85. 171
      src/subpackage/message/pages/complaint/detail.vue
  86. 87
      src/subpackage/message/pages/complaint/edit.vue
  87. 105
      src/subpackage/message/pages/complaint/list.vue
  88. 171
      src/subpackage/message/pages/work_order/detail.vue
  89. 99
      src/subpackage/message/pages/work_order/edit.vue
  90. 105
      src/subpackage/message/pages/work_order/list.vue
  91. BIN
      src/subpackage/message/static/images/arrow_b2.png
  92. BIN
      src/subpackage/message/static/images/close.png
  93. BIN
      src/subpackage/message/static/images/message.png
  94. BIN
      src/subpackage/message/static/images/unfold.png
  95. 59
      src/subpackage/verification/components/head_bar.vue
  96. 78
      src/subpackage/verification/components/record/dy_item.vue
  97. 95
      src/subpackage/verification/components/record/mall_item.vue
  98. 87
      src/subpackage/verification/components/record/site_item.vue
  99. 15
      src/subpackage/verification/components/stadium_picker.vue
  100. 7
      src/subpackage/verification/js/api.js

3
src/App.vue

@ -3,9 +3,8 @@
import util from './utils/util';
export default {
onLaunch: function() {
// #ifndef H5
// this.$store.commit('setLoginState', { loginState: false });
this.updateManager();
// #endif
},
methods: {
isLogin(){

138
src/components/period_select.vue

@ -0,0 +1,138 @@
<template>
<!-- 时间段选择 -->
<view class="period-select-mask" v-if="isShow" @click="isShow = false">
<view class="ol-period" @click.stop="()=>false">
<view class="op-tit">日期范围</view>
<picker mode="date" class="op-picker" @change="startTimeChange" :value="startTime">
<view class="op-time">
<text class="ot-tit">开始时间</text>
<view class="ot-right">
<input class="or-ipt" placeholder="选择时间" disabled :value="startTime"/>
<image class="or-icon" mode="aspectFit" src="/static/images/icon/arrow_b2.png"></image>
</view>
</view>
</picker>
<picker mode="date" class="op-picker" @change="endTimeChange" :value="endTime">
<view class="op-time">
<text class="ot-tit">截止时间</text>
<view class="ot-right">
<input class="or-ipt" placeholder="选择时间" disabled :value="endTime"/>
<image class="or-icon" mode="aspectFit" src="/static/images/icon/arrow_b2.png"></image>
</view>
</view>
</picker>
<view class="pl-btn active" @click="confirmBtn">确定</view>
</view>
</view>
</template>
<script>
import { showNone, showModal } from '@/utils/util';
export default {
props: {
value: {
type: String,
default: ''
}
},
watch: {
value: {
handler(val) {
let [start, end] = val.split('_');
this.startTime = start;
this.endTime = end;
},
immediate: true
}
},
data(){
return {
isShow: false,
startTime: '',
endTime: '',
}
},
methods: {
show(){
this.isShow = true
},
hide(){
this.isShow = false
},
startTimeChange(e){
this.startTime = e.detail.value;
},
endTimeChange(e){
this.endTime = e.detail.value;
},
confirmBtn(){
let { startTime, endTime } = this;
let _sTimestemp = new Date(startTime.replace(/\-/g,'/')).getTime();
let _eTimestemp = new Date(endTime.replace(/\-/g,'/')).getTime();
if(_sTimestemp > _eTimestemp)return showModal({ content: '开始时间不能大于结束时间' });
this.$emit('input', `${startTime}_${endTime}`);
this.hide();
}
}
}
</script>
<style lang="scss">
.period-select-mask{
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0,0,0,0.4);
z-index: 9999;
}
.ol-period{
position: absolute;
left: 0;
top: 0;
padding-left: 38upx;
padding-top: 66upx;
height: 472upx;
width: 100%;
background-color: #fff;
.op-tit{
@include flcw(24upx, 34upx, #9c9c9f);
}
.op-picker{
width: 100%;
}
.op-time{
padding-right: 40upx;
height: 124upx;
border-bottom: 2upx solid #D8D8D8;
@include ctf(space-between);
.ot-tit{
flex-shrink: 0;
@include flcw(32upx, 44upx, #1a1a1a);
}
.ot-right{
flex-grow: 1;
@include ctf(flex-end);
.or-ipt{
flex-grow: 1;
height: 100%;
text-align: right;
@include flcw(32upx, 44upx, #1a1a1a);
}
.or-icon{
flex-shrink: 0;
margin-left: 36upx;
width: 28upx;
height: 28upx;
}
}
}
.pl-btn{
min-height: 124upx;
text-align: center;
@include flcw(32upx, 124upx, $mColor, 500);
}
}
</style>

5
src/js/api.js

@ -167,5 +167,10 @@ API['party'] = {
bindingOpenId: `${ORIGIN}/assistant/bindingOpenId`, //改为小程序端绑定openid
}
API['mine'] = {
userCurrent:`${ORIGIN}/user/current`, //获取账户信息, (不要传品牌id)
unbindAssistant:`${ORIGIN}/admin/user/unbindAssistant`, // 解绑退出
}
export default { ORIGIN, API };

12
src/js/server.js

@ -1,5 +1,6 @@
import util from '../utils/util';
// import { app as vm } from '../main';
import { app as vm } from '../main';
const islog = true;
@ -27,6 +28,9 @@ export class Server {
method: method,
data: res,
})
handleUserExceptions({ res });
if(isDefaultGet){
if(failMsg == '')throw Error('默认回调,失败提示不能为空 key -> failMsg');
defaultGet({
@ -70,6 +74,14 @@ export class Server {
reject({url,res,data});
}
}
// 用户信息异常
function handleUserExceptions({ res }){
if(res?.data?.code === 401&&res?.data?.message?.indexOf('用户') !== -1){
console.log('用户信息异常,请重新登录');
const _store = vm.$store;
_store.commit('setLoginState', { loginState: false });
}
}
}
get({url,data={},header={},isDefaultGet=true,failMsg=''}){
return this.request(url,data,'GET',header,isDefaultGet,failMsg);

3
src/main.js

@ -6,8 +6,9 @@ Vue.config.productionTip = false
App.mpType = 'app'
const app = new Vue({
export const app = new Vue({
...App,
store
})
app.$mount()

4
src/manifest.json

@ -76,7 +76,9 @@
"appid" : "wx7106e84614cf0060",
"setting" : {
"urlCheck" : false,
"minified" : true
"minified" : true,
"ignoreDevUnusedFiles": false,
"ignoreUploadUnusedFiles": false
},
"usingComponents" : true,
"optimization": {

201
src/pages.json

@ -1,12 +1,47 @@
{
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"componentPlaceholder": {
"bottom-logo": "view",
"authorization-login": "view"
}
}
},
{
"path": "pages/menu/second",
"style": {
"navigationBarTitleText": "推广营销",
"componentPlaceholder": {
"bottom-logo": "view"
}
}
},
{
"path": "pages/menu/third",
"style": {
"navigationBarTitleText": "商家服务",
"componentPlaceholder": {
"bottom-logo": "view"
}
}
},
{
"path": "pages/menu/forth",
"style": {
"navigationBarTitleText": "个人中心",
"componentPlaceholder": {
"mine-header": "view",
"bottom-logo": "view",
"line-tab": "view",
"authorization-login": "view",
"authorization-user": "view"
}
}
},
{
"path": "pages/login/login",
"style": {
"navigationBarTitleText": "登录"
@ -171,18 +206,6 @@
}
},
{
"path": "pages/write_off/mall/list/list",
"style": {
"navigationBarTitleText": "商城订单核销"
}
},
{
"path": "pages/write_off/menu/menu",
"style": {
"navigationBarTitleText": "核销订单"
}
},
{
"path": "pages/write_off/ym_card_gated/ym_card_gated",
"style": {
"navigationBarTitleText": "核销订单"
@ -195,36 +218,12 @@
}
},
{
"path": "pages/write_off/search_result/search_result",
"style": {
"navigationBarTitleText": "核销查询"
}
},
{
"path": "pages/write_off/operate/operate",
"style": {
"navigationBarTitleText": "核销订单"
}
},
{
"path": "pages/write_off/confirm_order/confirm_order",
"style": {
"navigationBarTitleText": "核销订单"
}
},
{
"path": "pages/write_off/null/null",
"style": {
"navigationBarTitleText": "确认订单信息"
}
},
{
"path": "pages/write_off/douyin/poi_list",
"style": {
"navigationBarTitleText": "核销订单"
}
},
{
"path": "pages/employee/review_list/review_list",
"style": {
"navigationBarTitleText": "员工管理"
@ -759,9 +758,15 @@
}
},
{
"path": "pages/index/recharge_record",
"style" : {
"navigationBarTitleText": "充值记录"
}
},
{
"path": "pages/douyin_withdraw/index",
"style" : {
"navigationBarTitleText": "抖音提现"
"navigationBarTitleText": "平台提现"
}
},
{
@ -895,10 +900,99 @@
"root": "subpackage/verification",
"pages": [
{
"path": "pages/null",
"style" : {
"navigationBarTitleText": "核销查询"
}
},
{
"path": "pages/record_search",
"style" : {
"navigationBarTitleText": "核销查询"
}
},
{
"path": "pages/index",
"style" : {
"navigationBarTitleText": "核销查询"
}
},
{
"path": "pages/site_people/index",
"style" : {
"navigationBarTitleText": "现场人数"
}
},
{
"path": "pages/record",
"style" : {
"navigationBarTitleText": "核销记录"
}
}
]
},
{
"root": "subpackage/message",
"pages": [
{
"path": "pages/complaint/edit",
"style" : {
"navigationBarTitleText": "投诉建议"
}
},
{
"path": "pages/complaint/list",
"style" : {
"enablePullDownRefresh" : true,
"navigationBarTitleText": "投诉建议"
}
},
{
"path": "pages/complaint/detail",
"style" : {
"navigationBarTitleText": "投诉详情"
}
},
{
"path": "pages/work_order/list",
"style" : {
"enablePullDownRefresh" : true,
"navigationBarTitleText": "工单列表"
}
},
{
"path": "pages/work_order/detail",
"style" : {
"navigationBarTitleText": "工单详情"
}
},
{
"path": "pages/work_order/edit",
"style" : {
"navigationBarTitleText": "提交工单"
}
}
]
},
{
"root": "subpackage/menu",
"pages": [
{
"path": "pages/index",
"style" : {
"navigationBarTitleText": ""
}
}
]
},
{
"root": "subpackage/authorization",
"pages": [
{
"path": "pages/index",
"style" : {
"navigationBarTitleText": ""
}
}
]
},
@ -955,5 +1049,36 @@
"navigationBarTitleText": "欧轩智能商家助手",
"navigationBarBackgroundColor": "#009874",
"backgroundColor": "#f2f2f7"
},
"tabBar": {
"color": "#B2B2B2",
"selectedColor": "#009874",
"backgroundColor": "#FFFFFF",
"list": [
{
"pagePath": "pages/index/index",
"text": "工作台",
"iconPath": "static/images/tab/tab_1.png",
"selectedIconPath": "static/images/tab/ta_1.png"
},
{
"pagePath": "pages/menu/second",
"text": "推广营销",
"iconPath": "static/images/tab/tab_2.png",
"selectedIconPath": "static/images/tab/ta_2.png"
},
{
"pagePath": "pages/menu/third",
"text": "商家服务",
"iconPath": "static/images/tab/tab_3.png",
"selectedIconPath": "static/images/tab/ta_3.png"
},
{
"pagePath": "pages/menu/forth",
"text": "我的",
"iconPath": "static/images/tab/tab_4.png",
"selectedIconPath": "static/images/tab/ta_4.png"
}
]
}
}

385
src/pages/index/index.vue

@ -2,22 +2,22 @@
<view class="index-container">
<view class="ic-content">
<view class="ic-header">
<view class="ih-address" v-if="loginStatus" @click="toStoreList">
<view class="ih-address" v-if="loginState" @click="toStoreList">
<view>{{indexData.brand_name || '-'}}({{indexData.stadium_num || '0'}})</view>
<image src="/static/images/icon/arrow_ff.png" mode="aspectFit"></image>
</view>
<view v-else class="ih-btn" hover-class="hover-active" @click="showAuthor">点击登陆</view>
<block v-if="isPermissionShowTab({ serverKey: 1001 })">
<view class="ih-tip">今日总收入</view>
<view class="ih-price"><text>{{loginStatus?'¥':''}}</text>{{loginStatus?(indexData.amount || '0'):'***'}}</view>
<view class="ih-price"><text>{{loginState?'¥':''}}</text>{{loginState?(indexData.amount || '0'):'***'}}</view>
<view class="ih-amount">
<view>
<view>收款笔数</view>
<view>{{loginStatus?(indexData.in_count || '0'):'**'}}</view>
<view>{{loginState?(indexData.in_count || '0'):'**'}}</view>
</view>
<view>
<view>退款笔数</view>
<view>{{loginStatus?(indexData.out_count || '0'):'**'}}</view>
<view>{{loginState?(indexData.out_count || '0'):'**'}}</view>
</view>
</view>
</block>
@ -37,7 +37,8 @@
</view>
<view class="ic-tabs" v-if="indexData&&indexData.permission">
<block v-for="(e, i) in tabList" :key="i">
<view class="it-item" @click="toPageInfo(e)" v-if="isPermissionShowTab(e)">
<!-- tid1807 去掉"核销查询菜单(已有悬浮按钮) -->
<view class="it-item" @click="toPageInfo(e)" v-if="isPermissionShowTab(e)&&e.id!=4">
<view>
<image mode="aspectFit" :src="'/static/images/icon/index/tab_'+ e.id + '.png'"></image>
<view>{{e.name}}</view>
@ -49,33 +50,12 @@
<navigator url="/subpackage/party/pages/login/login">party/pages/login/login</navigator>
</view> -->
</view>
<view class="ox-dark-mask" v-if="isShowAuthorModal">
<view class="ic-author-modal">
<view class="iam-title">微信授权</view>
<view class="iam-tip">您的信息和数据将受到保护</view>
<image class="iam-pic" mode="aspectFit" src="/static/images/icon/author_modal.png"></image>
<view class="iam-btns">
<button plain hover-class="hover-active" @click="cancelAuthor">取消</button>
<bottom-logo></bottom-logo>
<button
v-if="isProfile"
plain
hover-class="hover-active"
@click="profileConfirm"
>授权并登录</button>
<button
v-else
plain
hover-class="hover-active"
open-type="getUserInfo"
lang="zh_CN"
@getuserinfo="confirmAuthor"
>授权并登录</button>
</view>
</view>
</view>
<authorization-login ref="authorizationLogin"></authorization-login>
<view class="fly_btn" @click="toPageInfo(tabList[4])" v-if="loginStatus">
<view class="fly_btn" @click="toPageInfo(getTabForID(4))" v-if="loginState&&isPermissionShowTab(getTabForID(4))">
<image class="f_bg" src="/static/images/icon/index/green_bg_circle.png" mode="scaleToFill"/>
<image class="f_icon" src="/static/images/icon/index/scan_icon_white.png" mode="scaleToFill"/>
<text>核销</text>
@ -88,6 +68,19 @@
import util from '../../utils/util';
import { servers } from '../../js/server';
import { API } from '../../js/api';
import { mapGetters } from 'vuex';
import bottomLogo from "@/subpackage/menu/components/bottom_logo.vue";
import authorizationLogin from "@/subpackage/authorization/components/login.vue";
/**
* tid1807
* 1去掉"核销查询菜单(已有悬浮按钮) *
* 2去掉收款记录(在收入统计页面已有入口进入收款记录) *
* 3去掉进场人数异常(该入口计划放入新改版的核销查询页面) *
* 4系统工具文案改为小程序管理;钱包提现文案改为钱包&提现: 设备管理文案改为智能设备; *
* 5增加底部 (工作台推广营销商家服务我的) *
* 6增加工单功能
*
* */
const tabList = [
{
id: 0,
@ -96,16 +89,10 @@
serverKey: 1001 //
},
{
id: 1,
name: '收款记录',
path: '/subpackage/income/pages/details_record/details_record',
serverKey: 1002 //
},
{
id: 2,
name: '订单管理',
path: '/subpackage/order/pages/order_manage/order_manage',
serverKey: 1012 //
id: 11,
name: '钱包&提现',
path: '/subpackage/wallet/pages/index/index',
serverKey: 1017 // 1017
},
{
id: 3,
@ -114,10 +101,10 @@
serverKey: 1007 //
},
{
id: 4,
name: '核销查询',
path: '/pages/write_off/menu/menu',
serverKey: 1008 //
id: 2,
name: '订单管理',
path: '/subpackage/order/pages/order_manage/order_manage',
serverKey: 1012 //
},
{
id: 5,
@ -127,23 +114,11 @@
serverKey: 1009 //
},
{
id: 6,
name: '设备管理',
path: '/subpackage/device/pages/index/index',
serverKey: 1010 //
},
{
id: 7,
name: '商品零售',
path: '/subpackage/retail/pages/index/index',
serverKey: 1011 //
},
// {
// id: 8,
// name: '',
// path: '/subpackage/course/pages/index/index',
// serverKey: 1013 // 1013
// },
{
id: 9,
name: '储值卡管理',
@ -151,85 +126,91 @@
serverKey: 1014 // 1014
},
{
id: 13,
name: '水阀卡管理',
path: '/subpackage/shower/pages/card/manage',
serverKey: 1019 // 1019
id: 6,
name: '智能设备',
path: '/subpackage/device/pages/index/index',
serverKey: 1010 //
},
{//ICON
id: 12,
name: '小程序管理',
path: '/subpackage/party/pages/index/index',
serverKey: 1016 // 1016
},
{
id: 10,
name: '进场人数异常',
path: '/subpackage/blacklist/pages/abnormal_list/abnormal_list',
serverKey: 1015 // 1015
id: 13,
name: '工单',
path: '/subpackage/message/pages/work_order/list',
serverKey: 1022 // 1022
},
// {
// id: 1,
// name: '',
// path: '/subpackage/income/pages/details_record/details_record',
// serverKey: 1002 //
// },
{
id: 11,
name: '钱包提现',
path: '/subpackage/wallet/pages/index/index',
serverKey: 1017 // 1017
id: 4,
name: '核销查询',
path: '/subpackage/verification/pages/index',
serverKey: 1008 //
},
// {
// id: 8,
// name: '',
// path: '/subpackage/course/pages/index/index',
// serverKey: 1013 // 1013
// },
{//ICON
id: 12,
name: '系统工具',
path: '/subpackage/party/pages/index/index',
serverKey: 1016 // 1016
// {
// id: 10,
// name: '',
// path: '/subpackage/blacklist/pages/abnormal_list/abnormal_list',
// serverKey: 1015 // 1015
// },
{
id: 13,
name: '水阀卡管理',
path: '/subpackage/shower/pages/card/manage',
serverKey: 1019 // 1019
},
];
const app = getApp();
// #ifndef H5
const uniGetSetting = util.promisify(uni.getSetting);
const uniLogin = util.promisify(uni.login);
const uniGetUserInfo= util.promisify(uni.getUserInfo);
const APPID = uni.getAccountInfoSync().miniProgram.appId;
// #endif
// #ifdef H5
const APPID = "wx7106e84614cf0060" //TODO
// #endif
export default {
components: { bottomLogo, authorizationLogin },
computed: {
isProfile: _=>util.isProfile(),
// loginStatus(){
// return app.isLogin();
// }
...mapGetters([ 'loginState' ]),
},
data() {
return {
tabList,
isShowAuthorModal: false,
indexData: {},
loginStatus: false,
}
},
async onLoad(options) {
try{
util.showLoad();
let _loginRes = {};
// 20230829
if(options.role !== 'ADMIN-PLATFORM')await this.checkUserAuthor();
util.hideLoad();
this.loginStatus = app.isLogin();
if(!!app.isLogin())this.getIndexInfo();
if(options.role !== 'ADMIN-PLATFORM')_loginRes = await this.$store.dispatch('checkUserAuthor');
// 20240325 openid
if(_loginRes?.data === '')this.webBrandUserCheck();
if(_loginRes?.data)this.getIndexInfo();
}catch(err){
util.hideLoad();
uni.removeStorageSync('token');
// util.showNone(err.message || '');
console.log('onLoad err',err);
this.loginStatus = app.isLogin();
console.warn('pages index onLoad err--->', err);
}
},
onShow(){
let { indexData } = this;
if(JSON.stringify(indexData)!='{}'&&!!app.isLogin())this.getIndexInfo();
let { indexData, loginState, getIndexInfo } = this;
if(JSON.stringify(indexData)!='{}'&&loginState)getIndexInfo();
},
methods: {
getTabForID(id){
return tabList.find(e=>e.id === id) || {}
},
// ID1000840
isPermissionShowTab(e){
let { indexData } = this;
@ -239,13 +220,13 @@
return false;
},
toNoticeList(){
if(!app.isLogin())return this.showAuthor();
if(!this.loginState)return this.showAuthor();
util.routeTo(`/pages/message/list/list`,'nT');
},
toPageInfo(tabInfo){
let { indexData } = this;
let { indexData, loginState } = this;
let _permission = indexData.permission || {};
if(!app.isLogin())return this.showAuthor();
if(!loginState)return this.showAuthor();
if(!tabInfo.path)return util.showNone('暂未开放!');
//if(tabInfo.id == 2)return util.routeTo(tabInfo.path,'nT'); //
if(!_permission[tabInfo.serverKey])return util.showNone('暂无权限,请联系管理员开启!')
@ -260,7 +241,6 @@
if([ 10, 11, 4, 13 ].includes(tabInfo.id))return util.routeTo(tabInfo.path + `?brand_id=${indexData.brand.id}`,'nT');
if(tabInfo.id === 12){
// if(!indexData.brand.mini_wechat_appid)return util.showNone('appid')
let url = tabInfo.path + `?appid=${indexData.brand.mini_wechat_appid}`
return util.routeTo(url,'nT');
}
@ -280,137 +260,15 @@
this.$store.commit('setBrandInfo',res);
})
},
// token
async checkUserAuthor(){
try{
let loginRes =""
// #ifndef H5
loginRes = await uniLogin();
// #endif
// #ifdef H5
loginRes = {errMsg: "login:ok", code: "0c3xEi0w3kr1t23zcU3w3ZQR3w3xEi0h"} //TODO H5
// #endif
console.log(loginRes)
return servers.get({
url: API.wechatMiniAppLoginAndSync,
data: {
code: loginRes.code,
appid: APPID,
// #ifdef H5
token:"3d2b0092-e761-11ee-8a66-5254005df464", //TODO
// #endif
},
isDefaultGet: false,
})
.then(res=>{
util.hideLoad();
let _data = res.data;
if(_data.code == 0){
// let _data = res.data.data;
if(_data.data == ''){
uni.removeStorageSync('token');
this.loginStatus = app.isLogin();
// 20240325 openid
this.webBrandUserCheck();
// return util.routeTo(`/pages/login/login`,'rL');
}
uni.setStorageSync('token',_data.data);
return _data;
}else{
util.showNone(_data.message || '校验身份失败!');
throw res.data || {};
}
})
.catch(err=>{
throw err;
})
}catch(err){
throw err;
}
},
showAuthor(){
this.isShowAuthorModal = true
},
closeAuthor(){
this.isShowAuthorModal = false
},
cancelAuthor(){
this.closeAuthor();
},
getLoginQuery({
userInfo,
loginRes
}){
return {
appid: APPID,
code: loginRes.code,
encryptedData: userInfo.encryptedData,
// is_details: 1,
//
user_info: userInfo.userInfo,
user_raw_data: userInfo.rawData,
...userInfo.userInfo,
}
},
//
profileConfirm(){
uni.getUserProfile({
lang: 'zh_CN', desc: '授权登陆',
success: res => {
this.confirmAuthor({detail: {...res}});
this.$refs?.authorizationLogin?.alert?.({
success: ()=>{
setTimeout(this.getIndexInfo, 1000);
},
fail: function(err) {
util.showNone('获取用户信息失败!请重试');
console.warn('getUserProfile Err', err)
fail: err =>{
// console.warn('pages index showAuthor authorizationLogin Err ->', err)
}
})
},
async confirmAuthor(userRes){
if(!userRes.detail.userInfo){
this.closeAuthor();
return util.showNone('获取用户信息失败!请稍后重试');
}
let loginRes = await uniLogin();
if(!loginRes.code){
this.closeAuthor();
return util.showNone('获取登陆凭证失败!稍后重试');
}
servers.post({
url: API.wechatMiniAppLoginAndSync,
data: this.getLoginQuery({
userInfo: userRes.detail,
loginRes
}),
isDefaultGet: false,
})
.then(res=>{
util.hideLoad();
let _data = res.data || {};
if(_data.code == 0){
if(_data.data == '')return util.routeTo(`/pages/login/login`,'rL');
util.showNone(_data.message || '登陆成功!');
// let _data = res.data.data;
// if(_data.user.role == '')return util.routeTo(`/pages/merchant_login/merchant_login`,'rL');
uni.setStorageSync('token',_data.data);
setTimeout(_=>{
this.getIndexInfo();
this.closeAuthor();
this.loginStatus = app.isLogin();
}, 1200);
}else{
util.showNone(_data.message || '后台登陆失败!');
setTimeout(_=>this.closeAuthor(), 1200);
}
}).catch(util.hideLoad)
},
webBrandUserCheck(){
@ -456,8 +314,8 @@
})
},
toStoreList(){
if(!app.isLogin())return this.showAuthor();
let { indexData } = this;
let { indexData, loginState } = this;
if(!loginState)return this.showAuthor();
util.routeTo(`/pages/store_list/store_list?brand_id=${indexData.brand.id}`,'nT');
},
bindUserOpenid(){
@ -687,57 +545,6 @@
}
}
.ic-author-modal{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding-top: 60upx;
width: 662upx;
height: 884upx;
border-radius: 10upx;
background-color: #fff;
.iam-title{
margin-bottom: 22upx;
text-align: center;
line-height: 60upx;
font-size: 44upx;
font-weight: 500;
color: #1a1a1a;
}
.iam-tip{
margin-bottom: 52upx;
line-height: 40upx;
text-align: center;
font-size: 28upx;
color: #9c9c9f;
}
.iam-pic{
margin: 0 auto 62upx;
display: block;
width: 488upx;
height: 416upx;
}
.iam-btns{
@include centerFlex(center);
>button{
margin: 0 20upx;
width: 240upx;
height: 92upx;
line-height: 88upx;
text-align: center;
border-radius: 46upx;
border: 2upx solid $themeColor;
font-size: 32upx;
color: $themeColor;
&+button{
background-color: $themeColor;
color: #fff;
}
}
}
}
// fly_btn
.fly_btn{

6
src/pages/login/login.vue

@ -43,7 +43,7 @@
<script>
import { API } from '../../js/api';
import { servers } from '../../js/server';
import util from '../../utils/util';
import util, { jsonStr } from '../../utils/util';
// import wxplugin from '../../utils/wx_plugin';
const uniGetSetting = util.promisify(uni.getSetting);
const uniLogin = util.promisify(uni.login);
@ -67,7 +67,7 @@ export default {
//
MiniprogramThirdpartyPlugin.init(wx)
// MiniprogramThirdpartyPlugin.init(wx)
},
@ -189,7 +189,7 @@ export default {
}
},
toWebView(){
util.routeTo(`/pages/web_view/web_view?src=${encodeURIComponent("https://www.ouxuanzhineng.cn")}`,'rL');
util.routeTo(`/pages/web_view/web_view?src=${jsonStr("https://www.ouxuanzhineng.cn")}`,'rL');
}
},
}

243
src/pages/menu/forth.vue

@ -0,0 +1,243 @@
<template>
<view class="forth-menu">
<!-- 后端 account-> 如果有手机号就显示手机号 没有的话显示username, 都没有的话就空好了 -->
<mine-header
:is-login="loginState"
:nickname="userInfo.nickname"
:name="userInfo.actual_name"
:account="userInfo.mobile || userInfo.username || ''"
:photo="userInfo.avatar_url"
@on:munted="menuPackageLoaded = true"
@click:login="loginBtn"
@click:update="updateUser"
></mine-header>
<block v-if="menuPackageLoaded">
<line-tab :icon-num='0'>
<template slot="default">账号管理</template>
<template slot="right">
<view class="ft-account">
<view class="fc-name" v-if="loginState">{{ accountName || '-' }}</view>
<image class="fc-icon" mode="aspectFit" src="/static/images/icon/arrow_b2.png"></image>
</view>
</template>
</line-tab>
<line-tab :icon-num='1' @click="toWebView(deadData.helpCenterLink)">帮助中心</line-tab>
<block v-if="loginState">
<line-tab :icon-num='2' @click="toMiniProgram(deadData.assistantCoachAppid)">教练助手</line-tab>
<line-tab :icon-num='3'>
<template slot="default">收银系统</template>
<template slot="right">
<view class="fm-copy" @click="copyLink(deadData.cashierSystemLink)">(复制网址)</view>
</template>
</line-tab>
<line-tab :icon-num='4' @click="toWebView(deadData.backstageLink)">
<view class="fm-admin">
<view class="fa-txt">总后台<text class="ft-txt">({{ deadData.backstageLink }})</text></view><view class="fa-copy" @click.stop="copyLink(deadData.backstageLink)">复制</view>
</view>
</line-tab>
</block>
<line-tab :icon-num='5' @click="toComplaint">投诉建议</line-tab>
<line-tab :icon-num='6' v-if="loginState" @click="unBindBtn">解绑退出</line-tab>
</block>
<bottom-logo></bottom-logo>
<authorization-login ref="authorizationLogin"></authorization-login>
<authorization-user ref="authorizationUser"></authorization-user>
</view>
</template>
<script>
import mineHeader from "@/subpackage/menu/components/mine/header.vue";
import lineTab from "@/subpackage/menu/components/mine/line_tab.vue";
import bottomLogo from "@/subpackage/menu/components/bottom_logo.vue";
import authorizationLogin from "@/subpackage/authorization/components/login.vue";
import authorizationUser from "@/subpackage/authorization/components/user_info/impower.vue";
import { routeTo, debounce, showLoad, hideLoad, showModal, showNone, jsonStr } from "@/utils/util.js";
import { mapGetters, mapState } from 'vuex';
import { servers } from '../../js/server';
import { API } from '../../js/api';
export default {
components:{
mineHeader,
lineTab,
bottomLogo,
authorizationLogin,
authorizationUser,
},
data(){
return {
//
menuPackageLoaded: false,
userInfo: {},
deadData: {
helpCenterLink: 'https://help.ouxuanzhineng.cn/', //
cashierSystemLink: 'https://kb.ouxuanzhineng.cn/', //
backstageLink: 'https://admin.ouxuanzhineng.cn/', //
assistantCoachAppid: 'wxd71043ec955dfecf', // AppID
},
}
},
computed: {
...mapGetters([ 'loginState' ]),
...mapState([ 'brandInfo' ]),
extension(){
return this.userInfo?.extension || {}
},
/**
* @param {string} permission_type 1:ADMIN-STADIUM 2:ADMIN-BRAND
* */
permissionType(){
return this.userInfo?.extension?.permission_type || '';
},
accountName(){
let { permissionType, extension } = this;
if(permissionType === 'ADMIN-STADIUM')return extension?.stadium_name ?? '';
if(permissionType === 'ADMIN-BRAND')return extension?.brand_name ?? '';
return '';
}
},
onLoad(options){
if(this.loginState)this.getUserInfo();
},
methods: {
//
unBindBtn: debounce(function(){
let { userInfo } = this;
if(!userInfo?.id)return showModal({ content: '用户信息有误' })
showModal({
content: '您确定要解绑退出吗?',
showCancel: true,
success: mRes=>{
if(mRes.confirm)this.unBindReq(userInfo.id);
}
})
}, 300, true),
//
unBindReq(user_id){
showLoad();
return servers.post({
url: API.mine.unbindAssistant,
data: { user_id },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
showNone('操作成功!');
setTimeout(() => {
this.$store.commit('setLoginState', { loginState: false });
routeTo(`/pages/login/login`,'rL');
}, 1000);
console.log('pages menu unBindReq res --->', _data);
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({ content: err?.message || '操作失败!' });
console.warn('pages menu unBindReq err --->', err);
// return Promise.reject(err);
})
},
//
updateUser(){
this.$refs?.authorizationUser?.show?.({ success: this.getUserInfo });
},
//
toMiniProgram(appid){
uni.navigateToMiniProgram({ appId: appid });
},
//
toWebView(url){
routeTo(`/pages/web_view/web_view?src=${jsonStr(url)}`, 'nT');
},
//
copyLink(url){
uni.setClipboardData({ data: url });
},
//
toComplaint(){
let { loginState, showAuthor, brandInfo } = this;
if(!loginState)return showAuthor();
routeTo(`/subpackage/message/pages/complaint/list?brand_id=${brandInfo?.brand?.id}`, 'nT');
},
//
loginBtn(){
this.showAuthor();
},
showAuthor(){
this.$refs?.authorizationLogin?.alert?.({ success: this.getUserInfo });
},
getUserInfo(){
showLoad();
return servers.post({
url: API.mine.userCurrent,
data: {},
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
this.userInfo = _data?.data || {};
console.log('pages menu getUserInfo res --->', _data);
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({ content: err?.message || '加载用户失败!' });
console.warn('pages menu getUserInfo err --->', err);
// return Promise.reject(err);
})
},
}
}
</script>
<style lang="scss">
.forth-menu{
}
.fm-txt{
font-size: 28upx;
.ft-copy{
color: $mColor;
}
}
.ft-account{
@include ctf(flex-end);
.fc-name{
@include flcw(32upx, 48upx, #9A9A9D);
@include tHide;
}
.fc-icon{
flex-shrink: 0;
margin-left: 22upx;
width: 30upx;
height: 30upx;
}
}
.fm-copy{
@include flcw(32upx, 44upx, $mColor);
}
.fm-admin{
@include ctf;
.fa-txt{
@include flcw(32upx, 44upx, #9A9A9D);
@include tHide;
.ft-txt{
font-size: 28upx;
}
}
.fa-copy{
padding: 0upx 10upx;
flex-shrink: 0;
@include flcw(28upx, 44upx, $mColor);
}
}
</style>

32
src/pages/menu/second.vue

@ -0,0 +1,32 @@
<template>
<view class="second-pages">
<view class="sp-post">
<view class="sp-box">
<view class="tl-tit">板块更新中</view>
</view>
</view>
<bottom-logo is-fixed></bottom-logo>
</view>
</template>
<script>
import bottomLogo from "@/subpackage/menu/components/bottom_logo.vue";
export default {
components:{ bottomLogo },
}
</script>
<style lang="scss">
.sp-post{
padding: 24upx;
.sp-box{
padding: 22upx 28upx;
height: 400upx;
background-image: linear-gradient(270deg, #d7d7da 0%, $mColor 100%);
.tl-tit{
@include flcw(43upx, 60upx, #fff, 500);
@include tHide;
}
}
}
</style>

117
src/pages/menu/third.vue

@ -0,0 +1,117 @@
<template>
<view class="third-page">
<view class="tp-container">
<image class="tc-post" mode="aspectFit" src="/static/images/third_pages/banner.png"></image>
<view class="tc-bot">
<view class="tb-left" @click="clickTap">
<image class="tl-bg" mode="aspectFill" src="/static/images/third_pages/bg.png"></image>
<view class="tl-tit">采购商城</view>
<view class="tl-txt">采购专业装备</view>
</view>
<view class="tb-right">
<view class="tr-item active" @click="clickTap">
<view class="ti-title">客服外包</view>
<view class="ti-text">客服外包</view>
<image class="tr-icon" src="/static/images/third_pages/tab_a.png"></image>
</view>
<view class="tr-item" @click="clickTap">
<view class="ti-title">敬请期待</view>
<view class="ti-text">敬请期待</view>
<image class="tr-icon" src="/static/images/third_pages/tab_b.png"></image>
</view>
<view class="tr-item" @click="clickTap">
<view class="ti-title">敬请期待</view>
<view class="ti-text">敬请期待</view>
<image class="tr-icon" src="/static/images/third_pages/tab_b.png"></image>
</view>
</view>
</view>
</view>
<bottom-logo is-fixed></bottom-logo>
</view>
</template>
<script>
import bottomLogo from "@/subpackage/menu/components/bottom_logo.vue";
import { showNone } from "@/utils/util";
export default {
components:{ bottomLogo },
methods: {
clickTap(){
showNone('板块更新中');
}
}
}
</script>
<style lang="scss">
.third-page{
.tp-container{
padding: 28upx 24upx 0upx;
.tc-post{
display: block;
height: 400upx;
width: 100%;
}
.tc-bot{
margin-top: 24upx;
display: flex;
align-items: stretch;
.tb-left{
position: relative;
flex-shrink: 0;
margin-right: 22upx;
padding: 22upx 28upx;
width: 340upx;
height: 420upx;
.tl-bg{
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: -1;
}
.tl-tit{
@include flcw(43upx, 60upx, #fff, 500);
@include tHide;
}
.tl-txt{
@include flcw(30upx, 42upx, #fff, 500);
@include tHide;
}
}
.tb-right{
flex-grow: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.tr-item{
position: relative;
height: 124upx;
background-image: linear-gradient(270deg, #FFFFFF 0%, #C8C8CA 100%);
padding: 16upx;
&.active{
background-image: linear-gradient(270deg, #F2D286 0%, #E0A46F 100%);
}
.ti-title{
@include flcw(32upx, 44upx, #fff, 500);
@include tHide;
}
.ti-text{
@include flcw(22upx, 32upx, #fff, 500);
@include tHide;
}
.tr-icon{
position: absolute;
right: 6upx;
bottom: 0upx;
width: 136upx;
height: 104upx;
}
}
}
}
}
}
</style>

4
src/pages/merchant_login/merchant_login.vue

@ -8,11 +8,11 @@
</view>
</template>
<script>
import util from '../../utils/util'
import util, { jsonStr } from '../../utils/util'
export default {
methods: {
toWebView(){
util.routeTo(`/pages/web_view/web_view?src=${encodeURIComponent("https://www.ouxuanzhineng.cn")}`,'rL');
util.routeTo(`/pages/web_view/web_view?src=${jsonStr("https://www.ouxuanzhineng.cn/")}`,'rL');
}
}
}

2
src/pages/order_search/order_search.vue

@ -3,7 +3,7 @@
<view class="os-bar">
<view>
<image mode="aspectFit" src="/static/images/icon/search.png"></image>
<input placeholder="输入订单号" v-model="searchTxt" confirm-type="search" @confirm="iptConfirm" />
<input placeholder="输入订单号" v-model="searchTxt" confirm-type="search" @confirm="iptConfirm" />
<image v-if="searchTxt!=''" mode="aspectFit" src="/static/images/icon/round_close.png" @click="clearSearch"></image>
</view>
</view>

3
src/pages/web_view/web_view.vue

@ -2,6 +2,7 @@
<web-view :src="address"></web-view>
</template>
<script>
import { jsonPar } from "@/utils/util.js";
export default {
data(){
return {
@ -10,7 +11,7 @@ export default {
},
onLoad(options){
console.log('web_view options', options);
this.address = decodeURIComponent(options?.src || 'https://www.ouxuanzhineng.cn');
this.address = jsonPar(decodeURIComponent(options?.src ?? ''));
}
}
</script>

175
src/pages/write_off/douyin/poi_list.vue

@ -1,175 +0,0 @@
<template>
<view class="dy-poi-ls">
<view class="dpl-header">
<stadium-picker
:stadium-list="stadiumList"
@change:stadium="stadiumChange"
></stadium-picker>
<!-- <period-picker></period-picker> -->
<view class="dh-number">核销数量{{ totalNum || 0 }}</view>
</view>
<view class="dpl-list">
<view class="dl-item" v-for="(e, i) in writeOffList" :key="i">
<list-item
:order-no="e.order_no"
:user-phone="e.user_phone"
:verify-code="e.verify_code"
:verify-method="e.verify_method"
:verify-time="e.verify_time"
></list-item>
</view>
</view>
<view class="r-bottom-btn"><view @click="toOperate">核销团购券</view></view>
</view>
</template>
<script>
import periodPicker from "../all_components/period_picker.vue";
import stadiumPicker from "../all_components/stadium_picker.vue";
import listItem from "../all_components/list_item.vue";
import { API } from '../../../js/api';
import { servers } from '../../../js/server';
import util from '../../../utils/util';
import { mapState } from 'vuex';
import { WRITE_OFF_STORE_NAME } from '../../../js/once_name';
export default {
computed: {
...mapState([ 'brandInfo' ]),
},
components: {
periodPicker,
stadiumPicker,
listItem
},
data() {
return {
totalNum: 0,
writeOffList: [],
stadiumList: [],
curStadium: {},
page: 1,
}
},
async onLoad() {
let _brand_id = this.brandInfo?.brand?.id || 63;
let _list = await this.getStoreList({ brand_id: _brand_id });
this.stadiumList = _list || [];
this.getList({ brand_id: _brand_id });
},
onReachBottom(){
let { page, curStadium } = this;
this.getList({
brand_id: curStadium?.brand_id,
stadium_id: curStadium?.id,
page: page + 1,
})
},
methods: {
toOperate(){
let { stadiumList, curStadium } = this;
util.$_emit(WRITE_OFF_STORE_NAME, {
stadiumList,
curStadium,
})
util.routeTo(`/pages/write_off/operate/operate?type=dypoi`, 'rT');
},
stadiumChange(stadium){
this.curStadium = stadium;
this.page = 1;
this.totalNum = 0;
this.writeOffList = [];
this.getList({
brand_id: stadium?.brand_id,
stadium_id: stadium?.id,
})
},
getList({ brand_id, stadium_id = '', date = '', page = 1, page_size = 20, order_status = 'used' }){
util.showLoad();
servers.get({
url: API.writeOff.dyPoiOrderList,
data: { brand_id, stadium_id, date, page, page_size, order_status },
failMsg: '加载失败!',
})
.then(res=>{
util.hideLoad();
this.totalNum = res.total || 0;
let _list = (res.list || []).map(e=>this.formating(e));
if(page == 1)return this.writeOffList = _list;
if(!_list.length)return util.showNone('没有更多!');
this.page = page;
this.writeOffList = [...this.writeOffList, ..._list];
})
},
formating(data){
let _code = data?.order_codes?.find(e=>e?.code_status == 'used');
return {
order_no: data?.order_no || '',
user_phone: data?.mobile || '',
verify_code: _code?.code || '',
verify_method: _code?.verification_method || '',
verify_time: _code?.verification_time || '',
}
},
getStoreList({
page=1,
page_size=9999,
brand_id='',
}){
return servers.get({
url: API.stadiumList,
data: {
page,
page_size,
brand_id,
},
failMsg: '获取列表失败!'
})
.then(res=>res.list || [])
},
}
}
</script>
<style lang="scss">
.dy-poi-ls{
@include isPd(150upx);
}
.dpl-header{
position: sticky;
padding-bottom: 24upx;
background: #fff;
.dh-number{
padding: 0 24upx;
text-align: right;
@include flcw(32upx, 44upx);
@include tHide;
}
}
.dpl-list{
padding: 24upx;
.dl-item+ .dl-item{
margin-top: 24upx;
}
}
.r-bottom-btn{
position: fixed;
left: 0;
bottom: 0;
width: 100%;
padding: 10upx 24upx;
padding-bottom: calc( 10upx + constant(safe-area-inset-bottom)); /* 兼容 iOS < 11.2 */
padding-bottom: calc( 10upx + env(safe-area-inset-bottom)); /* 兼容 iOS >= 11.2 */
background-color: #f2f2f7;
>view{
height: 112upx;
line-height: 112upx;
text-align: center;
font-size: 32upx;
border-radius: 10upx;
color: #fff;
background-color: $mColor;
}
}
</style>

237
src/pages/write_off/mall/list/list.vue

@ -1,237 +0,0 @@
<template>
<view class="mall-order-ls">
<view class="mol-date">
<view class="md-txt">核销日期</view>
<view class="md-picker">
<picker mode="date" @change="stChange">
<view>
<input :value="startTime" disabled placeholder="请选择时间" />
<image mode="aspectFit" src="/static/images/icon/arrow_c33.png"></image>
</view>
</picker>
</view>
<view class="md-txt"></view>
<view class="md-picker">
<picker mode="date" @change="edChange">
<view>
<input :value="endTime" disabled placeholder="请选择时间" />
<image mode="aspectFit" src="/static/images/icon/arrow_c33.png"></image>
</view>
</picker>
</view>
</view>
<view class="mol-list">
<view class="ml-item" v-for="(e, i) in writeOffList" :key="i">
<view class="mi-order-no">
<view class="mon-num">订单编号{{ e.product_order_no || '-' }}</view>
<view class="mon-btn" @click="copyBtn(e.product_order_no || '-')">复制</view>
</view>
<view class="mi-info">
<view class="mi-line">取货码{{ e.product_order_no || '-' }}</view>
<view class="mi-line">取货人{{ e.product_order_self_pickup_info.name || '-' }} {{ e.product_order_self_pickup_info.phone || '-' }}</view>
<view class="mi-line">商品{{ e.product_order_goods.join(';') }}</view>
<view class="mi-line">核验人{{ e.optuname }}</view>
<view class="mi-line">取货时间{{ e.created_at || '-' }}</view>
</view>
</view>
</view>
<view class="mol-fixed">
<view @click="toOperate">核销订单</view>
</view>
</view>
</template>
<script>
import { API } from '../../../../js/api';
import { servers } from '../../../../js/server';
import util from '../../../../utils/util';
import { mapState } from 'vuex';
import { WRITE_OFF_STORE_NAME } from '../../../../js/once_name';
export default {
computed: {
...mapState([ 'brandInfo' ]),
},
data(){
return {
writeOffList: [],
page: 1,
startTime: '',
endTime: '',
}
},
onReachBottom(){
let { brandInfo, startTime, endTime, page } = this;
this.getList({
brand_id: brandInfo.brand.id,
stime: startTime || '',
etime: endTime || '',
page: ++page
})
},
onLoad(){
// 1
let _startDate = util.formatDate({
date: new Date().getTime() - 30*24*60*60*1000
}).substr(0, 10);
let _endDate = util.formatDate({ }).substr(0, 10);
this.startTime = _startDate;
this.endTime = _endDate;
// 1
this.$nextTick(_=> this.refreshPage());
},
methods: {
refreshPage(){
let { brandInfo, startTime, endTime } = this;
this.page = 1;
this.writeOffList = [];
this.$nextTick(_=>{
this.getList({
brand_id: brandInfo.brand.id,
stime: startTime || '',
etime: endTime || '',
})
})
},
copyBtn(data){
uni.setClipboardData({ data })
},
stChange(e){
console.warn(e)
this.startTime = e.detail.value;
this.$nextTick(_=>this.refreshPage());
},
edChange(e){
this.endTime = e.detail.value;
this.$nextTick(_=>this.refreshPage());
},
toOperate(){
util.$_emit(WRITE_OFF_STORE_NAME, null);
util.routeTo(`/pages/write_off/operate/operate?type=mall`, 'nT');
},
getList({ brand_id, page = 1, page_size = 15, stime = '', etime = '' }){
util.showLoad();
servers.get({
url: API.writeOff.shop2WriteoffList,
data: { brand_id, page, page_size, stime, etime },
failMsg: '加载失败!',
})
.then(res=>{
util.hideLoad();
this.totalNum = res.total || 0;
let _list = res.list || [];
if(page == 1)return this.writeOffList = _list;
if(!_list.length)return util.showNone('没有更多!');
this.page = page;
this.writeOffList = [...this.writeOffList, ..._list];
})
},
}
}
</script>
<style lang="scss">
@import '~style/public.scss';
.mall-order-ls{
padding-bottom: 122upx;
padding-bottom: calc( 122upx + constant(safe-area-inset-bottom)); /* 兼容 iOS < 11.2 */
padding-bottom: calc( 122upx + env(safe-area-inset-bottom)); /* 兼容 iOS >= 11.2 */
}
.mol-date{
padding: 0 28upx;
height: 148upx;
background-color: #fff;
@include centerFlex(flex-start);
.md-txt{
font-size: 32upx;
line-height: 44upx;
color: #1a1a1a;
}
.md-picker{
margin: 0 20upx;
width: 226upx;
border: 2upx solid #D8D8D8;
border-radius: 10upx;
overflow: hidden;
view{
padding: 0 10upx;
height: 88upx;
background-color: #f2f2f7;
@include centerFlex(space-between);
>input{
flex-grow: 1;
height: 100%;
font-size: 28upx;
color: #1a1a1a;
}
>image{
margin-left: 10upx;
flex-shrink: 0;
width: 22upx;
height: 22upx;
}
}
}
}
.mol-list{
padding: 24upx;
.ml-item{
margin-bottom: 24upx;
padding: 0 24upx;
background-color: #fff;
.mi-order-no{
padding: 30upx 4upx;
border-bottom: 2upx solid #f2f2f7;
@include centerFlex(space-between);
.mon-num{
font-size: 28upx;
font-weight: 500;
line-height: 40upx;
color: #1a1a1a;
}
.mon-btn{
flex-shrink: 0;
margin-left: 20upx;
font-size: 28upx;
line-height: 40upx;
color: $themeColor;
}
}
.mi-info{
padding: 30upx 4upx;
.mi-line{
font-size: 28upx;
line-height: 52upx;
color: #9C9C9F;
@include textHide(1);
}
}
}
}
.mol-fixed{
position: fixed;
left: 0;
bottom: 0;
width: 100%;
background-color: #fff;
padding: 10upx 40upx;
padding-bottom: calc( 10upx + constant(safe-area-inset-bottom)); /* 兼容 iOS < 11.2 */
padding-bottom: calc( 10upx + env(safe-area-inset-bottom)); /* 兼容 iOS >= 11.2 */
>view{
height: 112upx;
line-height: 112upx;
text-align: center;
font-size: 32upx;
color: #fff;
background-color: $themeColor;
border-radius: 10upx;
}
}
</style>

131
src/pages/write_off/menu/menu.vue

@ -1,131 +0,0 @@
<template>
<view class="write-off-menu">
<view class="wom-section">
<view class="ws-tit">场地订单核销</view>
<view class="ws-ls">
<view class="wl-item" @click="toOperate('site')">
<image mode="aspectFit" src="/static/images/write_off/site.png"></image>
<view>场地订单核销</view>
</view>
<view class="wl-item" @click="toSiteList">
<image mode="aspectFit" src="/static/images/write_off/order.png"></image>
<view>场地订单核销记录</view>
</view>
</view>
</view>
<view class="wom-section">
<view class="ws-tit">商城订单核销</view>
<view class="ws-ls">
<view class="wl-item" @click="toOperate('mall')">
<image mode="aspectFit" src="/static/images/write_off/mall.png"></image>
<view>商城订单核销</view>
</view>
<view class="wl-item" @click="toMallLs">
<image mode="aspectFit" src="/static/images/write_off/order.png"></image>
<view>商城订单核销记录</view>
</view>
</view>
</view>
<view class="wom-section">
<view class="ws-tit">抖音团购核销</view>
<view class="ws-ls">
<view class="wl-item" @click="toOperate('dypoi')">
<image mode="aspectFit" src="/static/images/write_off/site.png"></image>
<view>抖音团购核销</view>
</view>
<view class="wl-item" @click="toDypoiOrderLs">
<image mode="aspectFit" src="/static/images/write_off/order.png"></image>
<view>抖音团购核销记录</view>
</view>
</view>
</view>
<view class="wom-section">
<view class="ws-tit">现场散客人数</view>
<view class="ws-ls">
<view class="wl-item" @click="toPeopleNum">
<image mode="aspectFit" src="/static/images/write_off/people.png"></image>
<view>现场散客人数查询</view>
</view>
</view>
</view>
</view>
</template>
<script>
import util from '../../../utils/util';
import { WRITE_OFF_STORE_NAME } from '../../../js/once_name';
export default {
data(){
return {
brand_id: ''
}
},
onLoad(options){
this.brand_id = options?.brand_id || ''
},
methods: {
toPeopleNum(){
let _qryStr = `brand_id=${this.brand_id}`
util.routeTo(`/subpackage/verification/pages/site_people/index?${_qryStr}`, 'nT');
},
toOperate(type){
util.$_emit(WRITE_OFF_STORE_NAME, null);
util.routeTo(`/pages/write_off/operate/operate?type=${type}`, 'nT');
},
toSiteList(){
util.routeTo(`/pages/write_off/search_result/search_result`, 'nT');
},
toMallLs(){
util.routeTo(`/pages/write_off/mall/list/list`, 'nT');
},
toDypoiOrderLs(){
util.routeTo(`/pages/write_off/douyin/poi_list`, 'nT');
}
}
}
</script>
<style lang="scss">
@import '~style/public.scss';
.write-off-menu{
padding: 60upx 68upx 0upx;
.wom-section{
margin-bottom: 30upx;
.ws-tit{
margin-bottom: 20upx;
font-size: 32upx;
font-weight: 500;
line-height: 44upx;
color: #1a1a1a;
}
.ws-ls{
@include centerFlex(space-between);
flex-wrap: wrap;
.wl-item{
padding-top: 58upx;
margin-bottom: 32upx;
width: 288upx;
height: 220upx;
border-radius: 10upx;
background-color: #fff;
>image{
margin: 0 auto 22upx;
display: block;
width: 60upx;
height: 60upx;
}
>view{
text-align: center;
line-height: 40upx;
font-size: 28upx;
color: #9c9c9f;
@include textHide(1);
}
}
}
}
}
</style>

70
src/pages/write_off/null/null.vue

@ -1,70 +0,0 @@
<template>
<view class="null-container">
<image mode="aspectFit" :src="imgPath"></image>
<view class="c-tip" v-if="operateType == 'decrypt_text'">很抱歉获取不到二维码订单信息</view>
<view class="c-tip" v-if="operateType == 'verify_code'">很抱歉获取不到验证码订单信息</view>
<view class="c-btn" @click="toBlack">返回</view>
</view>
</template>
<script>
import util from '../../../utils/util';
export default {
computed: {
imgPath(){
let { operateType } = this;
if(operateType == 'verify_code')return '/static/images/code_null.png';
if(operateType == 'decrypt_text')return '/static/images/scan_null.png';
}
},
onLoad(options){
let { type } = options;
this.operateType = options.type
},
data(){
return {
isScan: false,
operateType: '', // verify_code()/decrypt_text()
}
},
methods: {
toBlack(){
util.routeTo();
}
}
}
</script>
<style lang="scss">
@import "~style/public.scss";
page{
background-color: #fff;
}
.null-container{
padding-top: 90upx;
>image{
display: block;
margin: 0 auto 86upx;
width: 420upx;
height: 420upx;
}
.c-tip{
margin-bottom: 260upx;
line-height: 40upx;
text-align: center;
font-size: 28upx;
color: #9c9c9f;
}
.c-btn{
margin: 0 auto;
width: 280upx;
text-align: center;
height: 92upx;
line-height: 88upx;
font-size: 32upx;
border: 2upx solid $themeColor;
color: $themeColor;
border-radius: 46upx;
}
}
</style>

496
src/pages/write_off/number_of_people/number_of_people.vue

@ -1,496 +0,0 @@
<template>
<view class="number-of-people">
<view class="nop-store-name">
<picker :range="stadiumList" range-key="name" @change="stadiumChange">
<view class="nsn-frame">
<input placeholder="请选择店铺" :value="curStadium.name" disabled />
<image mode="aspectFit" src="/static/images/icon/arrow_c33.png"></image>
</view>
</picker>
</view>
<view class="nop-main">
<view class="nm-date">日期{{ dateStr || '-' }}</view>
<view class="nm-tit">现场散客人数</view>
<view class="nm-num">
<image mode="aspectFit" src="/static/images/countdown_bg.png"></image>
<view class="nn-txt-num">{{peopleInfo.present_person_number || 0}}</view>
</view>
<view class="nm-txt" @click="checkNotLeave">查看未离场订单</view>
<view class="nm-btn" @click="isChangeNum = true">修改人数</view>
<view class="nm-line">
<view class="nl-txt">凌晨自动清零</view>
<view class="nl-switch" @click="switchChange">
<switch color="#009777" disabled style="transform:scale(0.8)" :checked="peopleInfo. present_person_number_clear"></switch>
</view>
</view>
<view class="nm-tip">
<text>* 不开启凌晨自动清零则现场灯光按现场散客人数去判断是否开启或关闭修改人数会直接影响现场灯光开关\n\r* 开启凌晨自动清零则现场灯光按散客订单未离场数量去判断是否开启或关闭修改人数不会影响现场灯光开关但如有散客订单一直未扫码离场可能会无法关灯需要将未离场的散客订单设置为已离场后才可关闭灯光</text>
</view>
</view>
<view class="ox-dark-mask" v-if="isChangeNum">
<view class="nop-modifies-modal">
<image class="nmm-close" @click="isChangeNum = false" src="/static/images/icon/x_close.png"></image>
<view class="nmm-tit">修改现场散客人数</view>
<view class="nmm-info">
<view class="ni-num">当前现场散客人数为{{peopleInfo.present_person_number || 0}}</view>
<view class="ni-ipt">
<input placeholder="请输入散客人数" v-model="changeNum" type="number" />
</view>
<view class="ni-tip">修改现场人数可能会影响现场灯光开关请谨慎操作</view>
</view>
<view class="nmm-btns">
<view @click="isChangeNum = false">取消</view>
<view @click="confirmChange">确认</view>
</view>
</view>
</view>
<view class="ox-dark-mask" v-if="isNotLeave">
<view class="nop-modifies-modal">
<image class="nmm-close" @click="isNotLeave = false" src="/static/images/icon/x_close.png"></image>
<view class="nmm-tit nmm-btm">未离场订单</view>
<view class="nmm-line" v-if="orderNum.person_number>0">
<view>次卡未离场{{orderNum.person_number || 0}}</view>
<view @click="checkBtn(0)">查看</view>
</view>
<view class="nmm-line" v-if="orderNum.person_timing>0">
<view>计时未离场{{orderNum.person_timing || 0}}</view>
<view @click="checkBtn(1)">查看</view>
</view>
<view class="nmm-line">
<view>年月卡未离场{{orderNum.monthly_card||0}}</view>
<view @click="checkBtn(0)">查看</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { API } from '../../../js/api'
import { servers } from '../../../js/server'
import { mapState } from 'vuex';
import util from '../../../utils/util';
let timer = null;
export default {
computed: {
...mapState([ 'brandInfo' ]),
},
data(){
return {
isChangeNum: false,
stadiumList: [],
curStadium: {
name: ''
},
peopleInfo: {},
dateStr: '-',
changeNum: '',
isNotLeave: false,
orderNum: {}, //
}
},
onLoad(options){
this.initStore();
this.dateStr = util.formatDate({ partition: 'zh' }) || '-';
},
onUnload(){
this.clearTime();
},
methods: {
confirmChange: util.debounce(function(){
let { changeNum } = this;
if(isNaN(changeNum))return util.showNone('请输入正确人数!');
this.isChangeNum = false;
this.setStadiumPresentNumber(changeNum);
}, 200, true),
setStadiumPresentNumber(num){
let { curStadium } = this;
util.showLoad();
servers.get({
url: API.writeOff.setStadiumPresentNumber,
data: {
brand_id: curStadium.brand_id,
stadium_id: curStadium.id,
number: +num,
},
isDefaultGet: false,
})
.then(res=>{
util.hideLoad();
if(res.data.code == 0){
util.showNone(res.data.message || '操作成功!');
setTimeout(_=>{
this.getUserNum({
stadium_id: curStadium.id,
brand_id: curStadium.brand_id
})
this.initInertval();
}, 1200)
}else{
util.showNone(res.data.message || '操作失败!');
}
})
.catch(util.hideLoad)
},
initInertval(){
this.clearTime();
let { curStadium } = this;
if(!curStadium.id || !curStadium.brand_id)return;
timer = setInterval(_=>{
if(!curStadium.id || !curStadium.brand_id)return this.clearTime();
this.getUserNum({
stadium_id: curStadium.id,
brand_id: curStadium.brand_id
})
}, 3000);
},
clearTime(){
clearInterval(timer);
timer = null;
},
stadiumChange(e){
let { stadiumList } = this;
let _curStadium = stadiumList[e.detail.value] || {};
if(!_curStadium.id || !_curStadium.brand_id)return;
this.curStadium = _curStadium;
this.getUserNum({
stadium_id: _curStadium.id,
brand_id: _curStadium.brand_id
})
this.initInertval();
},
async initStore(){
let { brandInfo } = this;
try{
util.showLoad();
let _storeList = await this.getStoreList({ brand_id: brandInfo.brand.id || '' });
if(!_storeList || !_storeList.length)return util.showNone('没有店铺信息');
this.stadiumList = _storeList || [];
let _curStadium = _storeList[0] || {};
this.curStadium = _curStadium;
this.initInertval();
this.getUserNum({
stadium_id: _curStadium.id,
brand_id: _curStadium.brand_id
})
util.hideLoad();
}catch(err){
util.hideLoad();
util.showNone('初始化店铺数据失败!');
console.warn('加载数据失败!', err);
}
},
//
getStoreList({
page=1,
page_size=9999,
brand_id='',
}){
return servers.get({
url: API.stadiumList,
data: {
page,
page_size,
brand_id,
},
failMsg: '获取列表失败!'
})
.then(res=>{
let _list = res.list || [];
return _list
})
},
getUserNum({
stadium_id,
brand_id,
}){
servers.get({
url: API.writeOff.timingNumber,
data: { stadium_id, brand_id },
failMsg: '加载现场人数失败!'
})
.then(res=>{
this.peopleInfo = res
})
},
// -
checkNotLeave(){
let { curStadium } = this;
util.showLoad();
servers.get({
url: API.writeOff.notLeavingNums,
data: {
brand_id: curStadium.brand_id,
stadium_id: curStadium.id,
},
// isDefaultGet: false,
failMsg: '请求失败!'
})
.then(res=>{
util.hideLoad();
this.orderNum = res
this.$nextTick(_=>{
this.isNotLeave = true
})
})
},
checkBtn(type){
if(type == 0)return util.routeTo(`/pages/write_off/search_result/search_result`, 'nT');
if(type == 1)return util.routeTo(`/pages/order_list/order_list?order_type=1`, 'nT');
},
switchChange(){
let { peopleInfo, curStadium } = this
this.$nextTick(_=>{
util.showModal({
title: '提示',
content: peopleInfo.present_person_number_clear==false?'是否确认开启凌晨自动清零?':peopleInfo.present_person_number_clear==true?'是否确认关闭凌晨自动清零?':'',
showCancel: true,
success: modalRes=>{
if(modalRes.confirm){
util.showLoad();
servers.get({
url: API.writeOff.timingOpen, ///
data: {
brand_id: curStadium.brand_id,
stadium_id: curStadium.id,
status: peopleInfo.present_person_number_clear==false?1:peopleInfo.present_person_number_clear==true?0:'',
},
failMsg: '请求失败!'
})
.then(res=>{
util.hideLoad();
util.showNone('操作成功!');
})
}
}
})
})
},
}
}
</script>
<style lang="scss">
@import '~style/public.scss';
page{
background-color: #fff;
}
.number-of-people{
.nop-store-name{
height: 144upx;
@include centerFlex(center);
.nsn-frame{
padding: 0 20upx;
width: 702upx;
height: 92upx;
border-radius: 10upx;
background-color: #F2F2F7;
@include centerFlex(space-between);
>input{
flex-grow: 1;
line-height: 40upx;
font-size: 28upx;
color: #1A1A1A;
}
>image{
flex-shrink: 0;
margin-left: 20upx;
width: 28upx;
height: 28upx;
}
}
}
.nop-main{
padding: 8upx 0upx 0;
.nm-date{
margin: 0 24rpx 86rpx;
line-height: 44upx;
font-size: 32upx;
color: #1a1a1a;
}
.nm-tit{
margin: 0 30rpx 16rpx;
text-align: center;
font-size: 32upx;
font-weight: 500;
}
.nm-num{
position: relative;
margin: 0 auto;
display: block;
width: 400upx;
height: 400upx;
.nn-txt-num{
line-height: 400upx;
text-align: center;
font-size: 96upx;
font-weight: 500;
color: $themeColor;
}
>image {
position: absolute;
left: 50%;
top: 50%;
z-index: -1;
margin-left: -250upx;
margin-top: -250upx;
display: block;
width: 500upx;
height: 500upx;
animation: Rotate 6s linear infinite
}
@keyframes Rotate{
0% {transform: rotate(360deg);}
50% {transform: rotate(180deg);}
100% {transform: rotate(0deg);}
}
}
.nm-txt{
margin: 10rpx auto 42rpx;
color: #9C9C9F;
font-size: 28rpx;
text-align: center;
line-height: 40rpx;
text-decoration: underline;
}
.nm-btn{
margin: 0 auto;
width: 618upx;
line-height: 112upx;
height: 112upx;
text-align: center;
border-radius: 10upx;
font-size: 38upx;
color: #fff;
background-color: $themeColor;
}
.nm-line{
margin: 46rpx 0 0;
padding: 34rpx 28rpx 20rpx 34rpx;
border-top: 2rpx solid #F2F2F7;
@include centerFlex(space-between);
.nl-txt{
color: #1A1A1A;
font-size: 32rpx;
line-height: 44rpx;
}
.nl-switch{
flex-shrink: 0;
flex-grow: 0;
}
}
.nm-tip{
margin: 0 32rpx 30rpx 34rpx;
font-size: 28upx;
line-height: 52upx;
color: #9C9C9F;
}
}
.nop-modifies-modal{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding-top: 78upx;
width: 620upx;
height: 706upx;
background-color: #fff;
border-radius: 10upx;
.nmm-close{
position: absolute;
right: 30upx;
top: 30upx;
width: 34upx;
height: 34upx;
}
.nmm-tit{
line-height: 44upx;
text-align: center;
font-weight: 500;
font-size: 32upx;
color: #1A1A1A;
}
.nmm-info{
padding: 54upx 80upx 80upx;
.ni-num{
margin-bottom: 30upx;
font-size: 28upx;
line-height: 48upx;
color: #1A1A1A;
@include textHide(1);
}
.ni-ipt{
margin-bottom: 26upx;
padding: 0 20upx;
height: 88upx;
border-radius: 10upx;
border: 2upx solid #D8D8D8;
>input{
flex-grow: 1;
height: 100%;
font-size: 28upx;
color: #1A1A1A;
}
}
.ni-tip{
font-size: 24upx;
line-height: 34upx;
color: #EA5061;
}
}
.nmm-btns{
@include centerFlex(center);
>view{
margin: 0 10upx;
width: 240upx;
height: 88upx;
line-height: 84upx;
text-align: center;
font-size: 32upx;
border-radius: 10upx;
border: 2upx solid $themeColor;
color: $themeColor;
&+view{
color: #fff;
background-color: $themeColor;
}
}
}
.nmm-btm{
margin-bottom: 8rpx;
}
.nmm-line{
margin: 78rpx 70rpx 0 74rpx;
@include centerFlex(space-between);
>view{
&:first-child{
color: #1A1A1A;
font-size: 28rpx;
line-height: 48rpx;
}
&:nth-child(2){
width: 156rpx;
height: 68rpx;
border: 2rpx solid #009874;
border-radius: 10rpx;
color: #009874;
font-size: 32rpx;
line-height: 64rpx;
text-align: center;
}
}
}
}
}
</style>

364
src/pages/write_off/operate/operate.vue

@ -1,364 +0,0 @@
<template>
<view class="operate-container">
<view class="store-bar">
<text>当前门店</text>
<picker mode="selector" :range="stadiumList" range-key="name" @change="stadiumChange">
<view>
<input disabled v-model="curStadium.name" />
<image mode="aspectFit" src="/static/images/icon/arrow_c33.png"></image>
</view>
</picker>
</view>
<view class="c-scan-btn" @click="scanCodeBtn">
<image mode="aspectFit" src="/static/images/icon/scan_code_btn.png"></image>
</view>
<view class="c-verification-code">
<view><input placeholder="输入订单验证码" v-model="iptCode" /></view>
<view @click="confirmBtn">确认核销</view>
</view>
</view>
</template>
<script>
import util from '../../../utils/util';
import { API } from '../../../js/api';
import { servers } from '../../../js/server';
import { WRITE_OFF_STORE_NAME, WRITE_OFF_ORDER_INFO, WRITE_OFF_MALL_ORDER_INFO } from '../../../js/once_name';
import { mapState } from 'vuex';
export default {
data(){
return {
iptCode: '',
stadiumList: [],
curStadium: {},
writeOffType: '', // site()/ mall()/ dypoi()
}
},
computed: {
...mapState([ 'brandInfo' ]),
},
onLoad(options){
this.writeOffType = options.type || '';
util.$_once(WRITE_OFF_STORE_NAME, data => { //
if(!data)return this.initStore();
this.curStadium = data.curStadium;
this.stadiumList = data.stadiumList;
})
},
methods: {
async initStore(){
let { brandInfo } = this;
try{
util.showLoad();
let _storeList = await this.getStoreList({ brand_id: brandInfo.brand.id || '' });
if(!_storeList || !_storeList.length)return util.showNone('没有店铺信息');
this.stadiumList = _storeList || [];
if(_storeList.length) this.curStadium = _storeList[0];
util.hideLoad();
}catch(err){
util.hideLoad();
util.showNone('初始化店铺数据失败!');
}
},
//
getStoreList({
page=1,
page_size=99999,
brand_id='',
}){
return servers.get({
url: API.stadiumList,
data: {
page,
page_size,
brand_id,
},
failMsg: '获取列表失败!'
})
.then(res=>{
let _list = res.list || [];
return _list
})
},
scanCodeBtn: util.debounce(function(){
uni.scanCode({
onlyFromCamera: true,
scanType: 'qrCode',
success: res=> {
if(util.changeLowerCase(res.scanType) !== 'qr_code')return util.showNone('不支持此类型!');
this.analysisOrder({ decrypt_text: res.result });
},
fail: function(err) {
util.showNone('扫码失败!');
console.warn('扫码失败--->', err);
}
})
}, 300, true),
stadiumChange(e){
let { stadiumList } = this;
this.curStadium = stadiumList[e.detail.value];
},
confirmBtn: util.debounce(function(){
let { iptCode } = this;
if(!iptCode)return util.showNone('请输入核销码!');
this.analysisOrder({ verify_code: this.iptCode });
}, 300, true),
//
analysisOrder({ verify_code = '', decrypt_text = '' }){
let { curStadium, writeOffType } = this;
if(!verify_code&&!decrypt_text)return;
let _query = {
brand_id: curStadium.brand_id,
stadium_id: curStadium.id,
}
let _vType = '';
if(!!verify_code){
_vType = 'verify_code';
writeOffType == 'site'&&(_query['verify_code'] = verify_code);
writeOffType == 'mall'&&(_query['vcode'] = verify_code);
}
if(!!decrypt_text){
_vType = 'decrypt_text';
writeOffType == 'site'&&(_query['decrypt_text'] = decrypt_text);
writeOffType == 'mall'&&(_query['vcode'] = decrypt_text);
}
if(writeOffType == 'site')return this.siteGet({ query: _query, vType: _vType, });
if(writeOffType == 'mall')return this.mallGet({ query: _query, vType: _vType, });
if(writeOffType == 'dypoi'){
let _code = verify_code;
if(decrypt_text){
try{
_code = JSON.parse(decrypt_text)?.code;
}catch(err){console.warn(err)}
}
return this.dypoiGet({
..._query,
code: _code,
});
}
},
//
dypoiGet(query){
util.showLoad();
servers.get({
url: API.writeOff.dyPoiEnterVerifyOrder,
data: query,
isDefaultGet: false
})
.then(res=>{
util.hideLoad();
let { code, data, message } = res?.data || {};
if(code!==0)return util.showModal({
title: '提示',
content: message || '查询失败!',
confirmText: '好的'
})
if(data?.order?.order_no)return util.showModal({
title: data?.order?.product_cache?.name || '提示',
content: '核销数量:1',
showCancel: true,
confirmText: '确认核销',
cancelText: '取消',
success: res=>{
if(res.confirm){
this.dypoiConfirm({ ...query });
}
}
})
console.log('订单查询---->', res);
})
.catch(util.hideLoad)
},
dypoiConfirm({
stadium_id = '',
brand_id = '',
code = '',
}){
util.showLoad();
servers.post({
url: API.writeOff.dyPoiAssistantVerify,
data: {
stadium_id,
brand_id,
code,
},
isDefaultGet: false
})
.then(res=>{
util.hideLoad();
let { code, data, message } = res?.data || {};
if(code!==0)return util.showModal({
title: '提示',
content: message || '核销失败!',
confirmText: '好的'
})
util.showModal({
title: '提示',
content: message || '核销成功!',
confirmText: '关闭'
})
})
.catch(util.hideLoad)
},
//
mallGet({ query = {}, vType = '', }){
util.showLoad();
servers.get({
url: API.writeOff.shop2WriteoffGet,
data: query,
isDefaultGet: false
})
.then(res=>{
util.hideLoad();
if(res.data.code == 0){
let _data = res.data.data || {};
if(_data.has_order){
util.$_emit(WRITE_OFF_MALL_ORDER_INFO, {..._data.order || {}});
util.routeTo(`/pages/write_off/mall/confirm/confirm?type=${vType}`, 'nT');
}else{
util.routeTo(`/pages/write_off/null/null?type=${vType}`, 'nT');
}
}else{
util.showNone(res.data.message || '操作失败!')
// util.routeTo(`/pages/write_off/null/null?type=${vType}`, 'nT');
}
})
.catch(util.hideLoad)
},
// / /
// @vType verify_code()/ decrypt_text()
siteGet({ query = {}, vType = '', }){
util.showLoad();
servers.get({
url: API.writeOff.enterVerifyOrder,
data: query,
isDefaultGet: false
})
.then(res=>{
util.hideLoad();
if(res.data.code == 0){
let _data = res.data.data || {}
//
if(_data.extension&&_data.extension.verify_order_type === 'monthly_card'){
util.$_emit(WRITE_OFF_ORDER_INFO, { data: _data, query });
util.routeTo(`/pages/write_off/ym_confirm/ym_confirm?type=${vType}`, 'nT');
return
}
//
if(_data.extension&&_data.extension.verify_order_type === 'match_order'){
util.$_emit(WRITE_OFF_ORDER_INFO, {..._data});
util.routeTo(`/pages/write_off/events_order/events_order?type=${vType}`, 'nT');
return
}
util.$_emit(WRITE_OFF_ORDER_INFO, {..._data});
util.routeTo(`/pages/write_off/confirm_order/confirm_order?type=${vType}`, 'nT');
}else{
util.routeTo(`/pages/write_off/null/null?type=${vType}`, 'nT');
}
console.log('订单查询---->', res);
})
.catch(util.hideLoad)
}
}
}
</script>
<style lang="scss">
@import '~style/public.scss';
page{
background-color: #f2f2f7;
}
.operate-container{
.store-bar{
margin-bottom: 24upx;
padding: 0 24upx;
height: 144upx;
background-color: #fff;
@include centerFlex(space-between);
>text{
margin-right: 20upx;
flex-shrink: 0;
font-size: 28upx;
color: #9C9C9F;
}
>picker{
flex-grow: 1;
}
view{
padding: 0 20upx;
height: 92upx;
border-radius: 10upx;
background: #f2f2f2;
@include centerFlex(space-between);
>input{
flex-grow: 1;
height: 100%;
font-size: 28upx;
color: #1A1A1A;
}
>image{
flex-shrink: 0;
flex-grow: 0;
width: 28upx;
height: 28upx;
}
}
}
.c-scan-btn{
margin: 0 auto 24upx;
width: 702upx;
height: 360upx;
border-radius: 10upx;
background-color: #fff;
@include centerFlex(center);
>image{
width: 172upx;
height: 172upx;
}
}
.c-verification-code{
padding: 40upx;
border-radius: 10upx;
background-color: #fff;
>view{
&:first-child{
margin-bottom: 30upx;
padding: 0 20upx;
height: 112upx;
border-radius: 10upx;
background-color: #f2f2f7;
>input{
height: 100%;
width: 100%;
font-size: 32upx;
color: #1a1a1a;
}
}
&+view{
height: 112upx;
text-align: center;
line-height: 112upx;
border-radius: 10upx;
font-size: 32upx;
color: #fff;
background-color: $themeColor;
}
}
}
}
</style>

339
src/pages/write_off/search_result/search_result.vue

@ -1,339 +0,0 @@
<template>
<view class="search-result">
<view class="store-bar">
<text>当前门店</text>
<picker mode="selector" :range="stadiumList" range-key="name" @change="stadiumChange">
<view>
<input disabled v-model="curStadium.name" />
<image mode="aspectFit" src="/static/images/icon/arrow_c33.png"></image>
</view>
</picker>
</view>
<view class="r-timer-select">
<picker mode="date" @change="dateChange">
<view>
<text>核销日期{{curDate || '-'}}</text>
<image mode="aspectFit" src="/static/images/icon/arrow_c33.png"></image>
</view>
</picker>
<view>核销数量{{writeOffList.length || 0}}</view>
</view>
<view class="r-order-list">
<view class="l-item" v-for="(e, i) in writeOffList" :key="i">
<view class="i-name">{{ e.extension.stadium_name || '-' }}</view>
<view class="i-lines">
<view>
<view>订单编号{{ e.order_no || '-' }}</view>
<view>{{ e.type || '-' }}</view>
</view>
<view>
<view>用户信息{{ e.extension.user_phone || '-' }}{{ e.extension.nickname || '-' }}</view>
</view>
<view>
<view>核销码&#12288;{{e.verify_code || '-' }}</view>
</view>
<view>
<view>验证方式{{ e.desc || '-' }}</view>
</view>
<view>
<view>核销时间{{ e.verify_time || '-'}}</view>
</view>
<view>
<view>离场时间{{ e.verify_leave_time || ''}}</view>
</view>
</view>
<view class="i-btn" v-if="e.verify_leave_time==''" @click="leaveBtn(e)">手动离场</view>
</view>
</view>
<view class="r-bottom-btn"><view @click="toOperate">核销订单</view></view>
</view>
</template>
<script>
import util from '../../../utils/util';
import { API } from '../../../js/api';
import { servers } from '../../../js/server';
import { mapState } from 'vuex';
import { WRITE_OFF_STORE_NAME } from '../../../js/once_name';
export default {
computed: {
...mapState([ 'brandInfo' ]),
},
data(){
return {
stadiumList: [], //
curStadium: {}, //
writeOffList: [], // ,
curDate: util.formatDate({}),
page: 1,
totalNum: 0,
}
},
// 20210716
// onReachBottom(){
// let { page, curDate, curStadium } = this;
// this.getList({
// brand_id: curStadium.brand_id || '',
// stadium_id: curStadium.id || '',
// date: curDate || '',
// page: ++page,
// });
// },
onLoad(){
this.initPage();
},
onShow(){
let { curStadium } = this;
if(curStadium&&curStadium.id)this.refreshList();
},
methods: {
refreshList(){
let { curDate, curStadium } = this;
this.page = 1;
this.writeOffList = [];
this.getList({
brand_id: curStadium.brand_id || '',
stadium_id: curStadium.id || '',
date: curDate || '',
});
},
dateChange(e){
this.curDate = e.detail.value;
this.$nextTick(this.refreshList);
},
stadiumChange(e){
let { stadiumList } = this;
this.curStadium = stadiumList[e.detail.value];
this.$nextTick(this.refreshList);
},
async initPage(){
let { brandInfo } = this;
try{
let _storeList = await this.getStoreList({ brand_id: brandInfo.brand.id || '' });
this.stadiumList = _storeList || [];
if(_storeList.length) this.curStadium = _storeList[0];
this.$nextTick(this.refreshList);
}catch(err){
console.warn('加载数据失败!', err);
}
},
toOperate(){
let { stadiumList, curStadium } = this;
util.$_emit(WRITE_OFF_STORE_NAME, {
stadiumList,
curStadium,
})
util.routeTo(`/pages/write_off/operate/operate?type=site`, 'nT');
},
getList({ brand_id, stadium_id = '', date = '', page = 1, page_size = '' }){
util.showLoad();
servers.get({
url: API.writeOff.listVerifyRecord,
data: { brand_id, stadium_id, date, page, page_size },
failMsg: '加载失败!',
})
.then(res=>{
util.hideLoad();
this.totalNum = res.total || 0;
let _list = res.list || [];
if(page == 1)return this.writeOffList = _list;
if(!_list.length)return util.showNone('没有更多!');
this.page = page;
this.writeOffList = [...this.writeOffList, ..._list];
})
},
getStoreList({
page=1,
page_size=9999,
brand_id='',
}){
return servers.get({
url: API.stadiumList,
data: {
page,
page_size,
brand_id,
},
failMsg: '获取列表失败!'
})
.then(res=>{
let _list = res.list || [];
return _list
})
},
leaveBtn(item){
let { curStadium } = this;
util.showModal({
title: '提示',
content: '是否确认手动离场?',
showCancel: true,
success: modalRes=>{
if(modalRes.confirm){
util.showLoad();
servers.get({
url: API.writeOff.leaveVerifyOrder,
data: {
brand_id: curStadium.brand_id,
id: item.id,
},
failMsg: '请求失败!'
})
.then(res=>{
util.hideLoad();
util.showNone('操作成功!');
this.refreshList()
})
}
}
})
},
}
}
</script>
<style lang="scss">
@import "~style/public.scss";
page{
background-color: #f2f2f7;
}
.search-result{
padding-bottom: 132upx;
padding-bottom: calc( 132upx + constant(safe-area-inset-bottom)); /* 兼容 iOS < 11.2 */
padding-bottom: calc( 132upx + env(safe-area-inset-bottom)); /* 兼容 iOS >= 11.2 */
.store-bar{
padding: 0 24upx;
height: 144upx;
background-color: #fff;
@include centerFlex(space-between);
>text{
margin-right: 20upx;
flex-shrink: 0;
font-size: 28upx;
color: #9C9C9F;
}
>picker{
flex-grow: 1;
}
view{
padding: 0 20upx;
height: 92upx;
border-radius: 10upx;
background: #f2f2f2;
@include centerFlex(space-between);
>input{
flex-grow: 1;
height: 100%;
font-size: 28upx;
color: #1A1A1A;
}
>image{
flex-shrink: 0;
flex-grow: 0;
width: 28upx;
height: 28upx;
}
}
}
.r-timer-select{
padding: 0 24upx;
margin-bottom: 10upx;
@include centerFlex(space-between);
picker{
flex-shrink: 0;
view{
padding: 24upx 0;
>text{
font-weight: 500;
font-size: 32upx;
}
>image{
margin-left: 16upx;
vertical-align: middle;
width: 22upx;
height: 22upx;
}
}
}
>view{
max-width: 40%;
text-align: right;
font-weight: 500;
font-size: 32upx;
@include textHide(1);
}
}
.r-order-list{
padding: 0 24upx;
.l-item{
position: relative;
margin-bottom: 24upx;
padding: 0 20upx;
border-radius: 10upx;
background-color: #fff;
.i-name{
height: 100upx;
line-height: 98upx;
border-bottom: 2upx solid #D8D8D8;
font-weight: 500;
font-size: 28upx;
color: #1a1a1a;
}
.i-lines{
padding-top: 8upx;
padding-bottom: 32upx;
>view{
display: flex;
>view{
min-width: 0;
line-height: 52upx;
font-size: 28upx;
color: #9c9c9f;
@include textHide(1);
&+view{
flex-shrink: 0;
color: $themeColor;
}
}
}
}
.i-btn{
position: absolute;
right: 20rpx;
bottom: 32rpx;
background-color: #009874;
border-radius: 10rpx;
width: 156rpx;
height: 68rpx;
color: #FFFFFF;
font-size: 28rpx;
text-align: center;
line-height: 68rpx;
}
}
}
.r-bottom-btn{
position: fixed;
left: 0;
bottom: 0;
width: 100%;
padding: 10upx 24upx;
padding-bottom: calc( 10upx + constant(safe-area-inset-bottom)); /* 兼容 iOS < 11.2 */
padding-bottom: calc( 10upx + env(safe-area-inset-bottom)); /* 兼容 iOS >= 11.2 */
background-color: #f2f2f7;
>view{
height: 112upx;
line-height: 112upx;
text-align: center;
font-size: 32upx;
border-radius: 10upx;
color: #fff;
background-color: $themeColor;
}
}
}
</style>

BIN
src/static/images/code_null.png

Before

Width: 380  |  Height: 380  |  Size: 7.4 KiB

BIN
src/static/images/countdown_bg.png

Before

Width: 700  |  Height: 700  |  Size: 33 KiB

BIN
src/static/images/icon/arrow_b2.png

Before

Width: 40  |  Height: 40  |  Size: 251 B

After

Width: 15  |  Height: 15  |  Size: 226 B

BIN
src/static/images/icon/index/green_bg_circle.png

Before

Width: 296  |  Height: 296  |  Size: 51 KiB

After

Width: 296  |  Height: 296  |  Size: 7.7 KiB

BIN
src/static/images/icon/index/tab_12.png

Before

Width: 52  |  Height: 52  |  Size: 2.4 KiB

After

Width: 52  |  Height: 52  |  Size: 901 B

BIN
src/static/images/icon/index/tab_8.png

Before

Width: 52  |  Height: 52  |  Size: 2.4 KiB

After

Width: 52  |  Height: 52  |  Size: 775 B

BIN
src/static/images/icon/scan_code_btn.png

Before

Width: 172  |  Height: 172  |  Size: 932 B

BIN
src/static/images/icon/selected_ring.png

Before

Width: 32  |  Height: 32  |  Size: 419 B

BIN
src/static/images/icon/write_off_fail.png

Before

Width: 420  |  Height: 420  |  Size: 9.6 KiB

BIN
src/static/images/order_null.png

Before

Width: 380  |  Height: 380  |  Size: 8.5 KiB

BIN
src/static/images/scan_null.png

Before

Width: 420  |  Height: 420  |  Size: 11 KiB

BIN
src/static/images/tab/ta_1.png

After

Width: 56  |  Height: 56  |  Size: 558 B

BIN
src/static/images/tab/ta_2.png

After

Width: 56  |  Height: 56  |  Size: 795 B

BIN
src/static/images/tab/ta_3.png

After

Width: 56  |  Height: 56  |  Size: 726 B

BIN
src/static/images/tab/ta_4.png

After

Width: 56  |  Height: 56  |  Size: 553 B

BIN
src/static/images/tab/tab_1.png

After

Width: 56  |  Height: 56  |  Size: 653 B

BIN
src/static/images/tab/tab_2.png

After

Width: 56  |  Height: 56  |  Size: 986 B

BIN
src/static/images/tab/tab_3.png

After

Width: 56  |  Height: 56  |  Size: 939 B

BIN
src/static/images/tab/tab_4.png

After

Width: 56  |  Height: 56  |  Size: 668 B

BIN
src/static/images/third_pages/banner.png

After

Width: 351  |  Height: 200  |  Size: 23 KiB

BIN
src/static/images/third_pages/bg.png

After

Width: 170  |  Height: 210  |  Size: 2.5 KiB

BIN
src/static/images/third_pages/tab_a.png

After

Width: 68  |  Height: 57  |  Size: 2.3 KiB

BIN
src/static/images/third_pages/tab_b.png

After

Width: 68  |  Height: 57  |  Size: 1.9 KiB

BIN
src/static/images/write_off/mall.png

Before

Width: 128  |  Height: 128  |  Size: 1.4 KiB

BIN
src/static/images/write_off/order.png

Before

Width: 128  |  Height: 128  |  Size: 1.0 KiB

BIN
src/static/images/write_off/people.png

Before

Width: 128  |  Height: 128  |  Size: 1.8 KiB

BIN
src/static/images/write_off/site.png

Before

Width: 120  |  Height: 120  |  Size: 1.4 KiB

74
src/store/actions.js

@ -1,6 +1,8 @@
// 异步方法
import { servers } from '../js/server';
import { API } from '../js/api';
import { showLoad, hideLoad, showModal, promisify } from '../utils/util';
export default {
getBrandInfo({commit, state}){
if(state?.brandInfo?.brand?.id)return Promise.resolve(state.brandInfo);
@ -24,5 +26,75 @@ export default {
.catch(err=>{
console.warn('actions getOrderRefundList err -->', err);
})
}
},
// 获取门店列表
getStadiumLs({ commit, state }, brand_id){
showLoad();
return servers.post({
url: API.stadiumList,
data: { page_size: 9999, page: 1, brand_id },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
let _ls = _data?.data?.list || [];
return _ls;
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({
title: '提示',
content: err.message || '加载店铺失败!'
})
console.warn('store actions getStadiumLs err --->', err);
// return Promise.reject(err);
})
},
// 看是否授权,清除token 也可以理解自动登陆
async checkUserAuthor({ commit, state }){
let loginRes = {};
try{
const uniLogin = promisify(uni.login);
loginRes = await uniLogin();
}catch(err){
console.warn('store actions checkUserAuthor login err', err);
showModal({ content: '获取登陆凭证失败!请稍后重新登录!' });
return Promise.reject(err);
}
showLoad();
return servers.post({
url: API.wechatMiniAppLoginAndSync,
data: {
code: loginRes.code,
appid: state.APPID,
},
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
if(_data.data === ''){
commit('setLoginState', { loginState: false, token: '' });
return _data;
}
commit('setLoginState', { loginState: true, token: _data.data });
return _data;
}else{
return Promise.reject(_data);
}
})
.catch(err=>{
console.warn('store actions checkUserAuthor wechatMiniAppLoginAndSync err --->', err);
hideLoad();
showModal({ content: err?.message || '静默登录失败!请稍后重试!' });
return Promise.reject(err);
})
},
}

22
src/store/index.js

@ -33,19 +33,20 @@ export default new Vuex.Store({
// },
permissionObj: { // 权限代号对应
'1001': '营业额',
'1002': '收款记录',
// '1002': '收款记录',
'1012': '订单管理',
'1007': '员工管理',
'1008': '核销查询',
'1009': '场地管理',
'1010': '设备管理',
'1009': '场地看板',
'1010': '智能设备',
'1011': '商品零售',
// '1013': '课程管理',
'1014': '储值卡管理',
'1015': '进场人数异常',
'1016': '系统工具',
'1017': '钱包提现',
// '1015': '进场人数异常',
'1016': '小程序管理',
'1017': '钱包&提现',
'1018': '退款权限',
'1022': '工单',
'1019': '水阀卡管理',
},
// 场地占用提交页面信息
@ -55,7 +56,8 @@ export default new Vuex.Store({
typeInfo: {}, // 球场类型
venueList: [], // 选择场地列表
orderInfo: {}, // 订单信息 // 20230620 新增挂账需求,用于回显确认订单
}
},
isLogin: false, // 登陆状态
},
mutations,
actions,
@ -70,6 +72,12 @@ export default new Vuex.Store({
}
console.warn(_arr.length)
return _arr;
},
loginState: state =>{
let _isLogin = state.isLogin;
let _token = uni.getStorageSync('token');
// 只是想获得一个响应的token状态
if(_isLogin || !_isLogin)return !!_token;
}
}
});

13
src/store/mutations.js

@ -10,5 +10,18 @@ export default {
// 场地占用信息
setOccupyInfo(state, _occupyInfo){
state.occupyInfo = _occupyInfo;
},
//
setLoginState(state, { loginState = false, token = '' }){
console.log(loginState, token);
if(loginState&&token){
uni.setStorageSync('token', token);
state.isLogin = loginState;
}else{
uni.removeStorageSync('token', token);
state.isLogin = false;
}
}
}

188
src/subpackage/authorization/components/login.vue

@ -0,0 +1,188 @@
<template>
<view class="authorization-login" v-show="isShow">
<view class="ic-author-modal">
<view class="iam-title">微信授权</view>
<view class="iam-tip">您的信息和数据将受到保护</view>
<image class="iam-pic" mode="aspectFit" src="../static/images/author_modal.png"></image>
<view class="iam-btns">
<button plain hover-class="hover-active" @click="hide">取消</button>
<button
v-if="isProfile"
plain
hover-class="hover-active"
@click="profileConfirm"
>授权并登录</button>
<button
v-else
plain
hover-class="hover-active"
open-type="getUserInfo"
lang="zh_CN"
@getuserinfo="confirmAuthor"
>授权并登录</button>
</view>
</view>
</view>
</template>
<script>
import { promisify, showModal, showNone, showLoad, hideLoad, routeTo } from "@/utils/util";
import { AUTHOR_API } from "../js/api";
import server from "../js/server";
export default {
data(){
return {
isShow: false,
initData: {
/**
* @param {Object} data
* @param {Function} data.success
* @param {Function} data.fail
* */
},
}
},
methods: {
alert(data){
this.isShow = true;
this.initData = data ?? {};
},
hide(){
this.isShow = false;
},
isProfile(){
return !!uni.getUserProfile
},
//
profileConfirm(){
uni.getUserProfile({
lang: 'zh_CN', desc: '授权登陆',
success: res => { this.confirmAuthor({detail: {...res}}) },
fail: err => {
showModal({ content: '获取用户信息失败!请重试' });
console.warn('subpackage authorization components login getUserProfile Err ->', err)
}
})
},
async confirmAuthor(userRes){
if(!userRes.detail.userInfo){
this.hide();
return showModal({ content: '获取用户信息失败!请稍后重试' });
}
let loginRes = await promisify(uni.login)();
if(!loginRes.code){
this.hide();
return showModal({ content: '获取登陆凭证失败!请稍后重试' });
}
this.serverLogin({ userRes, loginRes })
},
getLoginQuery({
userInfo,
loginRes
}){
return {
appid: uni.getAccountInfoSync?.()?.miniProgram?.appId ?? '',
code: loginRes?.code ?? '',
encryptedData: userInfo?.encryptedData ?? '',
// is_details: 1,
//
user_info: userInfo?.userInfo ?? {},
user_raw_data: userInfo?.rawData ?? {},
...(userInfo?.userInfo ?? {}),
}
},
serverLogin({ userRes, loginRes }){
showLoad();
return server.post({
url: AUTHOR_API.wechatMiniAppLoginAndSync,
data: this.getLoginQuery({
userInfo: userRes.detail,
loginRes
}),
isDefaultGet: false,
})
.then(res=>{
hideLoad();
let _data = res.data || {};
if(_data.code == 0){
if(_data.data == '')return routeTo(`/pages/login/login`,'rL');
showNone(_data?.message || '登陆成功!');
this.$store.commit('setLoginState', { loginState: true, token: _data.data });
this.initData?.success?.(_data);
this.hide();
return _data;
}else{
return Promise.reject(_data);
}
})
.catch(err=>{
hideLoad();
showModal({ content: err?.message || '后台登陆失败!' });
console.warn('subpackage authorization components login serverLogin Err ->', err);
if(this.initData?.fail)return this.initData?.fail?.(err);
return Promise.reject(err);
})
},
cancelAuthor(){
}
}
}
</script>
<style lang="scss">
.authorization-login{
position: fixed;
left: 0;
top: var(--window-top);
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .5);
}
.ic-author-modal{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
padding-top: 60upx;
width: 662upx;
height: 884upx;
border-radius: 10upx;
background-color: #fff;
.iam-title{
margin-bottom: 22upx;
text-align: center;
@include flcw(44upx, 60upx, #1a1a1a, 500);
}
.iam-tip{
margin-bottom: 52upx;
text-align: center;
@include flcw(28upx, 40upx, #9c9c9f);
}
.iam-pic{
margin: 0 auto 62upx;
display: block;
width: 488upx;
height: 416upx;
}
.iam-btns{
@include ctf(center);
>button{
margin: 0 20upx;
width: 240upx;
height: 92upx;
text-align: center;
border-radius: 46upx;
border: 2upx solid $mColor;
@include flcw(32upx, 88upx, $mColor);
&+button{
background-color: $mColor;
color: #fff;
}
}
}
}
</style>

25
src/subpackage/authorization/components/user_info/iconfont.css

@ -0,0 +1,25 @@
@charset "UTF-8";
@font-face {
font-family: "tuniaoFont"; /* Project id 3784643 */
src:
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAN8AAsAAAAAB4gAAAMvAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHFQGYACDBgqCQIIdATYCJAMMCwgABCAFhH8HPRuYBiMRpmvPIfurAzsYPj7DCVbrRAmElfXCs4V6xacO8aHO69VXu54tTy8h0hcJ7ufgieY+3+xscrn9B8wKQQFBywplLZCw9SwcOyZ5pKsoAQAbcICJTr7AjPPqr06NRkeIAemFpXv8cje1+/LkuLvD789WJZUANjOe9F76qptDOSMUui/Ua0WQKpZGXaqf5hHZdAD6x8tRJtRKtQd7j5UKkk6lWywYkGADCZBnGLugJd6CAI4GOjH956Ep4k7imdNkYTGFAkm2nJyPrBNwA7YhWwgQ/K4bLXITD8wl8xxuBN+XwyYJwOAp5jFjOwNbPNwd3l3WiyFAv3sCBLjcAB6gQM3GjLLSbJpDo1ANiqsiFiX7WAQzqTcMA06j/pb/eAbEx8yMAaAChg3sDjs6COwub8IAoAD4PEZkA7iAZClPKdicmJLc2OiZcQC3cV613sc3Ab9W7YItQtio9YgdKGjgr+ANlbg5gPvXFacv8IyvKTH4pcjV7VIcfnuIAUd3VCbxcCIKj++rvr/hpx/iwxd4/5mxDXeILbBp2+ByMIlGMuQEU3gjiVaMcXk8Dn2gWo5wuRwuIldLRqRwWogvmCRXE9/kSvrVQP4iFDVWRSbRakMk/AtcBYVpO6rpah1V9J/m/Wt+q8WfsOV+S5KFTGD5dwW9KFstZUs5smuYK3V495zwUG+r/Izf0PWtbWj/HUQsAL9u+cGK8NCiAgRnaRJ49fqljrxWc4PaKs4pQwBlMLUeAzgcEGLakvg5nCZ3zgTBkZzAEEUuFEeJyMrq4BGhGT6OPjiqGd4eIZkLKGKjgTKuAAgpPIAhjmdQUngnsrJdeKTxBz4pkgjHjJQeFqFMXv8PQqg0QP+F2cbAdoHqFmaM52jYlcbNoeTq4CtPkjzHAE1Zl0QvFEjGmp9fY6vKwBI9kOvIuQhJ4kJWy0k1dVXFVXcpbfTmIIRKA/RfmG0M7G1T3cKM8VwMXGlEnR1KY0uU+CTJnM1ODk1ZdxJdSOjYdGOp+TW2k6szsNg8qIvIuQipetFCVstpQCB1lcXHXUl5ZOpfdBvgMI9QMaJi8TZdzLS36K8/ueDZOWMAAA==') format('woff2'),
url('//at.alicdn.com/t/c/font_3784643_5jru9pe5fad.woff?t=1669045092678') format('woff'),
url('//at.alicdn.com/t/c/font_3784643_5jru9pe5fad.ttf?t=1669045092678') format('truetype');
}
[class*='tn-icon-'] {
font-family: 'tuniaoFont' !important;
font-style: normal;
-webkit-font-smoothing: antialiased;
text-align: center;
text-decoration: none;
}
.tn-icon-close:before {
content: "\e74d";
}
.tn-icon-camera-fill:before {
content: "\e75d";
}

133
src/subpackage/authorization/components/user_info/impower.vue

@ -0,0 +1,133 @@
<template>
<wx-user-info-modal
v-model="showAuthorizationModal"
@updated="updatedUserInfoEvent"
:isModal="isModal"
:alway="alway"
></wx-user-info-modal>
</template>
<script>
import WxUserInfoModal from './tuniaoui-wx-user-info.vue';
import { AUTHOR_API } from '../../js/api';
import servers from '../../js/server';
import { showModal, jsonPar, showLoad, hideLoad } from '@/utils/util';
export default {
components: { WxUserInfoModal },
props: {
isModal: {
type: Boolean,
default: true
},
alway: {
type: Boolean,
default: false
}
},
watch: {
showAuthorizationModal(nVal, oVal){
let { initData } = this;
if(!nVal&&oVal)initData?.close?.();
}
},
data() {
return {
showAuthorizationModal: false,
initData: {},
}
},
methods: {
show(data){
this.showAuthorizationModal = true;
this.initData = data;
},
hide(){
this.showAuthorizationModal = false;
},
//
async updatedUserInfoEvent(info) {
let { initData, userEdit } = this;
try{
let _imgUrl = await this.uploadImg(info.avatar);
let _editObj = { nickname: info.nickname, avatar_url: _imgUrl };
await userEdit(_editObj);
this.hide();
initData?.success?.(_editObj);
}catch(err){
console.warn('authorize components user info impower updatedUserInfoEvent err --->', err)
initData?.fail?.(err);
}
},
//
async uploadImg(url){
try{
showLoad();
let _imgInfo = await servers.uploadFile({
url: AUTHOR_API.zs_user_avatar,
filePath: url,
});
if(_imgInfo.statusCode != 200)throw(_imgInfo);
let _imgRes = jsonPar(_imgInfo.data);
if(_imgRes.code != 0)throw(_imgRes);
hideLoad();
return _imgRes.data.url || '';
}catch(err){
hideLoad();
showModal({ content: err?.errMsg ?? err?.message ?? '上传图片失败!' });
console.warn('authorize components user info impower uploadImg err --->', err)
return Promise.reject(err);
}
},
//
userEdit({ nickname, avatar_url }){
showLoad();
return servers.post({
url: AUTHOR_API.changeAvatar,
data: { nickname, avatar_url },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
console.log('userEdit res--->', _data);
return _data?.data;
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({ content: err.message || '操作失败!' });
console.warn('authorize components user info impower userEdit err --->', err);
return Promise.reject(err);
})
},
//
refreshUserInfo(){
return servers.post({
url: ATR_API.userCurrent,
data: {},
isDefaultGet: false,
})
.then(res => {
let _data = res?.data || {};
if(_data.code === 0){
uni.setStorageSync('userInfo', _data?.data);
this.$store.commit('refreshUserInfo', _data?.data);
return _data?.data;
}else{
return Promise.reject(_data);
}
})
.catch(err => {
console.warn('authorize components user info impower refreshUserInfo err --->', err);
// return Promise.reject(err);
})
}
}
}
</script>
<style lang="scss">
</style>

357
src/subpackage/authorization/components/user_info/tuniaoui-wx-user-info.vue

@ -0,0 +1,357 @@
<template>
<view
v-if="openModal || alway"
class="wx-authorization-modal"
:class="{ 'modal-class': isModal }"
>
<view
class="wam__mask"
@touchmove.prevent=""
@tap.stop="closeModal"
></view>
<!-- 内容区域 -->
<view class="wam__wrapper">
<!-- 关闭按钮 -->
<view class="wam__close-btn" @tap.stop="closeModal">
<text class="tn-icon-close"></text>
</view>
<!-- 标题 -->
<view class="wam__title">获取您的昵称头像</view>
<!-- tips -->
<view class="wam__sub-title">
获取用户头像昵称主要用于向用户提供具有辨识度的用户中心界面
</view>
<!-- 头像选择 -->
<view class="wam__avatar">
<view class="button-shadow">
<button
class="button"
open-type="chooseAvatar"
@chooseavatar="chooseAvatarEvent"
>
<view v-if="userInfo.avatar" class="avatar__image">
<image class="image" :src="userInfo.avatar" mode="aspectFill"></image>
</view>
<view v-else class="avatar__empty">
<!-- <image class="image" src="https://cdn.nlark.com/yuque/0/2022/jpeg/280373/1668928062708-assets/web-upload/764843cf-055a-4cb6-b5d3-dca528b33fd4.jpeg" mode="aspectFill"></image> -->
</view>
<view class="avatar--icon">
<view class="tn-icon-camera-fill"></view>
</view>
</button>
</view>
</view>
<!-- 昵称输入 -->
<view class="wam__nickname">
<view class="nickname__data">
<input class="input" type="nickname" v-model="userInfo.nickname" placeholder="请输入昵称" placeholder-style="color: #AAAAAA;">
</view>
</view>
<!-- 保存按钮 -->
<view
class="wam__submit-btn"
:class="[{
'disabled': !userInfo.avatar || !userInfo.nickname
}]"
hover-class="tn-btn-hover-class"
:hover-stay-time="150"
@tap.stop="submitUserInfo"
>
</view>
</view>
</view>
</template>
<script>
export default {
options: {
// Vue(shadow)
virtualHost: true
},
props: {
value: {
type: Boolean,
default: false
},
isModal: {
type: Boolean,
default: true
},
alway: {
type: Boolean,
default: false
}
},
data() {
return {
openModal: false,
userInfo: {
avatar: '',
nickname: ''
}
}
},
watch: {
value: {
handler(val) {
this.openModal = val
},
immediate: true
}
},
methods: {
//
chooseAvatarEvent(e) {
this.userInfo.avatar = e.detail.avatarUrl
},
//
submitUserInfo() {
//
if (!this.userInfo.avatar || !this.userInfo.nickname) {
return uni.showToast({
icon: 'none',
title: '请选择头像和输入用户信息'
})
}
//
this.$emit('updated', this.userInfo)
},
//
closeModal() {
this.$emit('input', false)
},
}
}
</script>
<style lang="scss" scoped>
@import './iconfont.css';
.modal-class{
position: fixed;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
z-index: 99998;
.wam {
&__mask {
display: block !important;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
opacity: 0;
animation: showMask 0.25s ease 0.1s forwards;
}
&__close-btn {
display: block !important;
display: none;
position: absolute;
top: 30rpx;
right: 30rpx;
z-index: 99999;
}
&__wrapper{
position: absolute;
left: 0;
bottom: 0;
width: 100%;
transform-origin: center bottom;
transform: scaleY(0);
animation: showWrapper 0.25s ease 0.1s forwards;
z-index: 99999;
}
}
}
.wx-authorization-modal {
// view {
// box-sizing: border-box;
// }
.image {
width: 100%;
height: 100%;
border-radius: inherit;
}
.wam {
/* mask */
&__mask {
display: none;
}
/* close-btn */
&__close-btn {
display: none;
}
/* wrapper */
&__wrapper {
background-color: #FFFFFF;
border-radius: 20rpx 20rpx 0rpx 0rpx;
padding: 40rpx;
padding-top: 60rpx;
padding-bottom: 40rpx;
padding-bottom: calc(constant(safe-area-inset-bottom) + 40rpx);
padding-bottom: calc(env(safe-area-inset-bottom) + 40rpx);
}
/* title */
&__title {
font-size: 34rpx;
}
/* sub-title */
&__sub-title {
font-size: 26rpx;
color: #AAAAAA;
margin-top: 16rpx;
padding-bottom: 30rpx;
}
/* 头像选择 */
&__avatar {
width: 100%;
margin-top: 30rpx;
display: flex;
align-items: center;
justify-content: center;
.button-shadow {
border: 8rpx solid rgba(255,255,255,0.05);
box-shadow: 0rpx 0rpx 80rpx 0rpx rgba(0, 0, 0, 0.15);
border-radius: 50%;
}
.button {
position: relative;
width: 160rpx;
height: 160rpx;
border-radius: 50%;
overflow: visible;
background-image: repeating-linear-gradient(45deg, #E4E9EC, #F8F7F8);
color: #FFFFFF;
background-color: transparent;
padding: 0;
margin: 0;
font-size: inherit;
line-height: inherit;
border: none;
&::after {
border: none;
}
}
.avatar {
&__empty, &__image {
width: 100%;
height: 100%;
border-radius: inherit;
}
&--icon {
position: absolute;
right: -10rpx;
bottom: -6rpx;
width: 60rpx;
height: 60rpx;
// transform: translate(50%, 50%);
background-color: #1D2541;
color: #FFFFFF;
border-radius: 50%;
border: 6rpx solid #FFFFFF;
line-height: 1;
font-size: 36rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
}
/* 昵称 */
&__nickname {
margin-top: 40rpx;
.nickname {
&__data {
margin-top: 16rpx;
width: 100%;
padding: 26rpx 20rpx;
border-radius: 10rpx;
background-color: #F8F7F8;
.input {
color: #080808;
}
}
}
}
/* 保存按钮 */
&__submit-btn {
width: 100%;
background-color: $mColor;
color: #FFFFFF;
margin-top: 60rpx;
border-radius: 10rpx;
padding: 25rpx;
font-size: 32rpx;
display: flex;
align-items: center;
justify-content: center;
&.disabled {
background-color: #E6E6E6;
}
}
}
}
.tn-btn-hover-class {
box-shadow: inset 10rpx 2rpx 40rpx 0rpx rgba(0, 0, 0, 0.05);
}
@keyframes showMask {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
@keyframes showWrapper {
0% {
transform: scaleY(0);
}
100% {
transform: scaleY(1);
}
}
</style>

9
src/subpackage/authorization/js/api.js

@ -0,0 +1,9 @@
import { ORIGIN } from '@/js/api';
export const AUTHOR_API = {
wechatMiniAppLoginAndSync: `${ORIGIN}/assistant/WechatMiniAppGetToken`, // 小程序授权获取token,为空就登录
zs_user_avatar:`${ORIGIN}/upload/file/zs_user_avatar`, // 头像图片上传
changeAvatar: `${ORIGIN}/admin/assistant/changeAvatar`, // 修改用户头像、昵称
}
export default AUTHOR_API;

10
src/subpackage/authorization/js/server.js

@ -0,0 +1,10 @@
import { Server } from '@/js/server';
class _Server extends Server {
constructor(props){
super(props)
}
}
export default new _Server();

16
src/subpackage/authorization/pages/index.vue

@ -0,0 +1,16 @@
<template>
<!-- 需要定义一个页面以及在pagesjson里声明才能生成static文件夹 -->
<view>&nbsp;</view>
</template>
<script>
export default {
}
</script>
<style lang="scss">
page{
background: #fff;
}
</style>

0
src/static/images/icon/author_modal.png → src/subpackage/authorization/static/images/author_modal.png

Before

Width: 488  |  Height: 416  |  Size: 14 KiB

After

Width: 488  |  Height: 416  |  Size: 14 KiB

4
src/subpackage/blacklist/pages/abnormal_list/abnormal_list.vue

@ -102,10 +102,10 @@ export default {
async onLoad(options){
try{
let _date = this.curDate = util.formatDate({});
let _stadiumLs = await this.getStoreList({});
let _stadiumLs = await this.getStoreList({ brand_id: options?.brand_id ?? '' });
let { curTimeTab } = this;
this.getRecords({
brand_id: options.brand_id,
brand_id: options?.brand_id ?? '',
[ curTimeTab?.label || '-' ]: _date
});
}catch(err){

41
src/subpackage/menu/components/bottom_logo.vue

@ -0,0 +1,41 @@
<template>
<view class="bottom-logo" :class="{ 'fixed-active': isFixed }" @click="toWebView">
<image @click="toWebView" class="bl-img" mode="aspectFit" src="/subpackage/menu/static/images/bot_logo.png"></image>
</view>
</template>
<script>
import { routeTo, jsonStr } from '@/utils/util.js'
export default {
props: {
isFixed: {
type: Boolean,
default: false
}
},
methods: {
toWebView(){
routeTo(`/pages/web_view/web_view?src=${jsonStr('https://www.ouxuanzhineng.cn/')}`, 'nT');
}
}
}
</script>
<style lang="scss">
.bottom-logo{
height: 200upx;
width: 100%;
@include ctf(center);
&.fixed-active{
position: fixed;
left: 50%;
bottom: 0;
transform: translateX(-50%);
z-index: -1;
}
}
.bl-img{
width: 312upx;
height: 100upx;
}
</style>

87
src/subpackage/menu/components/mine/header.vue

@ -0,0 +1,87 @@
<template>
<view class="mine-header">
<view class="mh-user">
<image class="mu-photo" mode="aspectFill" :src="photo"></image>
<view class="mu-info" v-if="isLogin">
<view class="mi-line">昵称{{ nickname || '-' }}</view>
<view class="mi-line">姓名{{ name || '-' }}</view>
<view class="mi-line">账号{{ account || '-' }}</view>
</view>
<button v-else class="mu-login" @click="$emit('click:login')">点击登录</button>
</view>
<view class="mh-update" v-if="isLogin" @click="$emit('click:update')">更新头像昵称</view>
</view>
</template>
<script>
export default {
props: {
isLogin: {
type: Boolean,
default: false
},
nickname: {
type: String,
default: ''
},
name: {
type: String,
default: ''
},
account: {
type: String,
default: ''
},
photo: {
type: String,
default: ''
}
},
mounted(){
console.log('mounted');
this.$emit('on:munted');
},
}
</script>
<style lang="scss">
.mine-header{
padding-top: 70upx;
height: 270upx;
background: $mColor;
.mh-user{
padding-left: 68upx;
padding-right: 30upx;
@include ctf;
.mu-photo{
flex-shrink: 0;
margin-right: 28upx;
width: 120upx;
height: 120upx;
border-radius: 50%;
border: 4upx solid #FFFFFF;
}
.mu-info{
.mi-line{
@include flcw(32upx, 44upx, #FFFFFF);
@include tHide;
}
}
.mu-login{
@include clearBtn;
width: 180upx;
height: 72upx;
text-align: center;
border-radius: 6upx;
background: #fff;
@include flcw(32upx, 72upx, $mColor, 500);
}
}
.mh-update{
padding-left: 68upx;
margin-top: 8upx;
text-decoration: underline;
@include flcw(20upx, 28upx, #FFFFFF);
}
}
</style>

63
src/subpackage/menu/components/mine/line_tab.vue

@ -0,0 +1,63 @@
<template>
<view class="line-tab" @click="$emit('click')">
<image class="lt-icon" mode="aspectFit" :src="'/subpackage/menu/static/images/mine_tab/' + iconNum + '.png'"></image>
<view class="lt-right">
<view class="lr-left">
<view class="lr-name">
<slot name="default">账号管理</slot>
</view>
</view>
<view class="lr-right">
<slot name="right">
<image class="lr-arrow" mode="aspectFit" src="/subpackage/menu/static/images/arrow_b2.png"></image>
</slot>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
iconNum: {
type: Number,
default: 0
}
}
}
</script>
<style lang="scss">
.line-tab{
padding-left: 22upx;
padding-right: 44upx;
height: 108upx;
background: #fff;
@include ctf;
.lt-icon{
flex-shrink: 0;
margin-right: 22upx;
width: 48upx;
height: 48upx;
}
.lt-right{
flex-grow: 1;
@include ctf(space-between);
.lr-left{
.lr-name{
@include flcw(32upx, 48upx, #9A9A9D);
@include tHide;
}
}
.lr-right{
flex-shrink: 0;
max-width: 60%;
font-size: 0;
.lr-arrow{
width: 30upx;
height: 30upx;
}
}
}
}
</style>

16
src/subpackage/menu/pages/index.vue

@ -0,0 +1,16 @@
<template>
<!-- 需要定义一个页面以及在pagesjson里声明才能生成static文件夹 -->
<view class="menu-index">&nbsp;</view>
</template>
<script>
export default {
}
</script>
<style lang="scss">
page{
background: #fff;
}
</style>

BIN
src/subpackage/menu/static/images/arrow_b2.png

After

Width: 15  |  Height: 15  |  Size: 226 B

BIN
src/subpackage/menu/static/images/bot_logo.png

After

Width: 312  |  Height: 100  |  Size: 3.4 KiB

BIN
src/subpackage/menu/static/images/mine_tab/0.png

After

Width: 96  |  Height: 96  |  Size: 1.3 KiB

BIN
src/subpackage/menu/static/images/mine_tab/1.png

After

Width: 96  |  Height: 96  |  Size: 1.5 KiB

BIN
src/subpackage/menu/static/images/mine_tab/2.png

After

Width: 96  |  Height: 96  |  Size: 1.0 KiB

BIN
src/subpackage/menu/static/images/mine_tab/3.png

After

Width: 96  |  Height: 96  |  Size: 1.4 KiB

BIN
src/subpackage/menu/static/images/mine_tab/4.png

After

Width: 96  |  Height: 96  |  Size: 1.0 KiB

BIN
src/subpackage/menu/static/images/mine_tab/5.png

After

Width: 96  |  Height: 96  |  Size: 942 B

BIN
src/subpackage/menu/static/images/mine_tab/6.png

After

Width: 96  |  Height: 96  |  Size: 640 B

59
src/subpackage/message/components/detail/answer_item.vue

@ -0,0 +1,59 @@
<template>
<view class="answer-item" :class="{ 'active-bg': activeBg }">
<view class="ai-user">
<view class="au-name">{{ name || '-' }}</view>
<view class="au-time">[回复] {{ time || '-' }}</view>
</view>
<view class="ai-content">{{ content || '-' }}</view>
</view>
</template>
<script>
export default {
props: {
name: {
type: String,
default: ''
},
time: {
type: String,
default: ''
},
content: {
type: String,
default: ''
},
activeBg: {
type: Boolean,
default: false
}
}
}
</script>
<style lang="scss">
.answer-item{
padding: 30upx 22upx;
background: #fff;
border-radius: 10upx;
&.active-bg{
background: #faeada;
}
}
.ai-user{
@include ctf;
@include flcw(32upx, 44upx, #1A1A1A);
.au-name{
@include tHide;
}
.au-time{
flex-shrink: 0;
color: #9C9C9F;
}
}
.ai-content{
padding-left: 26upx;
margin-top: 24upx;
@include flcw(28upx, 40upx, #9C9C9F);
}
</style>

51
src/subpackage/message/components/detail/image_flow.vue

@ -0,0 +1,51 @@
<template>
<view class="image-flow">
<image
class="if-img"
v-for="(e, i) in imgs"
:key="i"
:src="e"
mode="aspectFill"
@click="previewImg(e)"
></image>
</view>
</template>
<script>
export default {
props: {
imgs: {
type: Array,
default: ()=>[]
}
},
methods: {
previewImg(url) {
let { imgs } = this;
uni.previewImage({
urls: imgs,
current: url
})
}
}
}
</script>
<style lang="scss">
.image-flow{
padding: 30upx 14upx;
background: #fff;
border-radius: 20upx;
font-size: 0;
.if-img{
margin: 0 12upx;
width: 200upx;
height: 200upx;
border-radius: 10upx;
&:nth-of-type(n+4){
margin-top: 28upx;
}
}
}
</style>

60
src/subpackage/message/components/detail/info.vue

@ -0,0 +1,60 @@
<template>
<view class="detail-info">
<view class="di-line">
<view class="dl-name">标题</view>
<view class="dl-value">{{ title || '-' }}</view>
</view>
<view class="di-line">
<view class="dl-name">类型</view>
<view class="dl-value">{{ type || '-' }}</view>
</view>
<view class="di-line">
<view class="dl-name">反馈内容</view>
<view class="dl-value">{{ content || '-' }}</view>
</view>
</view>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
},
type: {
type: String,
default: ''
},
content: {
type: String,
default: ''
}
},
}
</script>
<style lang="scss">
.detail-info{
padding: 30upx 24upx;
border-radius: 10upx;
background: #fff;
.di-line{
display: flex;
align-items: flex-start;
justify-content: space-between;
&+.di-line{
margin-top: 10upx;
}
.dl-name{
flex-shrink: 0;
@include flcw(28upx, 40upx, #1a1a1a);
}
.dl-value{
flex-grow: 1;
text-align: right;
@include flcw(28upx, 40upx, #1a1a1a);
}
}
}
</style>

37
src/subpackage/message/components/edit/fixed_button.vue

@ -0,0 +1,37 @@
<template>
<view class="fixed-button">
<button class="fb-btn" @click="$emit('click:cancel')">取消</button>
<button class="fb-btn" @click="$emit('click:submit')">提交</button>
</view>
</template>
<script>
export default {
}
</script>
<style lang="scss">
.fixed-button{
position: fixed;
left: 0;
bottom: 0;
padding: 10upx 20upx;
width: 100%;
@include isPd(10upx);
@include ctf(center);
.fb-btn{
margin: 0 20upx;
width: 240upx;
text-align: center;
border: 2upx solid $mColor;
border-radius: 44upx;
background: #fff;
@include flcw(32upx, 88upx, $mColor);
&+.fb-btn{
background: $mColor;
color: #fff;
}
}
}
</style>

195
src/subpackage/message/components/edit/info_edit.vue

@ -0,0 +1,195 @@
<template>
<view class="info-edit">
<!-- 门店picker -->
<view class="ie-stadium" v-if="isStadium">
<view class="ie-title"><text class="it-star">*</text>门店</view>
<picker mode="selector" :range="stadiumList" range-key="name" @change="stadiumChange">
<view class="is-box">
<input type="text" class="ib-ipt" placeholder="请选择门店" :value="fromData.curStadium.name || ''" disabled/>
<image class="ib-icon" mode="aspectFit" src="/subpackage/message/static/images/unfold.png"></image>
</view>
</picker>
</view>
<!-- 标题 -->
<view class="ie-section">
<view class="ie-title"><text class="it-star">*</text>标题</view>
<input type="text" class="ie-ipt" placeholder="请输入" v-model="fromData.titleStr" />
</view>
<!-- 类型 -->
<view class="ie-types" v-if="isStadium">
<view class="ie-title"><text class="it-star">*</text>类型</view>
<view class="it-ls">
<view class="il-item" v-for="(e, i) in ['硬件', '软件']" :key="i" @click="fromData.typeStr = e">
<view class="ii-icon" :class="{ 'active': e === fromData.typeStr }"></view>
<view class="ii-txt">{{ e || '' }}</view>
</view>
</view>
</view>
<!-- 反馈内容 -->
<view class="ie-section">
<view class="ie-title"><text class="it-star">*</text>反馈内容</view>
<textarea class="ie-tarea" placeholder="请输入" v-model="fromData.feedbackStr"></textarea>
</view>
</view>
</template>
<script>
import { showNone } from "@/utils/util"
export default {
props: {
isStadium: {
type: Boolean,
default: false
},
isType: {
type: Boolean,
default: false
}
},
data(){
return {
stadiumList: [],
fromData: {
curStadium: {},
titleStr: '',
typeStr: '硬件',
feedbackStr: '',
},
}
},
methods: {
async initStadiumLs({ brand_id, stadium_id }){
let _stadiums = await this.$store?.dispatch?.('getStadiumLs', brand_id);
if(_stadiums.length > 0)this.stadiumList = _stadiums ?? [];
if(stadium_id){
let _curStadium = _stadiums.find(item => item.id === stadium_id);
if(_curStadium?.id)this.fromData.curStadium = _curStadium;
}
},
stadiumChange(e){
let { value } = e.detail;
if(value === -1)return;
this.fromData.curStadium = this.stadiumList[value];
},
async getData(){
let { fromData, isStadium, isType } = this;
let { curStadium, titleStr, typeStr, feedbackStr } = fromData;
if(titleStr === ''){
showNone('请输入标题');
return Promise.reject('请输入标题');
}
if(feedbackStr === ''){
showNone('请输入反馈内容');
return Promise.reject('请输入反馈内容');
}
if(isStadium&&!curStadium?.id){
showNone('请选择门店');
return Promise.reject('请选择门店');
}
let _data = {
title: titleStr,
content: feedbackStr,
}
if(isType)_data['type'] = typeStr;
if(isStadium)_data['stadium_id'] = curStadium.id;
return _data;
}
}
}
</script>
<style lang="scss">
.info-edit{
padding: 30upx;
border-radius: 20upx;
background: #fff;
.ie-title{
flex-shrink: 0;
@include flcw(32upx, 44upx, #1A1A1A, 500);
.it-star{
color: #EA5061;
}
}
.ie-section{
margin-top: 24upx;
}
.ie-ipt, .ie-tarea{
display: block;
margin-top: 24upx;
padding: 26upx 20upx;
border-radius: 16upx;
background: #F6F8F9;
@include flcw(28upx, 40upx, #1A1A1A);
}
.ie-tarea{
height: 306upx;
width: auto;
}
.ie-stadium{
@include ctf;
.ie-title{
margin-right: 30upx;
}
.is-box{
padding: 0 22upx;
width: 478upx;
height: 92upx;
border: 2upx solid #979797;
border-radius: 16upx;
@include ctf;
.ib-ipt{
height: 100%;
flex-grow: 1;
@include flcw(28upx, 40upx, #1A1A1A);
}
.ib-icon{
margin-left: 20upx;
flex-shrink: 0;
width: 28upx;
height: 28upx;
}
}
}
.ie-types{
margin-top: 24upx;
@include ctf;
.ie-title{
margin-right: 76upx;
}
.it-ls{
@include ctf;
.il-item{
width: 200upx;
@include ctf;
.ii-icon{
position: relative;
width: 40upx;
height: 40upx;
border-radius: 50%;
border: 2upx solid #D8D8D8;
&.active{
border-color: $mColor;
&::before{
content: '';
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 28upx;
height: 28upx;
border-radius: 50%;
background: $mColor;
}
}
}
.ii-txt{
margin-left: 12upx;
@include flcw(28upx, 40upx, #1A1A1A);
}
}
}
}
}
</style>

189
src/subpackage/message/components/edit/upload_img.vue

@ -0,0 +1,189 @@
<template>
<view class="upload-img">
<view class="ui-title">上传照片<text class="ut-txt">(最多上传{{ max }}只支持.jpgpng 格式)</text></view>
<view class="ui-ls">
<view class="ul-item" v-for="(e, i) in imgTemLs" :key="i">
<image class="ui-pic" @click="previewImg(e)" mode="aspectFill" :src="e"></image>
<image class="ui-close" @click="delImg(i)" mode="aspectFit" src="/subpackage/message/static/images/close.png"></image>
</view>
<view class="ul-item ul-add" @click="chooseImg" v-if="max > imgTemLs.length">
<view class="ua-icon"></view>
<view class="ua-txt">上传照片</view>
</view>
</view>
</view>
</template>
<script>
import { showModal, jsonPar, showLoad, hideLoad, promisify } from "@/utils/util";
import server from "../../js/server";
import { MESSAGE_API } from "../../js/api";
export default {
props: {
max: {
type: Number,
default: 6
}
},
data(){
return {
imgTemLs: [],
}
},
methods: {
chooseImg(){
let { max } = this;
uni.chooseImage({
count: max,
sourceType: ['album', 'camera'],
success: (res) => {
let { imgTemLs, max } = this;
let _curImgNum = imgTemLs?.length ?? 0;
let _temImgNum = res?.tempFilePaths?.length ?? 0;
if((_curImgNum + _temImgNum) > max)return showModal({ content: `最多上传${max}张图片!` });
this.imgTemLs = imgTemLs.concat(res.tempFilePaths);
}
});
},
delImg(idx){
this.imgTemLs.splice(idx, 1);
},
previewImg(url){
uni.previewImage({ urls: this.imgTemLs, current: url });
},
async getUrls(){
try{
let _urls = [];
let { imgTemLs } = this;
showLoad('图片上传中...');
for(let i = 0; i < imgTemLs?.length; i++){
let _url = await this.uploadImg(imgTemLs[i]);
if(_url !== '')_urls.push(_url);
}
if(_urls?.length < imgTemLs?.length){
let _showModal = promisify(showModal);
hideLoad();
if(_urls?.length === 0){
return _showModal({ content: '图片上传失败,请稍后重试' })
.then(_=>Promise.reject('全部图片上传失败'));
}
let _diffVal = imgTemLs?.length - _urls?.length;
return _showModal({
content: `${_diffVal}张图片上传失败`,
showCancel: true,
cancelText: '重试',
confirmText: '继续提交',
})
.then(res=>{
if(res.confirm)return { temps: imgTemLs, urls: _urls };
return Promise.reject(res);
})
}
hideLoad();
return {
temps: imgTemLs,
urls: _urls,
};
}catch(err){
hideLoad();
console.warn('message components edit upload_img getUrls err --->', err)
}
},
async uploadImg(url){
try{
let _imgInfo = await server.uploadFile({
url: MESSAGE_API.zs_message_imgs,
filePath: url,
});
if(_imgInfo.statusCode != 200)throw(_imgInfo);
let _imgRes = jsonPar(_imgInfo.data);
if(_imgRes.code != 0)throw(_imgRes);
return _imgRes?.data?.url ?? '';
}catch(err){
// showModal({
// titile: '',
// content: err?.errMsg ?? err?.message ?? ''
// });
console.warn('message components edit upload_img uploadImg err --->', err)
// return Promise.reject(err);
return '';
}
},
}
}
</script>
<style lang="scss">
.upload-img{
padding: 30upx 18upx;
border-radius: 20upx;
background: #fff;
.ui-title{
padding: 0 10upx;
@include flcw(32upx, 44upx, #333333);
.ut-txt{
font-size: 24upx;
color: #9A9A9D;
}
}
.ui-ls{
margin-top: 24upx;
font-size: 0;
.ul-item{
position: relative;
margin: 0 10upx;
vertical-align: top;
display: inline-block;
width: 200upx;
height: 200upx;
&:nth-of-type(n+4){
margin-top: 24upx;
}
.ui-pic{
width: 100%;
height: 100%;
border-radius: 10upx;
}
.ui-close{
position: absolute;
top: 6upx;
right: 6upx;
width: 40upx;
height: 40upx;
}
}
.ul-add{
padding-top: 36upx;
border: 2upx solid #D8D8D8;
.ua-icon{
position: relative;
margin: 0 auto;
width: 60upx;
height: 60upx;
&::before, &::after{
content: '';
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: block;
width: 100%;
height: 4upx;
background: #D8D8D8;
}
&::after{
transform: translate(-50%, -50%) rotate(90deg);
}
}
.ua-txt{
text-align: center;
margin-top: 28upx;
@include flcw(28upx, 40upx, #9A9A9D);
}
}
}
}
</style>

33
src/subpackage/message/components/fixed_button.vue

@ -0,0 +1,33 @@
<template>
<view class="fixed-button">
<button class="fb-btn" hover-class="hover-active" @click="$emit('click')">新建</button>
</view>
</template>
<script>
export default {
}
</script>
<style lang="scss">
.fixed-button{
position: fixed;
left: 0;
bottom: 0;
width: 100%;
padding: 10upx 24upx;
@include isPd(10upx);
.fb-btn{
@include clearBtn;
text-align: center;
height: 112upx;
background: $mColor;
border-radius: 10upx;
@include flcw(32upx, 112upx, #fff, 500);
}
.hover-active{
opacity: .9;
}
}
</style>

85
src/subpackage/message/components/message_item.vue

@ -0,0 +1,85 @@
<template>
<view class="message-item" @click="$emit('click:item')">
<image class="mi-icon" mode="aspectFit" src="/subpackage/message/static/images/message.png"></image>
<view class="mi-content">
<view class="mc-info">
<view class="mi-top">
<view class="mt-title">{{ title || '-' }}</view>
<view class="mt-tag" v-if="status">{{ status || '-' }}</view>
</view>
<view class="mi-text">{{ content || '-' }}</view>
</view>
<image class="mc-arrow" mode="aspectFit" src="/subpackage/message/static/images/arrow_b2.png"></image>
</view>
</view>
</template>
<script>
export default {
props: {
title: {
type: String,
default: ''
},
status: {
type: String,
default: ''
},
content: {
type: String,
default: ''
}
},
}
</script>
<style lang="scss">
.message-item{
padding: 30upx 24upx;
border-radius: 10upx;
background: #fff;
@include ctf;
.mi-icon{
flex-shrink: 0;
margin-right: 24upx;
width: 108upx;
height: 108upx;
}
.mi-content{
flex-grow: 1;
@include ctf;
.mc-info{
flex-grow: 1;
.mi-top{
@include ctf;
.mt-title{
@include flcw(32upx, 44upx, #1A1A1A);
@include tHide;
}
.mt-tag{
flex-shrink: 0;
margin-left: 20upx;
padding: 4upx 12upx;
border: 2upx solid $mColor;
border-radius: 6upx;
text-align: center;
@include flcw(24upx, 34upx, $mColor);
}
}
.mi-text{
margin-top: 18upx;
@include flcw(28upx, 40upx, #9C9C9F);
@include tHide(2);
}
}
.mc-arrow{
align-self: flex-start;
flex-shrink: 0;
margin-left: 20upx;
margin-top: 8upx;
width: 28upx;
height: 28upx;
}
}
}
</style>

16
src/subpackage/message/js/api.js

@ -0,0 +1,16 @@
import { ORIGIN } from '@/js/api';
export const MESSAGE_API = {
zs_message_imgs:`${ORIGIN}/upload/file/zs_message_imgs`, // 投诉/工单图片
complaintSubmit:`${ORIGIN}/admin/assistant/complaint/submit`, // 投诉提交
complaintList:`${ORIGIN}/admin/assistant/complaint/list`, // 投诉列表
complaintInfo:`${ORIGIN}/admin/assistant/complaint/info`, // 投诉详情
complaintReply:`${ORIGIN}/admin/assistant/complaint/reply`, // 投诉回复
workorderSubmit:`${ORIGIN}/admin/assistant/workorder/submit`, // 工单提交
workorderList:`${ORIGIN}/admin/assistant/workorder/list`, // 工单列表
workorderInfo:`${ORIGIN}/admin/assistant/workorder/info`, // 工单详情
workorderReply:`${ORIGIN}/admin/assistant/workorder/reply`, // 工单回复
}
export default { ORIGIN, MESSAGE_API };

9
src/subpackage/message/js/server.js

@ -0,0 +1,9 @@
import { Server } from '@/js/server';
class _server extends Server {
constructor(props){
super(props)
}
}
export default new _server();

171
src/subpackage/message/pages/complaint/detail.vue

@ -0,0 +1,171 @@
<template>
<view class="complaint-detail">
<view class="top-status">当前状态{{ orderInfo.status || '-' }}</view>
<view class="heigh-space" style="height: 24upx;"></view>
<detail-info
:title="orderInfo.title || '-'"
:type="orderInfo.type || '-'"
:content="orderInfo.content || '-'"
></detail-info>
<block v-if="imgArr.length">
<view class="heigh-space" style="height: 24upx;"></view>
<image-flow :imgs="imgArr"></image-flow>
</block>
<view class="heigh-space" style="height: 24upx;"></view>
<button class="right-btn" @click="replyBtn">回复</button>
<block v-for="(e, i) in commentArr" :key="i">
<view class="heigh-space" style="height: 24upx;"></view>
<answer-item
:name="e.user_name || ''"
:time="e.reply_time || ''"
:content="e.content || ''"
:active-bg="e.is_creator"
></answer-item>
</block>
</view>
</template>
<script>
import detailInfo from "../../components/detail/info.vue";
import imageFlow from "../../components/detail/image_flow.vue";
import answerItem from "../../components/detail/answer_item.vue";
import { routeTo, debounce, showLoad, hideLoad, showModal, showNone } from "@/utils/util.js";
import { MESSAGE_API } from "../../js/api.js";
import server from "../../js/server.js";
export default {
components: {
detailInfo, imageFlow, answerItem
},
computed: {
imgArr(){
let { orderInfo } = this;
let _showimgs = orderInfo?.showimgs ?? [];
return _showimgs;
},
commentArr(){
let { orderInfo } = this;
let _comment = orderInfo?.comment ?? [];
return _comment;
}
},
data(){
return {
orderInfo: {}
}
},
onLoad(options){
this.complaintInfo({
brand_id: options?.brand_id ?? '',
id: options?.id ?? ''
})
},
methods: {
replyBtn(){
let { orderInfo } = this;
showModal({
title: '回复',
showCancel: true,
editable: true,
placeholderText: '请输入回复内容',
success: mRes=>{
console.log('replyBtn mRes --->', mRes);
if(mRes?.confirm && mRes?.content)this.complaintReply({
brand_id: orderInfo?.brand_id ?? '',
id: orderInfo?.id ?? '',
reply_text: mRes?.content ?? ''
})
.then(res =>{
if(res === 'success'){
setTimeout(_=>{
this.complaintInfo({
brand_id: orderInfo?.brand_id ?? '',
id: orderInfo?.id ?? ''
})
}, 1000);
}
})
}
})
},
complaintInfo({ brand_id, id }){
showLoad();
return server.post({
url: MESSAGE_API.complaintInfo,
data: { brand_id: +brand_id, id: +id },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
let _info = _data?.data ?? {};
return this.orderInfo = _info;
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({
title: '提示',
content: err.message || '加载失败!'
})
console.warn('subpackage message pages complaint detail complaintInfo err --->', err);
// return Promise.reject(err);
})
},
complaintReply({ brand_id, id, reply_text }){
showLoad();
return server.post({
url: MESSAGE_API.complaintReply,
data: { brand_id: +brand_id, id: +id, reply_text },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
showNone(_data?.message ?? '回复成功!');
return 'success'
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({
title: '提示',
content: err.message || '操作失败!'
})
console.warn('subpackage message pages complaint detail complaintReply err --->', err);
// return Promise.reject(err);
})
},
},
}
</script>
<style lang="scss">
.complaint-detail{
padding: 24upx;
@include isPd(24upx);
}
.top-status{
padding: 0 24upx;
text-align: center;
border-radius: 10upx;
background: #fff;
@include tHide;
@include flcw(28upx, 84upx, #1A1A1A);
}
.right-btn{
margin-left: auto;
margin-right: 0upx;
width: 192upx;
text-align: center;
border-radius: 10upx;
background: $mColor;
@include flcw(32upx, 88upx, #FFFFFF);
}
</style>

87
src/subpackage/message/pages/complaint/edit.vue

@ -0,0 +1,87 @@
<template>
<view class="complaint-edit">
<info-edit ref="infoEdit"></info-edit>
<view style="height: 24rpx;"></view>
<upload-img ref="uploadImg"></upload-img>
<fixed-button
@click:cancel="cancelBtn"
@click:submit="submitBtn"
></fixed-button>
</view>
</template>
<script>
import infoEdit from "../../components/edit/info_edit.vue";
import uploadImg from "../../components/edit/upload_img.vue";
import fixedButton from "../../components/edit/fixed_button.vue";
import { routeTo, debounce, showLoad, hideLoad, showModal, showNone } from "@/utils/util.js";
import { MESSAGE_API } from "../../js/api.js";
import server from "../../js/server.js";
export default {
components: { infoEdit, uploadImg, fixedButton },
data() {
return {
brand_id: '',
}
},
onLoad(options) {
let _bid = options?.brand_id ?? '';
this.brand_id = _bid;
},
methods: {
cancelBtn: routeTo,
submitBtn: debounce(async function(){
try{
let { brand_id } = this;
let _infoData = await this.$refs?.infoEdit?.getData?.();
let { urls, temps } = await this.$refs?.uploadImg?.getUrls?.();
this.complaintSubmit({
brand_id,
title: _infoData?.title ?? '',
content: _infoData?.content ?? '',
showimgs: urls ?? [],
})
}catch(err){
hideLoad();
console.warn('subpackage message pages complaint edit submitBtn err --->', err);
}
}, 300, true),
//
complaintSubmit({ brand_id, title = '', content = '', showimgs = [] }){
showLoad();
return server.post({
url: MESSAGE_API.complaintSubmit,
data: { brand_id: +brand_id, title, content, showimgs },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
showNone(_data?.message ?? '操作成功!');
setTimeout(routeTo, 1000);
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({
title: '提示',
content: err.message || '操作失败!'
})
console.warn('subpackage message pages complaint edit complaintSubmit err --->', err);
// return Promise.reject(err);
})
},
}
}
</script>
<style lang="scss">
.complaint-edit{
padding: 24upx;
@include isPd(120upx);
}
</style>

105
src/subpackage/message/pages/complaint/list.vue

@ -0,0 +1,105 @@
<template>
<view class="complaint-list">
<view class="cl-ls">
<block v-for="(e, i) in listData" :key="i">
<message-item
:title="e.title"
:status="e.status"
:content="e.content"
@click:item="itemClick(e)"
></message-item>
<view style="height: 24rpx;"></view>
</block>
</view>
<fixed-button @click="clickBtn">新建投诉</fixed-button>
</view>
</template>
<script>
import fixedButton from "../../components/fixed_button.vue";
import messageItem from "../../components/message_item.vue";
import { routeTo, debounce, showLoad, hideLoad, showModal, showNone } from "@/utils/util.js";
import { MESSAGE_API } from "../../js/api.js";
import server from "../../js/server.js";
export default {
components: {
fixedButton,
messageItem,
},
data(){
return {
brand_id: '',
listData: [],
page: 1
}
},
onLoad(options){
let _bid = options?.brand_id ?? '';
this.brand_id = _bid;
this.complaintList({ brand_id: _bid });
},
async onPullDownRefresh(){
let { brand_id } = this;
this.listData = [];
this.page = 1;
await this.complaintList({ brand_id });
uni.stopPullDownRefresh();
},
onReachBottom(){
let { page, brand_id } = this;
this.complaintList({ page: page + 1, brand_id });
},
methods: {
itemClick(e){
let { brand_id } = this;
let _qryStr = `brand_id=${brand_id ?? ''}&id=${e?.id ?? ''}`;
routeTo(`/subpackage/message/pages/complaint/detail?${_qryStr}`, 'nT');
},
clickBtn(){
let { brand_id } = this;
routeTo(`/subpackage/message/pages/complaint/edit?brand_id=${brand_id ?? ''}`, 'nT');
},
complaintList({ brand_id, page = 1, page_size = 20}){
showLoad();
return server.post({
url: MESSAGE_API.complaintList,
data: { brand_id: +brand_id, page, page_size },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
let _ls = _data?.data ?? [];
if(page === 1)return this.listData = _ls;
if(!_ls.length)return showNone('没有更多!');
this.page = page;
this.listData = [ ...this.listData, ..._ls ];
return _ls;
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({
title: '提示',
content: err.message || '加载失败!'
})
console.warn('subpackage message pages complaint list complaintList err --->', err);
// return Promise.reject(err);
})
},
}
}
</script>
<style lang="scss">
.complaint-list{
@include isPd(132upx);
.cl-ls{
padding: 28upx 24upx 0;
}
}
</style>

171
src/subpackage/message/pages/work_order/detail.vue

@ -0,0 +1,171 @@
<template>
<view class="complaint-detail">
<view class="top-status">当前状态{{ orderInfo.status || '-' }}</view>
<view class="heigh-space" style="height: 24upx;"></view>
<detail-info
:title="orderInfo.title || '-'"
:type="orderInfo.type || '-'"
:content="orderInfo.content || '-'"
></detail-info>
<block v-if="imgArr.length">
<view class="heigh-space" style="height: 24upx;"></view>
<image-flow :imgs="imgArr"></image-flow>
</block>
<view class="heigh-space" style="height: 24upx;"></view>
<button class="right-btn" @click="replyBtn">回复</button>
<block v-for="(e, i) in commentArr" :key="i">
<view class="heigh-space" style="height: 24upx;"></view>
<answer-item
:name="e.user_name || ''"
:time="e.reply_time || ''"
:content="e.content || ''"
:active-bg="e.is_creator"
></answer-item>
</block>
</view>
</template>
<script>
import detailInfo from "../../components/detail/info.vue";
import imageFlow from "../../components/detail/image_flow.vue";
import answerItem from "../../components/detail/answer_item.vue";
import { routeTo, debounce, showLoad, hideLoad, showModal, showNone } from "@/utils/util.js";
import { MESSAGE_API } from "../../js/api.js";
import server from "../../js/server.js";
export default {
components: {
detailInfo, imageFlow, answerItem
},
computed: {
imgArr(){
let { orderInfo } = this;
let _showimgs = orderInfo?.showimgs ?? [];
return _showimgs;
},
commentArr(){
let { orderInfo } = this;
let _comment = orderInfo?.comment ?? [];
return _comment;
}
},
data(){
return {
orderInfo: {}
}
},
onLoad(options){
this.workorderInfo({
brand_id: options?.brand_id ?? '',
id: options?.id ?? ''
})
},
methods: {
replyBtn(){
let { orderInfo } = this;
showModal({
title: '回复',
showCancel: true,
editable: true,
placeholderText: '请输入回复内容',
success: mRes=>{
console.log('replyBtn mRes --->', mRes);
if(mRes?.confirm && mRes?.content)this.workorderReply({
brand_id: orderInfo?.brand_id ?? '',
id: orderInfo?.id ?? '',
reply_text: mRes?.content ?? ''
})
.then(res =>{
if(res === 'success'){
setTimeout(_=>{
this.workorderInfo({
brand_id: orderInfo?.brand_id ?? '',
id: orderInfo?.id ?? ''
})
}, 1000);
}
})
}
})
},
workorderInfo({ brand_id, id }){
showLoad();
return server.post({
url: MESSAGE_API.workorderInfo,
data: { brand_id: +brand_id, id: +id },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
let _info = _data?.data ?? {};
return this.orderInfo = _info;
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({
title: '提示',
content: err.message || '加载失败!'
})
console.warn('subpackage message pages work_order detail workorderInfo err --->', err);
// return Promise.reject(err);
})
},
workorderReply({ brand_id, id, reply_text }){
showLoad();
return server.post({
url: MESSAGE_API.workorderReply,
data: { brand_id: +brand_id, id: +id, reply_text },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
showNone(_data?.message ?? '回复成功!');
return 'success'
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({
title: '提示',
content: err.message || '操作失败!'
})
console.warn('subpackage message pages work_order detail workorderReply err --->', err);
// return Promise.reject(err);
})
},
},
}
</script>
<style lang="scss">
.complaint-detail{
padding: 24upx;
@include isPd(24upx);
}
.top-status{
padding: 0 24upx;
text-align: center;
border-radius: 10upx;
background: #fff;
@include tHide;
@include flcw(28upx, 84upx, #1A1A1A);
}
.right-btn{
margin-left: auto;
margin-right: 0upx;
width: 192upx;
text-align: center;
border-radius: 10upx;
background: $mColor;
@include flcw(32upx, 88upx, #FFFFFF);
}
</style>

99
src/subpackage/message/pages/work_order/edit.vue

@ -0,0 +1,99 @@
<template>
<view class="work-order-edit">
<info-edit
ref="infoEdit"
is-stadium
is-type
></info-edit>
<view style="height: 24rpx;"></view>
<upload-img ref="uploadImg"></upload-img>
<fixed-button
@click:cancel="cancelBtn"
@click:submit="submitBtn"
></fixed-button>
</view>
</template>
<script>
import infoEdit from "../../components/edit/info_edit.vue";
import uploadImg from "../../components/edit/upload_img.vue";
import fixedButton from "../../components/edit/fixed_button.vue";
import { routeTo, debounce, showLoad, hideLoad, showModal, showNone } from "@/utils/util.js";
import { MESSAGE_API } from "../../js/api.js";
import server from "../../js/server.js";
export default {
components: { infoEdit, uploadImg, fixedButton },
data() {
return {
brand_id: '',
}
},
onLoad(options) {
let _bid = options?.brand_id ?? '';
this.brand_id = _bid;
this.$nextTick(_=>{
this.$refs?.infoEdit?.initStadiumLs?.({
brand_id: _bid,
stadium_id: +(options?.stadium_id ?? '')
});
})
},
methods: {
cancelBtn: routeTo,
submitBtn: debounce(async function(){
try{
let { brand_id } = this;
let _infoData = await this.$refs?.infoEdit?.getData?.();
let { urls, temps } = await this.$refs?.uploadImg?.getUrls?.();
this.workorderSubmit({
brand_id,
stadium_id: _infoData?.stadium_id ?? '',
type: _infoData?.type ?? '',
title: _infoData?.title ?? '',
content: _infoData?.content ?? '',
showimgs: urls ?? [],
})
}catch(err){
hideLoad();
console.warn('subpackage message pages work order edit submitBtn err --->', err);
}
}, 300, true),
//
workorderSubmit({ brand_id, stadium_id, type, title = '', content = '', showimgs = [] }){
showLoad();
return server.post({
url: MESSAGE_API.workorderSubmit,
data: { brand_id: +brand_id, stadium_id, type, title, content, showimgs },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
showNone(_data?.message ?? '操作成功!');
setTimeout(routeTo, 1000);
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({
title: '提示',
content: err.message || '操作失败!'
})
console.warn('subpackage message pages work order edit workorderSubmit err --->', err);
// return Promise.reject(err);
})
},
}
}
</script>
<style lang="scss">
.work-order-edit{
padding: 24upx;
@include isPd(120upx);
}
</style>

105
src/subpackage/message/pages/work_order/list.vue

@ -0,0 +1,105 @@
<template>
<view class="complaint-list">
<view class="cl-ls">
<block v-for="(e, i) in listData" :key="i">
<message-item
:title="e.title"
:status="e.status"
:content="e.content"
@click:item="itemClick(e)"
></message-item>
<view style="height: 24rpx;"></view>
</block>
</view>
<fixed-button @click="clickBtn">新建工单</fixed-button>
</view>
</template>
<script>
import fixedButton from "../../components/fixed_button.vue";
import messageItem from "../../components/message_item.vue";
import { routeTo, debounce, showLoad, hideLoad, showModal, showNone } from "@/utils/util.js";
import { MESSAGE_API } from "../../js/api.js";
import server from "../../js/server.js";
export default {
components: {
fixedButton,
messageItem,
},
data(){
return {
brand_id: '',
listData: [],
page: 1
}
},
onLoad(options){
let _bid = options?.brand_id ?? '';
this.brand_id = _bid;
this.workorderList({ brand_id: _bid });
},
async onPullDownRefresh(){
let { brand_id } = this;
this.listData = [];
this.page = 1;
await this.workorderList({ brand_id });
uni.stopPullDownRefresh();
},
onReachBottom(){
let { page, brand_id } = this;
this.workorderList({ page: page + 1, brand_id });
},
methods: {
itemClick(e){
let { brand_id } = this;
let _qryStr = `brand_id=${brand_id ?? ''}&id=${e?.id ?? ''}`;
routeTo(`/subpackage/message/pages/work_order/detail?${_qryStr}`, 'nT');
},
clickBtn(){
let { brand_id } = this;
routeTo(`/subpackage/message/pages/work_order/edit?brand_id=${brand_id ?? ''}`, 'nT');
},
workorderList({ brand_id, page = 1, page_size = 20}){
showLoad();
return server.post({
url: MESSAGE_API.workorderList,
data: { brand_id: +brand_id, page, page_size },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
let _ls = _data?.data ?? [];
if(page === 1)return this.listData = _ls;
if(!_ls.length)return showNone('没有更多!');
this.page = page;
this.listData = [ ...this.listData, ..._ls ];
return _ls;
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({
title: '提示',
content: err.message || '加载失败!'
})
console.warn('subpackage message pages work_order list workorderList err --->', err);
// return Promise.reject(err);
})
},
}
}
</script>
<style lang="scss">
.complaint-list{
@include isPd(132upx);
.cl-ls{
padding: 28upx 24upx 0;
}
}
</style>

BIN
src/subpackage/message/static/images/arrow_b2.png

After

Width: 15  |  Height: 15  |  Size: 226 B

BIN
src/subpackage/message/static/images/close.png

After

Width: 40  |  Height: 40  |  Size: 401 B

BIN
src/subpackage/message/static/images/message.png

After

Width: 54  |  Height: 54  |  Size: 959 B

BIN
src/subpackage/message/static/images/unfold.png

After

Width: 28  |  Height: 28  |  Size: 288 B

59
src/subpackage/verification/components/head_bar.vue

@ -0,0 +1,59 @@
<template>
<view class="head-bar">
<picker class="hb-picker" :range="range" :range-key="rangeKey" @change="$emit('change', $event)">
<view class="hp-frame">
<input class="hf-ipt" type="text" disabled :value="value" :placeholder="placeholder">
<image class="hf-icon" mode="aspectFit" src="/subpackage/verification/static/images/arrow_c9f.png"></image>
</view>
</picker>
<image v-if="search" class="hb-icon" mode="aspectFit" src="/subpackage/verification/static/images/search.png" @click="$emit('click:search')"></image>
</view>
</template>
<script>
export default {
props: {
value: { type: String, default: '' },
placeholder: { type: String, default: '' },
range: { type: Array, default: [] },
rangeKey: { type: String, default: '' },
search: { type: Boolean, default: false }
}
}
</script>
<style lang="scss">
.head-bar{
padding-left: 24upx;
padding-right: 24upx;
height: 144upx;
background: #fff;
@include ctf(space-between);
.hb-picker{
flex-grow: 1;
.hp-frame{
padding: 0 24upx;
height: 92upx;
background: #f2f2f7;
@include ctf(space-between);
.hf-ipt{
flex-grow: 1;
height: 100%;
@include flcw(28upx, 40upx, #333);
}
.hf-icon{
flex-shrink: 0;
width: 28upx;
height: 28upx;
}
}
}
.hb-icon{
flex-shrink: 0;
margin-right: 16upx;
margin-left: 26upx;
width: 40upx;
height: 40upx;
}
}
</style>

78
src/subpackage/verification/components/record/dy_item.vue

@ -0,0 +1,78 @@
<template>
<view class="site-item">
<!-- <view class="si-stadium">{{ stadiumName || '-' }}</view> -->
<view class="si-lines">
<view class="sl-item">
<view class="sl-left">订单编号{{ orderNum || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">用户信息{{ userPhone || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">券码 {{ verifyCode || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">验证方式{{ verifyMethod || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">核销时间{{ verifyTime || '-' }}</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: [
// 'stadiumName',
'orderNum',
'userPhone',
'verifyCode',
'verifyMethod',
'verifyTime',
]
}
</script>
<style lang="scss">
.site-item{
position: relative;
padding: 0 20upx;
border-radius: 10upx;
background: #fff;
.si-stadium{
min-height: 100upx;
border-bottom: 2upx solid #D8D8D8;
@include flcw(28upx, 98upx, #1A1A1A, 500);
@include tHide;
}
.si-lines{
padding-top: 8upx;
padding-bottom: 32upx;
.sl-item{
display: flex;
.sl-left{
@include flcw(28upx, 52upx, #9c9c9f);
@include tHide;
}
.sl-right{
max-width: 200upx;
flex-shrink: 0;
margin-left: 10upx;
@include flcw(28upx, 52upx, $mColor);
@include tHide;
}
}
}
.si-btn{
position: absolute;
right: 20upx;
bottom: 32upx;
width: 192upx;
text-align: center;
background-color: $mColor;
border-radius: 10upx;
@include flcw(32upx, 80upx, #fff, 500);
}
}
</style>

95
src/subpackage/verification/components/record/mall_item.vue

@ -0,0 +1,95 @@
<template>
<view class="site-item">
<view class="si-order-num">
<view class="son-txt">订单编号{{ orderNum || '-' }}</view>
<view class="son-copy" @click="copyBtn">复制</view>
</view>
<view class="si-lines">
<view class="sl-item">
<view class="sl-left">取货码{{ orderCode || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">取货人{{ userName || '-' }} {{ userPhone || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">商品{{ goodsStr || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">核验人{{ optUser || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">取货时间{{ createdAt || '-' }}</view>
</view>
</view>
</view>
</template>
<script>
export default {
props: [
'orderNum',
'orderCode',
'userPhone',
'userName',
'goodsStr',
'optUser',
'createdAt',
],
methods: {
copyBtn(){
uni.setClipboardData({ data: this.orderNum });
}
}
}
</script>
<style lang="scss">
.site-item{
position: relative;
padding: 0 20upx;
border-radius: 10upx;
background: #fff;
.si-order-num{
min-height: 100upx;
border-bottom: 2upx solid #D8D8D8;
@include ctf;
.son-txt{
flex-grow: 1;
@include flcw(28upx, 98upx, #1A1A1A, 500);
@include tHide;
}
.son-copy{
padding-right: 20upx;
margin-left: 20upx;
flex-shrink: 0;
@include flcw(28upx, 98upx, $mColor, 500);
}
}
.si-lines{
padding-top: 8upx;
padding-bottom: 32upx;
.sl-item{
display: flex;
.sl-left{
@include flcw(28upx, 52upx, #9c9c9f);
@include tHide;
}
.sl-right{
max-width: 200upx;
flex-shrink: 0;
margin-left: 10upx;
@include flcw(28upx, 52upx, $mColor);
@include tHide;
}
}
}
.si-btn{
position: absolute;
right: 20upx;
bottom: 32upx;
width: 192upx;
text-align: center;
background-color: $mColor;
border-radius: 10upx;
@include flcw(32upx, 80upx, #fff, 500);
}
}
</style>

87
src/subpackage/verification/components/record/site_item.vue

@ -0,0 +1,87 @@
<template>
<view class="site-item">
<view class="si-stadium">{{ stadiumName || '-' }}</view>
<view class="si-lines">
<view class="sl-item">
<view class="sl-left">订单编号{{ orderNum || '-' }}</view>
<view class="sl-right">{{ orderType || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">用户信息{{ userPhone || '-' }}({{ userNickname || '-' }})</view>
</view>
<view class="sl-item">
<view class="sl-left">核销码 {{ verifyCode || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">验证方式{{ verifyMethod || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">核销时间{{ verifyTime || '-' }}</view>
</view>
<view class="sl-item">
<view class="sl-left">离场时间{{ verifyLeaveTime || '-' }}</view>
</view>
</view>
<view class="si-btn" v-if="verifyLeaveTime === ''" @click="$emit('click:leave')">手动离场</view>
</view>
</template>
<script>
export default {
props: [
'stadiumName',
'orderNum',
'orderType',
'userPhone',
'userNickname',
'verifyCode',
'verifyMethod',
'verifyTime',
'verifyLeaveTime',
]
}
</script>
<style lang="scss">
.site-item{
position: relative;
padding: 0 20upx;
border-radius: 10upx;
background: #fff;
.si-stadium{
min-height: 100upx;
border-bottom: 2upx solid #D8D8D8;
@include flcw(28upx, 98upx, #1A1A1A, 500);
@include tHide;
}
.si-lines{
padding-top: 8upx;
padding-bottom: 32upx;
.sl-item{
display: flex;
.sl-left{
@include flcw(28upx, 52upx, #9c9c9f);
@include tHide;
}
.sl-right{
max-width: 200upx;
flex-shrink: 0;
margin-left: 10upx;
@include flcw(28upx, 52upx, $mColor);
@include tHide;
}
}
}
.si-btn{
position: absolute;
right: 20upx;
bottom: 32upx;
width: 192upx;
text-align: center;
background-color: $mColor;
border-radius: 10upx;
@include flcw(32upx, 80upx, #fff, 500);
}
}
</style>

15
src/subpackage/verification/components/stadium_picker.vue

@ -0,0 +1,15 @@
<template>
<view class="stadium-picker">
</view>
</template>
<script>
export default {
}
</script>
<style lang="scss">
</style>

7
src/subpackage/verification/js/api.js

@ -6,6 +6,13 @@ export const SUB_API = {
skNotLeavingNums: `${ORIGIN}/stadium/sk/notLeavingNums`, // 【1001536】散客人数 - 未离场订单数量
setStadiumPresentNumber: `${ORIGIN}/admin/stadium/setStadiumPresentNumber`, // 商家助手散客人数校正
timingOpen: `${ORIGIN}/stadium/person/timing/open`, // 【20220208】凌晨自动清零【开/关】
// tid1803
enterVerifyOrder: `${ORIGIN}/admin/stadium/order/enterVerifyOrder`, // 商家助手-核销查询-输入验证码
listVerifyRecord: `${ORIGIN}/admin/stadium/order/listVerifyRecord`, // 商家助手-核销查询列表
leaveVerifyOrder:`${ORIGIN}/admin/stadium/order/leaveVerifyOrder`, // 【20220208】核销记录列表 - 手动离场
dyPoiOrderList: `${ORIGIN}/admin/douyinPlatformProductOrder/list`, // 商家助手-dypoi订单
shop2WriteoffList: `${ORIGIN}/admin/assistant/shop2/writeoffList`, // 商城订单核销 - 列表
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save