初始化项目

This commit is contained in:
山兮 2024-03-21 13:53:51 +08:00
commit bf8dc07cb7
369 changed files with 67648 additions and 0 deletions

5
.buildpath Normal file
View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?>
<buildpath>
<buildpathentry kind="src" path=""/>
<buildpathentry kind="con" path="org.eclipse.php.core.LANGUAGE"/>
</buildpath>

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/unpackage/

20
.hbuilderx/launch.json Normal file
View File

@ -0,0 +1,20 @@
{ // launch.json configurations app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtypelocalremote, localremote
"version": "0.0",
"configurations": [{
"app-plus" :
{
"launchtype" : "local"
},
"default" :
{
"launchtype" : "local"
},
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}

5
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/

12
.idea/UniappTool.xml Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="cn.fjdmy.uniapp.UniappProjectDataService">
<option name="basePath" value="$PROJECT_DIR$" />
<option name="generalBasePath" value="$PROJECT_DIR$" />
<option name="manifestPath" value="$PROJECT_DIR$/manifest.json" />
<option name="pagesPath" value="$PROJECT_DIR$/pages.json" />
<option name="scanNum" value="1" />
<option name="type" value="store" />
<option name="uniapp" value="true" />
</component>
</project>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
<excludeFolder url="file://$MODULE_DIR$/temp" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

8
.idea/modules.xml Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/dengLan_home_mpWeixin.iml" filepath="$PROJECT_DIR$/.idea/dengLan_home_mpWeixin.iml" />
</modules>
</component>
</project>

22
.project Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>djh5</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.wst.validation.validationbuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.dltk.core.scriptbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.php.core.PHPNature</nature>
</natures>
</projectDescription>

View File

@ -0,0 +1,3 @@
eclipse.preferences.version=1
include_path=0;/djh5
use_asp_tags_as_php=false

381
App.vue Normal file
View File

@ -0,0 +1,381 @@
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
import $api from "@/api/index.js"
import $store from "@/store/index.js"
export default {
data() {
return {
timer: null
}
},
async mounted() {
// #ifdef H5
if (typeof window.entryUrl === 'undefined' || window.entryUrl === '') {
window.entryUrl = window.location.href.split('#')[0]
}
if (window.location.href.indexOf('?#') < 0) {
window.location.href = window.location.href.replace("#", "?#");
}
// #endif
console.log('App mounted')
},
async onLaunch() {
let configInfo = uni.getStorageSync('configInfo') || ''
if (configInfo) {
$store.commit('updateConfigItem', {
key: 'configInfo',
val: configInfo
})
}
let isGzhLogin = uni.getStorageSync('isGzhLogin') || false
$store.commit('updateUserItem', {
key: 'isGzhLogin',
val: isGzhLogin
})
console.log('isGzhLogin',isGzhLogin)
let arr = ['autograph', 'userInfo', 'location', 'appLogin', 'loginType']
arr.map(key => {
let val = uni.getStorageSync(key) || ''
// #ifdef H5
val = isGzhLogin ? val : ''
// #endif
$store.commit('updateUserItem', {
key,
val
})
// #ifdef APP-PLUS
if (key == 'userInfo') {
$store.commit('updateUserItem', {
key: 'isShowLogin',
val: val && val.id ? false : true
})
}
// #endif
})
let {
commonOptions
} = this
let {
coach_id = 0
} = commonOptions
if (coach_id) {
commonOptions.coach_id = 0
$store.commit('updateUserItem', {
key: 'commonOptions',
val: commonOptions
})
}
this.initIndex()
console.log('App onLaunch')
},
async onShow() {
if (this.timer) {
clearInterval(this.timer)
}
let {
coach_position = 0
} = this.userInfo
let {
id: coachId = 0,
status = 0
} = this.coachInfo
if (coach_position == 1) {
if (!coachId) {
await this.getCoachInfo()
}
$store.commit('updateUserItem', {
key: 'locationChange',
val: true
})
}
uni.onNetworkStatusChange((res) => {
let {
isConnected
} = res
if (isConnected) return
let methodArr = ['updateServiceItem', 'updateTechnicianItem']
methodArr.map(item => {
$store.commit(item, {
key: 'pageActive',
val: false
})
})
})
},
async onHide() {
console.log('App Hide')
let {
coach_position = 0
} = this.userInfo
let {
status = 0
} = this.coachInfo
if (!coach_position || status != 2) return
$store.commit('updateUserItem', {
key: 'locationChange',
val: false
})
// #ifdef H5
if (this.$jweixin.isWechat()) {
await this.$jweixin.wxReady2();
this.toHidePage()
this.timer = setInterval(() => {
this.toHidePage()
}, 60000)
}
// #endif
},
onUnload() {
uni.offLocationChange((res) => {
console.log("====offLocationChange onUnload", res)
})
},
watch: {
locationChange(newVal, oldVal) {
if (newVal) {
uni.startLocationUpdate({
complete: msg => console.log(`startLocationUpdate API complete`, msg)
})
} else {
uni.stopLocationUpdate({
complete: msg => console.log(`stopLocationUpdate API complete`, msg)
})
}
},
'configInfo.attendant_name'(newVal, oldVal) {
if (newVal) {
this.mergeLocaleMessage()
}
}
},
computed: mapState({
configInfo: state => state.config.configInfo,
old_attendant_name: state => state.config.old_attendant_name,
commonOptions: state => state.user.commonOptions,
locationChange: state => state.user.locationChange,
locationChangeUnix: state => state.user.locationChangeUnix,
userInfo: state => state.user.userInfo,
coachInfo: state => state.user.coachInfo,
}),
methods: {
...mapActions(['getConfigInfo', 'getUserInfo', 'getCoachInfo']),
async initIndex() {
let that = this
let {
coach_position = 0
} = that.userInfo
uni.onLocationChange((res) => {
console.log("====onLocationChange", res)
let {
latitude: lat,
longitude: lng
} = res
that.toChangeLocation({
lat,
lng
})
})
if (coach_position == 1) {
let {
id: coachId = 0
} = that.coachInfo
if (!coachId) {
await that.getCoachInfo()
}
$store.commit('updateUserItem', {
key: 'locationChange',
val: true
})
}
let {
primaryColor = '',
plugAuth = {},
tabBar = []
} = that.configInfo
let mineInd = tabBar.findIndex(item => {
return item.id == 5
})
console.log(mineInd, "======mineInd")
// if ((primaryColor && plugAuth.length > 0) || mineInd != -1) return
await that.getConfigInfo()
},
async mergeLocaleMessage() {
let zh = JSON.parse(JSON.stringify(this.$i18n.messages.zh))
let {
attendant_name: name,
} = this.configInfo
let oldName = this.old_attendant_name
let reg = new RegExp(oldName, 'g')
for (let i in zh.action) {
if (zh.action[i].includes(oldName)) {
zh.action[i] = zh.action[i].replace(reg, name)
}
}
this.$i18n.mergeLocaleMessage('zh', zh)
$store.commit('updateConfigItem', {
key: 'old_attendant_name',
val: name
})
// console.log(this.$t('action.attendantName'), this.$i18n.messages.zh, "=====");
},
async toHidePage() {
let {
latitude: lat,
longitude: lng
} = await this.$jweixin.getWxLocation()
if (lat && lng) {
let val = this.$util.DateToUnix(this.$util.formatTime(new Date(), 'YY-M-D h:m:s'))
$store.commit('updateUserItem', {
key: 'locationChangeUnix',
val
})
this.toChangeLocation({
lat,
lng
})
}
},
async toChangeLocation(param) {
let {
lat,
lng
} = param
let {
id = 0,
coach_position = 0
} = this.userInfo
let {
lat: coach_lat,
lng: coach_lng,
status = 0
} = this.coachInfo
let {
locationChangeUnix
} = this
let curUnix = this.$util.DateToUnix(this.$util.formatTime(new Date(), 'YY-M-D h:m:s'))
let second = 3
// #ifdef APP-PLUS
second = 1
// #endif
// console.log(curUnix, locationChangeUnix, curUnix - locationChangeUnix,
// "=====curUnix - locationChangeUnix")
let isMin = curUnix - locationChangeUnix <= second || curUnix - locationChangeUnix >= 60
let isSame = coach_lat * 1 == lat && coach_lng * 1 == lng
// console.log(lat, lng, coach_position, isMin, isSame, "=====lat, lng, coach_position, isMin")
if (!coach_position || status != 2 || !isMin || isSame) return
let val = this.$util.DateToUnix(this.$util.formatTime(new Date(), 'YY-M-D h:m:s'))
$store.commit('updateUserItem', {
key: 'locationChangeUnix',
val
})
let key = `${lat},${lng}`
try {
let data = await this.$api.base.getMapInfo({
location: key
})
let {
status,
result
} = JSON.parse(data)
if (status == 0) {
let {
address
} = result
console.log("toChangeLocation==>", `${lat}-${lng}-${address}`)
await this.$api.technician.coachUpdate({
lat,
lng,
address
})
await this.getCoachInfo()
}
} catch (e) {
console.log('=====getMapInfo catch')
uni.stopLocationUpdate({
complete: msg => console.log(`stopLocationUpdate API complete`, msg)
})
}
}
}
}
</script>
<style lang="scss">
@import "/styles/index.wxss";
/* #ifdef H5 */
uni-page-head {
display: none;
}
/* #endif */
page {
font-size: 28rpx;
color: #222;
line-height: 1.5;
background: #F6F6F6;
font-family: -apple-system-font, Helvetica Neue, Helvetica, sans-serif;
}
input {
// font-family: PingFangSC-Medium, PingFang SC, -apple-system-font, Helvetica Neue, Helvetica, sans-serif;
}
input::-webkit-input-placeholder {
/* WebKit browsers */
color: #A9A9A9;
}
input:-moz-placeholder {
/* Mozilla Firefox 4 to 18 */
color: #A9A9A9;
}
input::-moz-placeholder {
/* Mozilla Firefox 19+ */
color: #A9A9A9;
}
input:-ms-input-placeholder {
/* Internet Explorer 10+ */
color: #A9A9A9;
}
view {
box-sizing: border-box;
}
image {
display: block;
}
.h5-image {
background-position: center center;
background-size: cover;
background-repeat: no-repeat;
}
/*隐藏滚动条*/
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
/* #ifdef MP-BAIDU */
.swan-button.swan-button-radius-ios {
border-radius: 0;
}
/* #endif */
</style>

0
README.md Normal file
View File

135
agent/pages/account.vue Normal file
View File

@ -0,0 +1,135 @@
<template>
<view class="agent-account pd-lg" v-if="isLoad">
<view class="pt-md pb-md c-title text-bold" style="font-size:34rpx">账号信息</view>
<view class="flex-warp pb-lg">
<view class="f-paragraph">账号名称</view>
<view class="f-desc">{{detail.username}}</view>
</view>
<view class="flex-warp pb-lg">
<view class="f-paragraph">账号密码</view>
<view class="f-desc">{{detail.passwd_text | handlePassWord}}</view>
</view>
<view class="flex-warp pb-lg">
<view class="f-paragraph">真实姓名</view>
<view class="f-desc">{{detail.agent_name}}</view>
</view>
<view class="flex-warp pt-lg pb-lg b-1px-t">
<view class="f-paragraph">当前等级</view>
<view class="f-desc">{{cityType[detail.city_type]}}代理({{detail.city_name}})</view>
</view>
<!-- / 查下级区县查上级 -->
<view class="flex-warp pb-lg" v-if="detail.city_type != 3">
<view class="f-paragraph">关联上级代理</view>
<view class="f-desc">
{{detail.top_data && detail.top_data.hasOwnProperty('username')?detail.top_data.username:'-'}}
</view>
</view>
<view class="flex-warp pb-lg" v-if="detail.city_type != 2">
<view class="f-paragraph">关联下级代理</view>
<view class="f-desc">
<block v-if="detail.sub_data.length==0">-</block>
<view class="mb-sm" v-for="(item,index) in detail.sub_data" :key="index">{{item.agent_name}}</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from "vuex"
export default {
components: {},
data() {
return {
isLoad: false,
detail: {},
cityType: {
1: '城市',
2: '区县',
3: '省'
},
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
commonOptions: state => state.user.commonOptions,
userInfo: state => state.user.userInfo,
}),
onLoad() {
this.initIndex()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
methods: {
...mapMutations([]),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
this.$util.showLoading()
this.detail = await this.$api.agent.adminInfoData()
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
this.isLoad = true
this.$util.hideAll()
},
initRefresh() {
this.initIndex(true)
},
toJump(key, index) {
let {
url
} = this[key][index]
this.$util.goUrl({
url
})
},
},
filters: {
handlePassWord(val) {
let len = val.length
let text = val.substring(0, len > 6 ? 3 : 1) + '***'
text += val.substring(len == 6 ? len - 2 : len - 3, len)
return text
}
}
}
</script>
<style lang="scss">
page {
background: #fff
}
.agent-account {
.f-paragraph {
width: 200rpx;
color: #4A4A4A;
}
.f-desc {
width: 490rpx;
color: #777777;
}
}
</style>

315
agent/pages/apply.vue Normal file
View File

@ -0,0 +1,315 @@
<template>
<view class="apply-pages" v-if="isLoad">
<view class="page-height" v-if="mineInfo.is_admin">
<abnor percent="150%" @confirm="$util.goUrl({url:`/agent/pages/index?agent=1`,openType: `reLaunch`})"
title="您已经是代理商了" :tip="[{ text: '快去管理订单吧', color: 0 }]" :button="[{ text: '去管理', type: 'confirm' }]"
image="https://lbqny.migugu.com/admin/public/apply_suc.jpg"></abnor>
</view>
<block v-else>
<view class="apply-info-box rel" :style="{background:primaryColor}">
<image class="bg-1 abs" src="https://lbqny.migugu.com/admin/anmo/apply/bg-4.png">
</image>
<image class="bg-2 abs" src="https://lbqny.migugu.com/admin/anmo/apply/bg-3.png">
</image>
<image class="join-us abs" src="https://lbqny.migugu.com/admin/anmo/apply/join-us-1.png">
</image>
<view class="join-us pd-lg abs" style="z-index: 3;">
<view style="height:90rpx"></view>
<view class="f-title c-title text-bold">您的姓名</view>
<input v-model="form.user_name" type="text"
class="item-input fill-base f-mini-title c-title mt-sm pl-lg pr-lg radius-10" maxlength="20"
placeholder-class="c-caption" :placeholder="rule[0].errorMsg"
:style="{borderColor:primaryColor}" />
<view class="mt-md f-title c-title text-bold">代理类型</view>
<view class="flex-y-center mt-sm">
<view @tap.stop="form.city_type = item.id" class="flex-y-center f-mini-title c-title"
:class="[{'mr-lg pr-lg':index!=2}]"
:style="{color:form.city_type == item.id ? primaryColor:''}"
v-for="(item,index) in cityList" :key="index"><i class="iconfont mr-sm"
:class="[{'icon-xuanze':form.city_type != item.id},{'icon-radio-fill':form.city_type == item.id}]"></i>{{item.title}}代理
</view>
</view>
<view class="mt-md f-title c-title text-bold">手机号</view>
<view class="item-input fill-base f-mini-title c-title mt-sm pl-lg pr-lg radius-10"
style="height:auto;min-height:82rpx" :style="{borderColor:primaryColor}">
<input v-model="form.phone" type="text" maxlength="20" placeholder-class="c-caption"
:placeholder="rule[1].errorMsg" style="margin-top: 15rpx;" />
<view class="flex-between" v-if="configInfo.short_code_status">
<input v-model="form.short_code" type="text" maxlength="6" placeholder-class="c-caption"
placeholder="请输入验证码" :style="{borderColor:primaryColor}" />
<view @tap.stop="toSend" :style="{color:primaryColor}">
{{authTime>0?`重新获取(${authTime}s)`:'获取验证码'}}
</view>
</view>
</view>
<view class="mt-md f-title c-title text-bold">申请加入的区域</view>
<input v-model="form.city" type="text"
class="item-input fill-base f-mini-title c-title mt-sm pl-lg pr-lg radius-10" maxlength="50"
placeholder-class="c-caption" :placeholder="rule[2].errorMsg"
:style="{borderColor:primaryColor}" />
</view>
<image @tap.stop="submit" class="submit abs"
src="https://lbqny.migugu.com/admin/anmo/apply/submit-1.png">
</image>
</view>
</block>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
components: {},
data() {
return {
isLoad: false,
options: {},
authTime: 0,
timer: null,
cityList: [{
id: 3,
title: '省'
}, {
id: 1,
title: '城市'
}, {
id: 2,
title: '区县'
}],
form: {
user_name: '', //
city_type: 3,
phone: '', //
short_code: '',
city: ''
},
rule: [{
name: "user_name",
checkType: "isNotNull",
errorMsg: "输入您的姓名",
regType: 2
},
{
name: "phone",
checkType: "isMobile",
errorMsg: "输入手机号"
},
{
name: "city",
checkType: "isNotNull",
errorMsg: "请输入您想代理的区域"
}
],
lockTap: false
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
mineInfo: state => state.user.mineInfo,
}),
async onLoad(options) {
this.options = options
this.$util.showLoading()
await this.initIndex()
},
methods: {
...mapActions(['getConfigInfo', 'getMineInfo']),
...mapMutations(['updateUserItem']),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
if (!this.configInfo.id || !this.configInfo.hasOwnProperty(
'plugAuth') || (this.configInfo.hasOwnProperty(
'plugAuth') && !this.configInfo.plugAuth.hasOwnProperty(
'store')) || refresh) {
await this.getConfigInfo()
}
await this.getMineInfo()
let {
is_admin = 0
} = this.mineInfo
uni.setNavigationBarTitle({
title: is_admin ? '' : '招商加盟'
})
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
this.$util.hideAll()
this.isLoad = true
},
initRefresh() {
this.initIndex(true)
},
//
async toSend() {
let {
authTime
} = this
if (authTime) return
let {
phone = ''
} = this.form
if (phone == null || !/^(1[0-9]{10})$/.test(phone)) {
this.$util.showToast({
title: phone == null ? `请输入手机号` : `${phone} 手机号无效`
});
return;
}
if (this.lockTap) return
this.lockTap = true
this.$util.showLoading()
try {
await this.$api.user.sendShortMsg({
phone
})
this.$util.hideAll()
this.lockTap = false
let time = 60
this.timer = setInterval(() => {
if (time === 0) {
clearInterval(this.timer)
return
}
time--
this.authTime = time
}, 1000)
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
}
},
//
validate(param) {
let validate = new this.$util.Validate();
this.rule.map(item => {
let {
name,
} = item
validate.add(param[name], item);
})
let message = validate.start();
return message;
},
async submit() {
let param = this.$util.deepCopy(this.form)
let msg = this.validate(param);
if (msg) {
this.$util.showToast({
title: msg
});
return;
}
let {
short_code_status
} = this.configInfo
if (short_code_status && (param.short_code == null || param.short_code.length != 6)) {
this.$util.showToast({
title: `请输入6位数短信验证码`
})
return
}
if (!short_code_status) {
delete param.short_code
}
if (this.lockTap) return
this.lockTap = true
this.$util.showLoading()
try {
await this.$api.agent.agentApply(param)
this.$util.hideAll()
this.$util.showToast({
title: `提交成功,即将跳转个人中心`,
})
if (this.timer) {
clearInterval(this.timer)
}
setTimeout(() => {
if (getCurrentPages().length > 1) {
this.$util.back()
}
this.$util.goUrl({
url: '/pages/mine',
openType: `reLaunch`
})
}, 2000)
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
}
}
}
}
</script>
<style lang="scss">
.apply-info-box {
width: 100%;
height: 1632rpx;
.bg-1 {
width: 750rpx;
height: 1632rpx;
top: 0;
left: 0;
z-index: 1;
}
.bg-2 {
width: 750rpx;
height: 575rpx;
top: 0;
left: 0;
z-index: 3;
}
.join-us {
width: 671rpx;
height: 890rpx;
top: 482rpx;
left: 44rpx;
z-index: 2;
.item-input {
height: 74rpx;
border: 4rpx solid #069F5E;
.flex-between {
height: 74rpx;
}
}
.item-input.text {
height: 82rpx;
}
}
.submit {
width: 662rpx;
height: 93rpx;
top: 1400rpx;
left: 47rpx;
z-index: 2;
}
}
</style>

View File

@ -0,0 +1,576 @@
<template>
<view class="agent-income-commission" v-if="isLoad">
<fixed @height="initFixHeight" zIndex="997">
<view class="record-search-info fill-body pt-md pl-md c-base">
<view class="record-info radius-32" :style="{background:primaryColor}">
<view class="search-item flex-between ml-lg mr-lg f-desc b-1px-b">
<view>查询时间</view>
<view @tap.stop="toShowTimePopup($event)" class="flex-y-center">
{{ prev_time | handleTimeText(1) }}<i class="iconfont icon-right"></i>
</view>
</view>
<view class="search-item flex-between ml-lg mr-lg f-desc">
<view>入账状态</view>
<view @tap.stop="$refs.show_status_item.open()" class="flex-y-center">
{{typeList[typeIndex].title}}<i class="iconfont icon-right"></i>
</view>
</view>
</view>
</view>
</fixed>
<block v-for="(item,index) in list.data" :key="index">
<view @tap.stop="toShowTimePopup($event,item.month)" class="count-item fill-body pl-md pr-md"
v-if="prev_time.activeIndex==0 && (index==0 || (index>0&&item.month!=list.data[index-1].month))">
<veiw class="title flex-y-center">
<view class="f-title c-title text-bold">{{item.month}}</view>
<i class="iconfont iconxia"></i>
</veiw>
<view class="f-caption c-caption">获得总提成¥{{item.total_cash}}{{cityType[item.my_city_type]}}代理
</view>
</view>
<view @tap.stop="toShowTimePopup($event,item.month)" class="count-item fill-body pl-md pr-md"
v-if="prev_time.activeIndex==1 && index==0">
<veiw class="title flex-y-center">
<view class="f-title c-title text-bold">{{prev_time | handleTimeText(2)}}</view>
<i class="iconfont iconxia"></i>
</veiw>
<view class="f-caption c-caption">获得总提成¥{{item.total_cash}}{{cityType[item.my_city_type]}}代理
</view>
</view>
<view class="list-item ml-md mr-md fill-base radius-16" :class="[{'mt-md':index!==0}]">
<view class="flex-between pt-lg pb-lg ml-lg mr-lg b-1px-b">
<view class="flex-y-center f-caption c-caption">{{$t('action.attendantName')}}
<view class="f-paragraph c-title text-bold ml-md max-380 ellipsis">
{{item.coach_info.coach_name}}
</view>
</view>
<view class="f-paragraph text-bold" :style="{color:item.status==1?subColor:primaryColor}">
{{statusType[item.have_tx]}}
</view>
</view>
<view class="pd-lg f-caption">
<view class="flex-warp">
<view class="item-text c-paragraph">创建时间: </view>
<view class="c-title">{{item.create_time}}</view>
</view>
<view class="flex-warp mt-md">
<view class="item-text c-paragraph">服务时间: </view>
<view class="c-title">{{item.start_time}}</view>
</view>
<view class="flex-warp mt-md">
<view class="item-text c-paragraph">项目: </view>
<view class="c-title" style="max-width:410rpx">
<view :class="[{'mt-md':aindex!=0}]" v-for="(aitem,aindex) in item.order_goods"
:key="aindex">
{{aitem.goods_name}}
</view>
</view>
</view>
<view class="flex-warp mt-md">
<view class="item-text c-paragraph">订单实际支付价格: </view>
<view class="flex-y-center c-title">
<view class="mr-sm">¥{{item.pay_price}}</view>
<block v-if="item.true_car_price*1>0">(含车费¥{{item.true_car_price}})</block>
</view>
</view>
<view class="flex-warp mt-lg pt-md">
<view class="item-text c-paragraph">{{$t('action.attendantName')}}分成: </view>
<view class="c-title" :style="{color:primaryColor}">¥{{item.coach_cash}}</view>
</view>
<view class="flex-warp mt-md" v-for="(aitem,aindex) in item.admin_cash_list" :key="aindex">
<view class="item-text c-paragraph">{{cityType[aitem.city_type]}}代理分成: </view>
<view class="c-title" :style="{color:primaryColor}">¥{{aitem.cash}}</view>
</view>
</view>
</view>
</block>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading" v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
<view class="space-footer"></view>
<uni-popup ref="show_choose_time" type="bottom">
<view class="popup-choose-time fill-base">
<view class="pl-lg pr-lg">
<view class="flex-between b-1px-b">
<tab @change="handerTabChange" :list="tabList" :activeIndex="activeIndex*1"
:activeColor="primaryColor" height="100rpx"></tab>
<i @tap.stop="toClose" class="iconfont icon-close"></i>
</view>
<view class="flex-center mt-md pt-lg pb-lg">
<view class="flex-1 flex-y-baseline">
<view class="f-paragraph c-title text-bold">{{activeIndex==0?'选择月份':'自定义时间'}}</view>
<view class="f-caption c-warning ml-sm" v-if="activeIndex==1">最长可查找时间跨度一年的交易</view>
</view>
<view class="f-paragraph" @tap.stop="toReset" style="color: #5A677E;"
v-if="(activeIndex==0&&check_time.month) || (activeIndex==1&&(check_time.start_time || check_time.end_time))">
清除</view>
</view>
<view class="flex-center pb-lg" v-if="activeIndex==0">
<view @tap.stop="toShowTime('month')" class="item-child flex-center flex-column">
<view>开始月份</view>
<view class="mt-sm" :style="{color:check_time.month ? primaryColor : '#999'}">
{{check_time.month || '选择月份'}}
</view>
</view>
</view>
<view class="flex-center pb-lg" v-if="activeIndex==1">
<view @tap.stop="toShowTime('start_time')" class="item-child flex-center flex-column">
<view>开始时间</view>
<view class="mt-sm" :style="{color:check_time.start_time ? primaryColor : '#999'}">
{{check_time.start_time || '选择时间'}}
</view>
</view>
<view @tap.stop="toShowTime('end_time')" class="item-child flex-center flex-column b-1px-l">
<view>结束时间</view>
<view class="mt-sm" :style="{color:check_time.end_time ? primaryColor : '#999'}">
{{check_time.end_time || '选择时间'}}
</view>
</view>
</view>
</view>
<view class="flex-center flex-column fill-body">
<view class="space-lg"></view>
<view @tap.stop="toConfirm" class="confirm-btn flex-center f-title c-base radius-16"
:style="{background: primaryColor}">确定</view>
<view class="space-lg"></view>
<view class="space-safe"></view>
</view>
</view>
</uni-popup>
<w-picker mode="date" :startYear="startYear*1-10" :endYear="startYear"
:value="activeIndex == 0 ?curMonth:curDay" :current="false" :fields="activeIndex == 0 ?'month':'day'"
@confirm="onConfirm($event)" :disabled-after="false" ref="day" :themeColor="primaryColor"
:visible.sync="showDate">
</w-picker>
<uni-popup type="bottom" ref="show_status_item" :custom="true">
<view class="popup-status pd-lg fill-base">
<view @tap.stop="handerTypeChange(index)" class="flex-center f-paragraph mb-lg"
:class="[{'mt-lg':index==0}]" :style="{color:typeIndex==index?primaryColor:''}"
v-for="(item,index) in typeList" :key="index">
{{item.title}}
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from "vuex"
import $util from "@/utils/index.js"
import wPicker from "@/components/w-picker/w-picker.vue";
export default {
components: {
wPicker
},
data() {
return {
isLoad: false,
popupHeight: '',
curDay: '',
curMonth: '',
startYear: '',
showKey: '',
showDate: false,
time_text: '',
prev_time: {
activeIndex: 0,
month: '',
start_time: '',
end_time: ''
},
check_time: {
activeIndex: 0,
month: '',
start_time: '',
end_time: ''
},
activeIndex: 0,
tabList: [{
id: 1,
title: '月份选择'
}, {
id: 2,
title: '自定义时间'
}],
typeIndex: 0,
typeList: [{
title: '全部',
id: -1
}, {
title: '未到账',
id: 0,
}, {
title: '已到账',
id: 1
}],
statusType: {
0: '未到账',
1: '已到账'
},
cityType: ['', '城市', '区县', '省'],
param: {
page: 1,
start_time: '',
end_time: ''
},
list: {
data: []
},
loading: true,
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
}),
async onLoad() {
this.$util.showLoading()
let cur_time = new Date(Math.ceil(new Date().getTime()))
this.curDay = this.$util.formatTime(cur_time, 'YY-M-D')
this.curMonth = this.$util.formatTime(cur_time, 'YY-M')
this.startYear = this.$util.formatTime(cur_time, 'YY')
await this.initIndex()
this.isLoad = true
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
this.getList();
},
methods: {
...mapMutations([]),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
await this.getList()
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
},
initRefresh() {
this.param.page = 1
this.initIndex(true)
},
async getList(page) {
if (page) {
this.param.page = 1
this.list.data = []
uni.pageScrollTo({
scrollTop: 0
})
}
let {
list: oldList,
param,
activeIndex,
typeIndex,
typeList
} = this
let {
id
} = typeList[typeIndex]
if (id != -1) {
param.have_tx = id
}
let {
month,
start_time,
end_time
} = this.$util.deepCopy(this.check_time)
if (activeIndex == 0) {
param.month = month ? this.$util.DateToUnix(`${month}-01`) : ''
}
if (activeIndex == 1) {
param.start_time = start_time && end_time ? this.$util.DateToUnix(start_time) : ''
param.end_time = start_time && end_time ? this.$util.DateToUnix(end_time) + 24 * 3600 - 1 : ''
}
let newList = await this.$api.agent.commList(param);
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
this.loading = false
this.$util.hideAll()
},
initFixHeight(val) {
this.popupHeight = val
},
handerTabChange(index) {
this.activeIndex = index
this.check_time.activeIndex = index
},
handerTypeChange(index) {
this.typeIndex = index
this.getList(1)
this.$refs.show_status_item.close()
},
toChangeType(index) {
this.active = index
},
toShowTimePopup(e, month = 0) {
this.check_time = this.$util.deepCopy(this.prev_time)
if (month && month != 0) {
this.activeIndex = 0
this.curMonth = month
this.check_time.month = month
}
this.activeIndex = this.check_time.activeIndex
this.$refs.show_choose_time.open()
},
toReset() {
let {
activeIndex
} = this
if (activeIndex == 0) {
this.check_time.month = ''
return
}
this.check_time.start_time = ''
this.check_time.end_time = ''
this.getList(1)
},
async toClose() {
let {
activeIndex
} = this
let {
month,
start_time,
end_time
} = this.check_time
if ((activeIndex == 0 && !month) || (activeIndex == 1 && (!start_time || !end_time))) {
if (activeIndex == 1 && (!start_time || !end_time)) {
this.activeIndex = 0
this.check_time.activeIndex = 0
this.check_time.month = ''
}
this.prev_time = this.$util.deepCopy(this.check_time)
}
if ((activeIndex == 0 && month) || (activeIndex == 1 && (start_time && end_time))) {
this.check_time = this.$util.deepCopy(this.prev_time)
}
this.$refs.show_choose_time.close()
this.getList(1)
},
toShowTime(key) {
let {
activeIndex
} = this
if (activeIndex == 1 && key == 'end_time' && !this.check_time.start_time) {
this.$util.showToast({
title: `请选择开始时间`
})
return
}
let showTime = this.check_time[key]
if (showTime) {
if (key == 'month') {
this.curMonth = showTime
} else {
this.curDay = showTime
}
}
this.showKey = key
this.showDate = true
},
async onConfirm(val) {
let {
start_time,
end_time
} = this.check_time
let {
showKey,
activeIndex = 0
} = this
let show_unit = this.$util.DateToUnix(showKey == 'month' ? `${val.result}-01` : val.result)
let start_unit = start_time ? this.$util.DateToUnix(start_time) : 0
let end_unit = end_time ? this.$util.DateToUnix(end_time) : 0
let cur_month = this.$util.formatTime(new Date(Math.ceil(new Date().getTime())), 'YY-M-D')
let cur_unit = this.$util.DateToUnix(cur_month) + 1
let msgType = {
month: '开始月份',
start_time: '开始时间',
end_time: '结束时间',
}
if (show_unit > cur_unit) {
this.$util.showToast({
title: `${msgType[showKey]}不能选择未来时间哦`
})
return
}
if (activeIndex == 1 && ((showKey == 'start_time' && end_unit && end_unit < this.$util.DateToUnix(val
.result)) ||
(showKey == 'end_time' && start_unit && start_unit > this.$util.DateToUnix(val.result)))) {
this.$util.showToast({
title: `结束时间不能小于开始时间`
})
return
}
this.check_time[showKey] = val.result
},
async toConfirm() {
let {
month = '',
start_time = '',
end_time = ''
} = this.check_time
let {
activeIndex = 0
} = this
let start_unit = this.$util.DateToUnix(start_time) * 1000
let end_unit = this.$util.DateToUnix(end_time) * 1000
if (activeIndex == 0 && !month) {
this.$util.showToast({
title: `请选择开始月份`
})
return
}
if (activeIndex == 1 && (!start_time || !end_time || (end_unit -
start_unit > 365 * 24 * 3600 * 1000))) {
this.$util.showToast({
title: !start_time ? `请选择开始时间` : !end_time ? `请选择结束时间` : `查询时间跨度最长为一年哦`
})
return
}
this.check_time.activeIndex = activeIndex
this.prev_time = this.$util.deepCopy(this.check_time)
this.$refs.show_choose_time.close()
if (activeIndex == 1) {
await this.getCount()
}
this.getList(1)
}
},
filters: {
handleTimeText(val, type) {
let text = '请选择'
let {
activeIndex,
month,
start_time,
end_time
} = val
if (activeIndex == 0 && month) {
text = $util.formatTime($util.DateToUnix(`${month}-01`) * 1000, 'YY年M月')
}
let formatType = type == 1 ? 'YY.M.D' : 'YY年M月D日'
if (activeIndex == 1 && start_time && end_time) {
text = $util.formatTime($util.DateToUnix(start_time) * 1000, formatType) + ' - ' +
$util.formatTime($util.DateToUnix(end_time) * 1000, formatType)
}
return text
}
}
}
</script>
<style lang="scss">
.agent-income-commission {
.record-search-info {
width: 750rpx;
// height: 200rpx;
.record-info {
width: 710rpx;
.search-item {
height: 100rpx;
.text {
color: #3D2C1B;
}
.iconfont {
font-size: 26rpx;
}
}
}
}
.count-item {
height: 120rpx;
.title {
padding-top: 18rpx;
.iconfont {
font-size: 16rpx;
}
}
}
.list-item {
.item-text {
width: 240rpx;
}
}
.popup-choose-time {
width: 750rpx;
border-radius: 30rpx 30rpx 0 0;
.icon-close {
color: #A8AEB8;
font-size: 40rpx;
}
.item-child {
width: 50%;
}
.confirm-btn {
width: 670rpx;
height: 100rpx;
}
}
.popup-status {
width: 750rpx;
height: 360rpx;
border-radius: 30rpx 30rpx 0 0;
}
}
</style>

653
agent/pages/index.vue Normal file
View File

@ -0,0 +1,653 @@
<template>
<view class="agent-index" v-if="isLoad">
<block v-if="options.agent">
<view class="mine-count-list c-base pd-lg radius-16" :style="{background:primaryColor}">
<view class="flex-between">
<view>
<view class="text flex-center f-caption">可提现()</view>
<view class="f-sm-title">{{detail.cash}} </view>
</view>
<view @tap.stop="$util.goUrl({url:`/user/pages/cash-out?type=agent`})"
class="cash-out-btn fill-base flex-center f-desc text-bold radius"
:style="{color: primaryColor}">
我要提现
</view>
</view>
<view class="count-data-list flex-x-center mt-lg pt-lg b-1px-t">
<view class="list-item flex-center flex-column">
<view class="f-sm-title">{{detail.total_cash}}</view>
<view class="text f-caption c-caption">总金额 ()</view>
</view>
<view class="list-item flex-center flex-column">
<view class="f-sm-title">{{detail.unrecorded_cash}}</view>
<view @tap.stop="$refs.show_rule_item.open()" class="text flex-center f-caption c-caption">未入账
()<i class="iconfont iconshuyi_shuoming ml-sm"></i></view>
</view>
</view>
</view>
<view @tap.stop="$util.goUrl({url:`/user/pages/distribution/bind-technician`})"
class="mine-menu-list share flex-center fill-base radius-16">
<view class="icon-info flex-center">
<i class="iconfont iconjishi1" :style="{color:primaryColor}"></i>
</view>
<view class="flex-1">
<view class="f-mini-title c-title text-bold">绑定{{$t('action.attendantName')}}</view>
<view class="f-desc c-caption mt-sm ellipsis">整合自己的{{$t('action.attendantName')}}资源,获取分润</view>
</view>
<view class="share-btn flex-center f-desc c-base text-bold" :style="{background:primaryColor}">邀请Ta
</view>
</view>
<view @tap.stop="$util.goUrl({url:`/agent/pages/poster/salesman`})"
class="mine-menu-list share flex-center fill-base radius-16" v-if="detail.salesman_auth">
<view class="icon-info flex-center">
<i class="iconfont iconbangdingyewuyuan" :style="{color:primaryColor}"></i>
</view>
<view class="flex-1">
<view class="f-mini-title c-title text-bold">绑定业务员</view>
<view class="f-desc c-caption mt-sm ellipsis">招揽人才,为自己拓宽渠道</view>
</view>
<view class="share-btn flex-center f-desc c-base text-bold" :style="{background:primaryColor}">邀请Ta
</view>
</view>
<view @tap.stop="$util.goUrl({url:`/agent/pages/poster/channel`})"
class="mine-menu-list share flex-center fill-base radius-16" v-if="detail.channel_auth">
<view class="icon-info flex-center">
<i class="iconfont iconbangdingqudaoshang" :style="{color:primaryColor}"></i>
</view>
<view class="flex-1">
<view class="f-mini-title c-title text-bold">绑定渠道商</view>
<view class="f-desc c-caption mt-sm ellipsis">直招渠道,获利更多</view>
</view>
<view class="share-btn flex-center f-desc c-base text-bold" :style="{background:primaryColor}">邀请Ta
</view>
</view>
</block>
<view class="mine-menu-list fill-base radius-16">
<view class="menu-title flex-between pl-lg pr-sm">
<view class="f-paragraph c-title text-bold">智能助手</view>
</view>
<view @tap.stop="goOrder(index)" class="notice-info flex-center pl-lg pr-md pb-lg"
v-for="(item,index) in detail.notice" :key="index">
<view class="title flex-center f-icontext c-base mr-md"
:style="{background:item.type==1?primaryColor: item.type==2?'#FFA229':'#E82F21'}">
{{noticeType[item.type].title}}
</view>
<view class="flex-1 flex-between f-desc">
<view class="c-title">{{item.id ? noticeType[item.type].text : '暂无数据'}}</view>
<view class="flex-y-center c-caption" v-if="item.id">点击查看<i class="iconfont icon-right"></i></view>
</view>
</view>
</view>
<view class="mine-menu-list box-shadow fill-base radius-16" v-if="detail.node.includes('shopOrder')">
<view class="menu-title flex-between pl-lg pr-sm">
<view class="f-paragraph c-title text-bold">订单管理</view>
</view>
<view class="flex-warp pb-lg">
<view @tap.stop="toJump('shopOrder', index)"
class="item-child flex-center flex-column f-caption c-title" v-for="(item, index) in shopOrder"
:key="index">
<view class="item-img rel flex-center radius">
<view class="abs dot-unread-number flex-center"
:style="{width: item.number>99 ? '44rpx': item.number > 9 ? '34rpx' :'',right: item.number>99 ? '-32rpx': item.number > 9 ? '-22rpx' :'-12rpx'}"
v-if="item.number > 0">
{{item.number < 100 ? item.number : '99+'}}
</view>
<view class="item-img radius abs" :style="{background:primaryColor}"></view>
<i class="iconfont c-title" :class="item.icon" :style="{color:primaryColor}"></i>
</view>
<view class="mt-sm">{{ item.text }}</view>
</view>
</view>
</view>
<view class="mine-menu-list box-shadow fill-base radius-16" v-if="detail.node.includes('shopBellOrder')">
<view class="menu-title flex-between pl-lg pr-sm">
<view class="f-paragraph c-title text-bold">加钟管理</view>
</view>
<view class="flex-warp pb-lg">
<view @tap.stop="toJump('shopBellOrder', index)"
class="item-child flex-center flex-column f-caption c-title" v-for="(item, index) in shopBellOrder"
:key="index">
<view class="item-img rel flex-center radius">
<view class="abs dot-unread-number flex-center"
:style="{width: item.number>99 ? '44rpx': item.number > 9 ? '34rpx' :'',right: item.number>99 ? '-32rpx': item.number > 9 ? '-22rpx' :'-12rpx'}"
v-if="item.number > 0">
{{item.number < 100 ? item.number : '99+'}}
</view>
<view class="item-img radius abs" :style="{background:primaryColor}"></view>
<image mode="heightFix" class="icon-img" :src="item.icon"></image>
</view>
<view class="mt-sm">{{ item.text }}</view>
</view>
</view>
</view>
<view class="mine-menu-list box-shadow fill-base radius-16" v-if="detail.node.includes('shopRefuseOrder')">
<view class="menu-title flex-between pl-lg pr-sm">
<view class="f-paragraph c-title text-bold">拒单管理</view>
</view>
<view class="flex-warp pb-lg">
<view @tap.stop="toJump('shopRefuseOrder', index)"
class="item-child flex-center flex-column f-caption c-title"
v-for="(item, index) in shopRefuseOrder" :key="index">
<view class="item-img rel flex-center radius">
<view class="abs dot-unread-number flex-center"
:style="{width: item.number>99 ? '44rpx': item.number > 9 ? '34rpx' :'',right: item.number>99 ? '-32rpx': item.number > 9 ? '-22rpx' :'-12rpx'}"
v-if="item.number > 0">
{{item.number < 100 ? item.number : '99+'}}
</view>
<view class="item-img radius abs" :style="{background:primaryColor}"></view>
<i class="iconfont c-title" :class="item.icon" :style="{color:primaryColor}"></i>
</view>
<view class="mt-sm">{{ item.text }}</view>
</view>
</view>
</view>
<view class="mine-menu-list box-shadow fill-base radius-16" v-if="detail.node.includes('shopRefund')">
<view class="menu-title flex-between pl-lg pr-sm">
<view class="f-paragraph c-title text-bold">服务退款</view>
</view>
<view class="flex-warp pb-lg">
<view @tap.stop="toJump('shopRefund', index)"
class="item-child flex-center flex-column f-caption c-title" v-for="(item, index) in shopRefund"
:key="index">
<view class="item-img rel flex-center radius">
<view class="abs dot-unread-number flex-center"
:style="{width: item.number>99 ? '44rpx': item.number > 9 ? '34rpx' :'',right: item.number>99 ? '-32rpx': item.number > 9 ? '-22rpx' :'-12rpx'}"
v-if="item.number > 0">
{{item.number < 100 ? item.number : '99+'}}
</view>
<view class="item-img radius abs" :style="{background:primaryColor}"></view>
<i class="iconfont c-title" :class="item.icon" :style="{color:primaryColor}"></i>
</view>
<view class="mt-sm">{{ item.text }}</view>
</view>
</view>
</view>
<view class="mine-menu-list box-shadow fill-base radius-16" v-if="detail.node.includes('shopBellRefund')">
<view class="menu-title flex-between pl-lg pr-sm">
<view class="f-paragraph c-title text-bold">加钟退款</view>
</view>
<view class="flex-warp pb-lg">
<view @tap.stop="toJump('shopBellRefund', index)"
class="item-child flex-center flex-column f-caption c-title" v-for="(item, index) in shopBellRefund"
:key="index">
<view class="item-img rel flex-center radius">
<view class="abs dot-unread-number flex-center"
:style="{width: item.number>99 ? '44rpx': item.number > 9 ? '34rpx' :'',right: item.number>99 ? '-32rpx': item.number > 9 ? '-22rpx' :'-12rpx'}"
v-if="item.number > 0">
{{item.number < 100 ? item.number : '99+'}}
</view>
<view class="item-img radius abs" :style="{background:primaryColor}"></view>
<image mode="heightFix" class="icon-img" :src="item.icon"></image>
</view>
<view class="mt-sm">{{ item.text }}</view>
</view>
</view>
</view>
<view class="mine-menu-list fill-base radius-16" v-if="options.agent">
<view class="menu-title flex-between pl-lg pr-sm">
<view class="f-paragraph c-title text-bold">其他功能</view>
</view>
<view class="flex-warp pb-sm">
<view @tap.stop="toJump('toolList', index)" class="item-child flex-center flex-column f-caption c-title"
style="width: 25%;margin:10rpx 0 20rpx 0" v-for="(item, index) in toolList" :key="index">
<i class="iconfont c-title" :class="item.icon" :style="{color:primaryColor}"></i>
<view class="mt-sm">{{ item.text }}</view>
</view>
</view>
</view>
<view class="space-footer"></view>
<uni-popup ref="show_rule_item" type="center" :maskClick="false">
<view class="common-popup-content fill-base pd-lg radius-34">
<view class="title">未入账</view>
<view class="f-desc c-title mt-lg">
平台未到账的服务订单金额
</view>
<view class="button">
<view @tap.stop="$refs.show_rule_item.close()" class="item-child c-base"
:style="{background: primaryColor,color:'#fff'}">知道了</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from "vuex"
import tabbar from "@/components/tabbar.vue"
export default {
components: {
tabbar
},
data() {
return {
isLoad: false,
options: {},
detail: {},
noticeType: {
1: {
title: '订单通知',
text: '您有新的订单来啦!'
},
2: {
title: '退款通知',
text: '您有新的客户退款通知!'
},
3: {
title: '拒单通知',
text: '有' + this.$t('action.attendantName') + '拒单, 请尽快处理!'
},
},
shopOrder: [{
icon: 'icondaifuwu4',
text: '待服务',
url: '/agent/pages/order/list?tab=1',
key: 2,
number: 0
}, {
icon: 'iconjishijiedan1',
text: this.$t('action.attendantName') + '接单',
url: '/agent/pages/order/list?tab=2',
key: 3,
number: 0
}, {
icon: 'iconjishichufa1',
text: this.$t('action.attendantName') + '出发',
url: '/agent/pages/order/list?tab=3',
key: 4,
number: 0
}, {
icon: 'iconjishidaoda1',
text: this.$t('action.attendantName') + '到达',
url: '/agent/pages/order/list?tab=4',
key: 5,
number: 0
}, {
icon: 'iconanmo2',
text: '服务中',
url: '/agent/pages/order/list?tab=5',
key: 6,
number: 0
}, {
icon: 'iconyiwancheng',
text: '已完成',
url: '/agent/pages/order/list?tab=6'
}],
shopBellOrder: [{
icon: 'https://lbqny.migugu.com/admin/anmo/menu/daifuwu.png',
text: '待服务',
url: '/agent/pages/order/list?bell=1&tab=1',
key: 2,
number: 0
}, {
icon: 'https://lbqny.migugu.com/admin/anmo/menu/jiedan.png',
text: this.$t('action.attendantName') + '接单',
url: '/agent/pages/order/list?bell=1&tab=2',
key: 3,
number: 0
}, {
icon: 'https://lbqny.migugu.com/admin/anmo/menu/fuwuzhong.png',
text: '服务中',
url: '/agent/pages/order/list?bell=1&tab=3',
key: 6,
number: 0
}, {
icon: 'https://lbqny.migugu.com/admin/anmo/menu/wancheng.png',
text: '已完成',
url: '/agent/pages/order/list?bell=1&tab=4',
}],
shopRefuseOrder: [{
icon: 'icondaizhuandan',
text: '待转单',
url: '/agent/pages/order/list?tab=8',
key: 8,
number: 0
}],
shopRefund: [{
icon: 'iconshenqingzhong',
text: '申请中',
url: '/agent/pages/refund/list?tab=1',
key: 1,
number: 0
}, {
icon: 'icontongyituikuan',
text: '同意退款',
url: '/agent/pages/refund/list?tab=2',
key: 2,
number: 0
}, {
icon: 'iconjujuetuikuan',
text: '拒绝退款',
url: '/agent/pages/refund/list?tab=3',
key: 3,
number: 0
}],
shopBellRefund: [{
icon: 'https://lbqny.migugu.com/admin/anmo/menu/shenqingzhong.png',
text: '申请中',
url: '/agent/pages/refund/list?bell=1&tab=1',
key: 1,
number: 0
}, {
icon: 'https://lbqny.migugu.com/admin/anmo/menu/tongyi.png',
text: '同意退款',
url: '/agent/pages/refund/list?bell=1&tab=2',
key: 2,
number: 0
}, {
icon: 'https://lbqny.migugu.com/admin/anmo/menu/jujue.png',
text: '拒绝退款',
url: '/agent/pages/refund/list?bell=1&tab=3',
key: 3,
number: 0
}],
toolList: [{
icon: 'iconzhanghaoshezhi',
text: '账号设置',
url: '/agent/pages/account'
}, {
icon: 'iconshenqingjishi3',
text: this.$t('action.attendantName') + '管理',
url: '/agent/pages/technician/list'
},
{
icon: 'iconyongjinxinxi1',
text: '佣金信息',
url: '/agent/pages/income/commission'
},
{
icon: 'icontixianshenqing1',
text: '提现申请',
url: '/user/pages/distribution/record?type=5'
}
],
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
commonOptions: state => state.user.commonOptions,
userInfo: state => state.user.userInfo,
haveOperItem: state => state.technician.haveOperItem,
}),
onLoad(options) {
let {
agent = 0
} = options
options.agent = agent * 1
this.options = options
uni.setNavigationBarTitle({
title: agent == 1 ? '代理商端' : '管理员端'
})
this.initIndex()
},
onShow() {
if (!this.haveOperItem) return
this.initRefresh()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
methods: {
...mapMutations(['updateTechnicianItem']),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
this.$util.showLoading()
this.updateTechnicianItem({
key: 'haveOperItem',
val: false
})
let {
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
let data = await this.$api[methodKey].index()
if (agent) {
data.node = ['shopOrder', 'shopBellOrder', 'shopRefuseOrder', 'shopRefund', 'shopBellRefund']
}
let {
notice
} = data
let {
order_id,
refund_id,
refuse_id
} = notice
if (order_id && order_id.length == 0) {
order_id = {
type: 1
}
}
if (refund_id && refund_id.length == 0) {
refund_id = {
type: 2
}
}
if (refuse_id && refuse_id.length == 0) {
refuse_id = {
type: 3
}
}
let arr = []
if (data.node.includes('shopOrder') || data.node.includes('shopOrder')) {
arr.push(order_id)
}
if (data.node.includes('shopRefund') || data.node.includes('shopBellRefund')) {
arr.push(refund_id)
}
if (data.node.includes('shopRefuseOrder')) {
arr.push(refuse_id)
}
data.notice = arr
this.shopOrder.map(item => {
if (item.key) {
item.number = data.order_count[item.key]
}
})
this.shopBellOrder.map(item => {
if (item.key) {
item.number = data.add_count[item.key]
}
})
this.shopRefuseOrder.map(item => {
if (item.key) {
item.number = data.refuse_order[item.key]
}
})
this.shopRefund.map(item => {
if (item.key) {
item.number = data.refund_count[item.key]
}
})
this.shopBellRefund.map(item => {
if (item.key) {
item.number = data.add_refund_count[item.key]
}
})
this.detail = data
this.isLoad = true
this.$util.hideAll()
},
initRefresh() {
this.initIndex(true)
},
toJump(key, index) {
let {
url
} = this[key][index]
let {
agent
} = this.options
let joinKey = url.includes('?') ? '&' : '?'
url += `${joinKey}agent=${agent}`
this.$util.goUrl({
url
})
},
async goOrder(index) {
let {
id = 0,
order_id = 0,
type
} = this.detail.notice[index]
if (!order_id) return
let {
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
await this.$api[methodKey].noticeUpdate({
id,
have_look: 1
})
this.initRefresh()
let page = type == 2 ? 'refund' : 'order'
let url = `/agent/pages/${page}/detail?id=${order_id}&agent=${agent}`
this.$util.goUrl({
url
})
}
}
}
</script>
<style lang="scss">
.agent-index {
.mine-count-list {
width: 710rpx;
height: 295rpx;
margin: 20rpx 20rpx 0 20rpx;
.text {
color: rgba(255, 255, 255, 0.6)
}
.cash-out-btn {
width: 180rpx;
height: 70rpx;
}
.count-data-list {
.list-item {
width: 50%;
}
}
}
.dot-unread-number {
top: 0;
right: 0;
width: 24rpx;
height: 24rpx;
line-height: 24rpx;
text-align: center;
color: #fff;
font-size: 18rpx;
border-radius: 24rpx;
background-color: #F1381F;
}
// /
.mine-menu-list {
margin: 20rpx 20rpx 0 20rpx;
.menu-title {
height: 90rpx;
color: #434343;
.iconfont {
font-size: 24rpx;
}
}
.icon-info {
width: 80rpx;
height: 80rpx;
background: #FFFFFF;
box-shadow: 0 3rpx 31rpx -2rpx rgba(217, 224, 219, 0.5);
border-radius: 29rpx;
margin-right: 16rpx;
.iconfont {
font-size: 48rpx;
}
}
.share-btn {
width: 130rpx;
height: 52rpx;
border-radius: 8rpx;
}
.notice-info {
.title {
width: 100rpx;
height: 34rpx;
border-radius: 4rpx;
}
.iconfont {
font-size: 20rpx;
}
}
.item-child {
width: 25%;
margin: 10rpx 0;
.iconfont {
font-size: 52rpx;
}
.item-img {
width: 60rpx;
height: 60rpx;
.iconfont {
font-size: 36rpx;
}
.item-img {
top: 0;
left: 0;
opacity: 0.1;
}
}
.icon-img {
height: 32rpx;
}
}
}
.mine-menu-list.share {
padding: 30rpx 30rpx 30rpx 20rpx;
}
}
</style>

View File

@ -0,0 +1,508 @@
<template>
<view class="order-pages" v-if="isLoad">
<fixed @height="initFixHeight" :initHeight="transferForm.coach_type">
<view class="fill-base pd-lg">
<view class="flex-between">
<view class="f-title c-title text-bold">转派订单</view>
<view class="flex-center">
<view @tap.stop="toChangeItem('coach_type',item.id)"
class="flex-center service-type-item c-caption" :class="[{'ml-lg':index!=0}]"
:style="{background:transferForm.coach_type == item.id ? primaryColor:'',color:transferForm.coach_type == item.id ? '#fff':''}"
v-for="(item,index) in tabList" :key="index">
{{item.title}}
</view>
</view>
</view>
<view class="fill-base flex-center mt-lg pt-lg b-1px-t" v-if="transferForm.coach_type == 1">
<view class="flex-1">
<search @input="toSearch" type="input" :padding="0" :radius="30" height="70rpx"
:placeholder="placeholder">
</search>
</view>
<view @tap.stop="$refs.show_transfer_item.open()" class="flex-y-center pl-lg">筛选<i
class="iconfont iconshaixuanxia-1 c-caption"></i></view>
</view>
</view>
</fixed>
<block v-if="transferForm.coach_type == 1">
<view @tap.stop="toChangeItem('coach_id',index)"
class="list-item fill-base pt-lg pb-lg pl-md pr-lg flex-center mt-md ml-md mr-md radius-16"
v-for="(item,index) in list.data" :key="index">
<i class="iconfont mr-md"
:class="[{'icon-xuanze':transferForm.coach_id!=item.id},{'icon-radio-fill':transferForm.coach_id==item.id}]"
:style="{color:transferForm.coach_id==item.id?primaryColor:''}"></i>
<view class="flex-1 flex-warp">
<image class="avatar radius" :src="item.work_img"></image>
<view class="flex-1 ml-md">
<view class="flex-between">
<view class="f-title c-title text-bold max-200 ellipsis">{{item.coach_name}}</view>
<view class="can-service-btn flex-center f-icontext rel" :style="{color:primaryColor}">
<view class="bg abs" :style="{background:primaryColor}"></view>
最早可约{{item.near_time}}
</view>
</view>
<view class="f-desc" style="color:#4D4D4D">
所属代理商{{item.admin_info.username}}{{cityType[item.admin_info.city_type]}}代理</view>
<view class="flex-between f-caption c-caption mt-sm">
<view>电话{{item.mobile}}</view>
<view class="flex-y-center"><i class="iconfont iconjuli1"></i>{{item.distance}}</view>
</view>
</view>
</view>
</view>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading"
v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
</block>
<block v-if="transferForm.coach_type == 2">
<view class="pd-lg f-mini-title c-title fill-base b-1px-t">
<view class="text-bold flex-y-center pb-lg"><i
class="iconfont icon-required c-warning"></i>线下{{$t('action.attendantName')}}</view>
<input v-model="transferForm.coach_name" type="text" class="item-input pl-lg pr-lg radius-16"
maxlength="15" :placeholder="rule[0].errorMsg" placeholder-class="color:#C7C7C7" />
<view class="text-bold flex-y-center pt-lg pb-lg"><i class="iconfont icon-required c-warning"></i>联系电话
</view>
<input v-model="transferForm.mobile" type="text" class="item-input pl-lg pr-lg radius-16" maxlength="11"
:placeholder="rule[1].errorMsg" placeholder-class="color:#C7C7C7" />
<view class="text-bold pt-lg pb-lg">转派备注 </view>
<textarea v-model="transferForm.text" class="item-textarea pd-lg radius-16"
placeholder-class="color:#C7C7C7" maxlength="400" placeholder="若订单有其他特殊情况可单独备注在此处" />
<view @tap.stop="toChooseAgent" class="flex-between pt-lg pb-lg">
<view class="text-bold">关联代理商</view>
<view class="flex-y-center" :class="[{'c-caption':!transferForm.admin_id}]">
<view class="max-400 ellipsis">{{transferForm.admin_id | handleAdminName(base_agent)}}</view>
<i class="iconfont icongengduo"></i>
</view>
</view>
</view>
<view class="flex-center f-caption c-caption pt-lg">
不关联代理商则默认是平台的{{$t('action.attendantName')}}</view>
</block>
<view class="space-max-footer"></view>
<fix-bottom-button @confirm="toConfirm" :text="[{text:'确定',type:'confirm'}]" bgColor="#fff">
</fix-bottom-button>
<uni-popup type="top" ref="show_transfer_item" :top="`${popupHeight+1}px`" :custom="true">
<view class="popup-transfer-type pd-lg fill-base">
<view @tap.stop="toChangeItem('type',item.id)" class="f-paragraph mb-lg" :class="[{'mt-lg':index==0}]"
:style="{color:param.type==item.id?primaryColor:''}" v-for="(item,index) in transfreTypeList"
:key="index">
{{item.title}}
</view>
</view>
</uni-popup>
<uni-popup ref="choose_item" type="bottom" :custom="true">
<view @touchmove.stop.prevent class="common-popup-content fill-base"
style="width: 100%;border-radius: 34rpx 34rpx 0 0;">
<view class="flex-center f-title c-title text-bold pb-lg">选择代理商</view>
<scroll-view scroll-y style="width: 100%;max-height:50vh">
<view @tap.stop="toChangeItem('chooseInd',index)" class="flex-center pt-sm pb-sm"
:style="{color:chooseInd == index ? primaryColor: ''}" v-for="(item,index) in base_agent"
:key="index">
<view class="f-title flex-1 pr-lg">
{{item.agent_name}}
</view>
<i class="iconfont c-caption"
:class="[{'icon-xuanze':chooseInd != index},{'icon-radio-fill':chooseInd == index}]"
style="font-size: 40rpx;" :style="{color:chooseInd == index ? primaryColor: ''}"></i>
</view>
</scroll-view>
<view class="button">
<view @tap.stop="$refs.choose_item.close()" class="item-child">
取消
</view>
<view @tap.stop="toConfirmCheck" class="item-child"
:style="{background: primaryColor,color:'#fff'}">
确定
</view>
</view>
<view class="space-safe"></view>
</block>
</view>
</uni-popup>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
components: {},
data() {
return {
isLoad: false,
options: {},
placeholder: '请输入' + this.$t('action.attendantName') + '名称',
tabList: [{
id: 1,
title: '更换' + this.$t('action.attendantName')
}, {
id: 2,
title: '委派' + this.$t('action.attendantName')
}],
transfreTypeList: [{
id: 1,
title: '距离最近'
}, {
id: 2,
title: '最早可预约'
}],
cityType: ['', '城市', '区县', '省'],
param: {
page: 1,
coach_name: '',
type: 1
},
list: {
data: []
},
loading: true,
index: -1,
lockTap: false,
popupHeight: '',
popupInfo: {},
base_agent: [],
chooseInd: -1,
transferForm: {
order_id: '',
coach_type: 1,
coach_id: '',
coach_name: '',
near_time: '',
mobile: '',
text: '',
admin_id: ''
},
rule: [{
name: "coach_name",
checkType: "isNotNull",
errorMsg: "请输入" + this.$t('action.attendantName') + "姓名",
regType: 2
},
{
name: "mobile",
checkType: "isMobile",
errorMsg: "请输入联系电话"
}
]
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
onLoad(options) {
let {
id,
agent = 0
} = options
options.agent = agent * 1
this.options = options
this.transferForm.order_id = id
this.initIndex()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh()
uni.stopPullDownRefresh()
},
onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
this.getList();
},
methods: {
...mapActions(['getConfigInfo']),
...mapMutations(['updateTechnicianItem']),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
if (!this.configInfo.id || refresh) {
await this.getConfigInfo()
}
await Promise.all([this.getBaseInfo(), this.getList()])
this.isLoad = true
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
},
initRefresh() {
this.param.page = 1
this.initIndex(true)
},
async getBaseInfo() {
let {
agent = 0
} = this.options
let methodKey = agent ? 'agent' : 'admin'
this.base_agent = await this.$api[methodKey].adminSelect()
},
toSearch(val) {
this.param.page = 1
this.param.coach_name = val
this.transferForm.coach_id = ''
this.getList()
},
async getList() {
let {
list: oldList,
param,
} = this
let {
id,
agent = 0
} = this.options
param.order_id = id
let methodKey = agent ? 'agent' : 'admin'
let newList = await this.$api[methodKey].orderChangeCoachList(param)
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
this.loading = false
this.$util.hideAll()
},
initFixHeight(val) {
this.popupHeight = val
},
toChangeItem(key, val) {
switch (key) {
case 'type':
this.transferForm.coach_id = ''
this.param[key] = val
this.param.page = 1
this.getList()
this.$refs.show_transfer_item.close()
break
case 'coach_type':
let data = Object.assign({}, this.transferForm, {
coach_type: val,
coach_id: '',
coach_name: '',
near_time: '',
mobile: '',
text: '',
admin_id: ''
})
this.transferForm = data
break
case 'coach_id':
let {
id, near_time
} = this.list.data[val]
this.transferForm[key] = id
this.transferForm.near_time = near_time
break
case 'chooseInd':
this[key] = val
break
}
},
toChooseAgent() {
let {
admin_id = 0
} = this.transferForm
let ind = this.base_agent.findIndex(item => {
return item.id == admin_id
})
this.chooseInd = ind
this.$refs.choose_item.open()
},
toConfirmCheck() {
let {
id
} = this.base_agent[this.chooseInd]
this.transferForm.admin_id = id
this.$refs.choose_item.close()
},
//
validate(param) {
let validate = new this.$util.Validate();
this.rule.map(item => {
let {
name,
} = item
validate.add(param[name], item);
})
let message = validate.start();
return message;
},
async toConfirm() {
let param = this.$util.deepCopy(this.transferForm)
let {
coach_type: ctype = 1,
coach_id = 0
} = param
if (ctype === 1) {
if (!coach_id) {
this.$util.showToast({
title: `请选择` + this.$t('action.attendantName')
})
return
}
delete param.coach_name
delete param.mobile
delete param.text
delete param.admin_id
} else {
param.coach_id = 0
let msg = this.validate(param);
if (msg) {
this.$util.showToast({
title: msg
});
return;
}
delete param.near_time
}
delete param.coach_type
param.text = param.text ? param.text.substring(0, 400) : ''
if (this.lockTap) return
let {
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
try {
await this.$api[methodKey].orderChangeCoach(param)
this.$util.showToast({
title: '操作成功'
})
this.lockTap = false;
this.$util.hideAll()
this.updateTechnicianItem({
key: 'haveOperItem',
val: true
})
this.$util.back()
this.$util.goUrl({
url: 1,
openType: `navigateBack`
})
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
return
}
},
//
goDetail(index) {
let {
id
} = this.list.data[index]
let {
agent
} = this.options
let url = `/agent/pages/order/detail?id=${id}&agent=${agent}`
this.$util.goUrl({
url
})
}
},
filters: {
handleAdminName(val, data) {
let text = '请选择代理商'
if (val) {
let arr = data.filter(item => {
return item.id == val
})
text = arr[0].agent_name
}
return text
}
}
}
</script>
<style lang="scss">
.order-pages {
.iconshaixuanxia-1 {
font-size: 20rpx;
transform: scale(0.6);
}
.popup-transfer-type {
width: 100%;
height: 234rpx;
}
.list-item {
.icon-xuanze,
.icon-radio-fill {
font-size: 38rpx;
color: '#BEC3CE'
}
.avatar {
width: 124rpx;
height: 124rpx;
}
.can-service-btn {
height: 32rpx;
padding: 0 6rpx 0 6rpx;
.bg {
opacity: 0.1;
border-radius: 5rpx;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 1;
}
}
}
.item-input {
height: 110rpx;
background: #F9FAF9;
}
.item-textarea {
width: 630rpx;
height: 300rpx;
background: #F9FAF9;
}
.icongengduo {
color: #5A677E;
font-size: 20rpx;
}
}
</style>

View File

@ -0,0 +1,549 @@
<template>
<view class="order-pages" v-if="detail.id">
<view class="item-child pd-lg fill-base f-paragraph c-base" :style="{background:primaryColor}">
<view class="text-bold">{{statusType[detail.pay_type]}}</view>
<view class="f-caption mt-sm" v-if="detail.pay_type == 1 && detail.end_time > 0">请在<min-countdown
:targetTime="over_time_text" @callback="countEnd"></min-countdown></view>
<view class="space-lg"></view>
</view>
<!-- // pay_type 123-4-5-678 -->
<view
class="menu-list flex-warp rel ml-lg mr-lg pt-lg pb-lg pl-md pr-md fill-base f-paragraph c-caption radius-16"
:class="[{'add-bell':detail.is_add || detail.store_id}]">
<view class="menu-line abs b-1px-b"></view>
<block v-for="(item,index) in lineList" :key="index">
<view class="item-child flex-center flex-column f-icontext c-paragraph"
:style="{color:detail.pay_type > item.pay_type -1?primaryColor:''}" v-if="item.icon">
<view class="item-img fill-base flex-center mb-sm radius"
:style="{borderColor:detail.pay_type > item.pay_type -1?primaryColor:''}">
<i class="iconfont" :class="item.icon"></i>
</view>
<view class="ellipsis" style="max-width:100%">{{item.title}}</view>
</view>
</block>
</view>
<view class="item-child mt-md ml-lg mr-lg pd-lg fill-base radius-16">
<view class="flex-between pb-lg">
<view class="f-paragraph c-title max-380 ellipsis">服务内容</view>
</view>
<view class="flex-center" :class="[{'mb-lg':aindex != detail.order_goods.length -1}]"
v-for="(aitem,aindex) in detail.order_goods" :key="aindex">
<!-- #ifdef H5 -->
<view class="avatar lg radius-16">
<view class="h5-image avatar lg radius-16"
:style="{ backgroundImage : `url('${aitem.goods_cover}')`}">
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image mode="aspectFill" class="avatar lg radius-16" :src="aitem.goods_cover"></image>
<!-- #endif -->
<view class="flex-1 ml-md">
<view class="flex-between">
<view class="f-mini-title c-title text-bold max-380 ellipsis">
{{aitem.goods_name}}
</view>
<view class="c-paragraph">x{{aitem.num}}</view>
</view>
<view class="f-caption c-caption" v-if="aitem.material_price*1>0">物料费¥{{aitem.material_price}}
</view>
<view class="flex-between mt-md">
<view class="f-caption" style="color:#777">服务时长 {{aitem.time_long}}分钟</view>
<view class="f-caption c-warning" v-if="aitem.refund_num>0">已退x{{aitem.refund_num}}</view>
</view>
<view class="f-paragraph c-warning text-bold mt-sm">¥{{aitem.price}}
</view>
</view>
</view>
</view>
<view class="store-info mt-md ml-lg mr-lg pd-lg fill-base radius-16" v-if="detail.store_id">
<view class="f-mini-title c-title text-bold pb-md">
{{detail.store_info.title}}
</view>
<view class="flex-between">
<view class="flex-y-center" style="color: #303030;">
<i class="iconfont icondizhi1 mr-sm"></i>
<view class="c-title flex-1 mr-md">
<span>{{detail.store_info.address || `暂未设置门店地址`}}</span>
<span @tap.stop="$util.goUrl({url:detail.store_info.address,openType:'copy'})"
class="copy-btn span f-icontext radius-5 ml-sm"
:style="{color:primaryColor,borderColor:primaryColor}"
v-if="detail.store_info.address">复制</span>
</view>
</view>
<view class="flex-center">
<view @tap.stop="$util.goUrl({url:detail.store_info.phone,openType:'call'})"
class="item-icon rel flex-center radius-16">
<view class="item-icon radius-16 abs" :style="{background:primaryColor}"></view>
<i class="iconfont icondadianhua_1" :style="{color:primaryColor}"></i>
</view>
<view @tap.stop="toMap('store_info')" class="item-icon rel flex-center radius-16 ml-md"
v-if="detail.store_info.address">
<view class="item-icon radius-16 abs" :style="{background:primaryColor}"></view>
<i class="iconfont icondizhi_1" :style="{color:primaryColor}"></i>
</view>
</view>
</view>
</view>
<view class="order-agent-info store-info mt-md ml-lg mr-lg pd-lg fill-base f-paragraph radius-16">
<view class="flex-center">
<image mode="aspectFill" class="coach-img radius" :src="detail.coach_info.work_img"></image>
<view class="flex-1 ml-lg f-title text-bold ellipsis" style="max-width: 506rpx;">
{{detail.coach_info ? detail.coach_info.coach_name : ''}}
</view>
</view>
<view class="flex-center mt-lg">
<view class="title">下单人</view>
<view class="text flex-1 ellipsis">{{detail.address_info.user_name}}</view>
</view>
<view class="flex-center mt-lg">
<view class="title">联系方式</view>
<view class="text flex-1 flex-between">
<view>
{{detail.address_info.mobile.substring(0,3)}}****{{detail.address_info.mobile.substring(7,11)}}
</view>
<view @tap.stop="toTel" class="item-icon rel flex-center radius-16">
<view class="item-icon radius-16 abs" :style="{background:primaryColor}"></view>
<i class="iconfont icondadianhua_1" :style="{color:primaryColor}"></i>
</view>
</view>
</view>
<view class="flex-warp mt-lg" v-if="!detail.store_id">
<view class="title">服务地址</view>
<view class="text flex-1 flex-between">
<view style="max-width: 350rpx;">
<span>{{`${detail.address_info.address}${detail.address_info.address_info}`}}</span>
<span @tap="toCopy" class="copy-btn fill radius-5 f-icontext ml-sm">复制</span>
</view>
<view @tap.stop="toMap('address_info')" class="item-icon rel flex-center radius-16">
<view class="item-icon radius-16 abs" :style="{background:primaryColor}"></view>
<i class="iconfont icondizhi_1" :style="{color:primaryColor}"></i>
</view>
</view>
</view>
<view class="mt-lg" v-if="detail.text">
<view class="title">订单备注</view>
<view class="text mt-sm">{{detail.text}}</view>
</view>
<view class="flex-center mt-lg pt-lg b-1px-t">
<view class="title">下单时间</view>
<view class="text flex-1">{{detail.create_time}}</view>
</view>
<view class="flex-center mt-lg">
<view class="title">服务时间</view>
<view class="text flex-1">{{detail.start_time}}</view>
</view>
<view class="flex-center mt-lg">
<view class="title">服务时长</view>
<view class="text flex-1">{{detail.time_long}}分钟</view>
</view>
<block v-if="!detail.is_add && !detail.store_id">
<view class="flex-center mt-lg">
<view class="title">车费详情</view>
<view class="text flex-1 flex-y-center">{{carType[detail.car_type]}}
<view class="ml-md" v-if="detail.car_type == 1">全程{{detail.distance}}</view>
</view>
</view>
<view class="flex-center mt-lg" v-if="detail.car_type == 1">
<view class="title">出行费用</view>
<view class="text flex-1">出租车 ¥{{detail.car_price}}</view>
</view>
</block>
<view class="flex-center mt-lg">
<view class="title">服务项目费用</view>
<view class="text flex-1">¥{{detail.init_service_price}}</view>
</view>
<view class="flex-center mt-lg" v-if="detail.discount*1 > 0">
<view class="title">卡券优惠</view>
<view class="text flex-1">-¥{{detail.discount}}</view>
</view>
<view class="flex-center mt-lg">
<view class="title">支付方式</view>
<view class="text flex-1">{{payType[detail.pay_model]}}</view>
</view>
<view class="flex-between mt-lg pt-lg b-1px-t">
<view class="title"></view>
<view class="flex-y-baseline f-paragraph c-black text-bold">总计<view class="c-warning">
¥{{detail.pay_price}}</view>
</view>
</view>
</view>
<view class="mt-md ml-lg mr-lg pd-lg fill-base radius-16">
<view class="flex-y-center pb-lg f-mini-title c-title flex-warp b-1px-b">
<view class="flex-between text-bold">订单编号</view>
<view class="flex-between flex-1 ">
<view class="text-bold max-350 ellipsis">{{detail.order_code}}</view>
<view class="copy-btn flex-center radius-5 f-icontext"
@tap.stop="$util.goUrl({openType:'copy',url:detail.order_code})"
:style="{borderColor:primaryColor ,color:primaryColor}">复制</view>
</view>
</view>
<view class="space-lg"></view>
<timeline :list="lineList" :info="detail"></timeline>
</view>
<view class="space-max-footer"></view>
<fix-bottom-button
@cancel="$util.goUrl({url:`/agent/pages/order/change?id=${options.id}&agent=${options.agent}`})"
@confirm="toConfirm"
:text="detail.is_add && detail.pay_type != 8 ? [{text:$t( `action.${ technicianStatusOperType[ detail.pay_type === 3 && (detail.is_add || detail.store_id) ? 5 : detail.pay_type ] }` ),type:'confirm'}] : [{text:$t('action.transferOrder'),type:'cancel'},{text:$t( `action.${ technicianStatusOperType[ detail.pay_type === 3 && (detail.is_add || detail.store_id) ? 5 : detail.pay_type == 8 ? -1 : detail.pay_type ] }` ),type:'confirm'}]"
bgColor="#fff" :classType="2" v-if="[2, 3, 4, 5, 6, 8].includes(detail.pay_type)">
</fix-bottom-button>
<uni-popup ref="change_item" type="center" :custom="true">
<view class="common-popup-content fill-base pd-lg radius-34">
<view class="title">温馨提示</view>
<view class="desc">
你确认要操作{{$t(`action.${technicianStatusOperType[popupInfo.type]}`)}}?
</view>
<view class="f-caption c-warning" v-if="popupInfo.type == -1">退款金额¥{{popupInfo.refund_price}}</view>
<view class="button">
<view @tap.stop="$refs.change_item.close()" class="item-child">取消</view>
<view @tap.stop="confirmChangeOrder" class="item-child c-base"
:style="{background: primaryColor,color:'#fff'}">确定</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
import timeline from '@/components/timeline.vue'
export default {
components: {
timeline
},
data() {
return {
options: {},
statusType: {
'-1': '已取消',
1: '待支付',
2: '待服务',
3: this.$t('action.attendantName') + '接单',
4: this.$t('action.attendantName') + '出发',
5: this.$t('action.attendantName') + '到达',
6: '服务中',
7: '已完成',
8: '待转单'
},
technicianStatusOperType: {
'-1': 'agreeRefund',
2: 'orderTaking',
3: 'setOut',
4: 'arrive',
5: 'startService',
6: 'serviceCompletion'
},
carType: {
0: '公交/地铁',
1: '出租车'
},
payType: {
1: '微信支付',
2: '余额支付',
3: '支付宝支付'
},
lineList: [],
base_service: [{
pay_type: 3,
title: this.$t('action.attendantName') + '接单',
time: 'receiving_time',
icon: 'iconjishijiedan'
}, {
pay_type: 4,
title: this.$t('action.attendantName') + '出发',
time: 'serout_time',
icon: 'iconjishichufa'
}, {
pay_type: 5,
title: this.$t('action.attendantName') + '到达',
time: 'arrive_time',
icon: 'iconjishidaoda'
}, {
pay_type: 6,
title: '开始服务',
time: 'start_service_time',
icon: 'iconjishifuwu'
}, {
pay_type: 7,
title: '服务完成',
time: 'order_end_time',
icon: 'iconjishiwancheng'
}, {
pay_type: 7,
title: '签字确认',
time: 'sign_time',
icon: ''
}],
base_bell: [{
pay_type: 3,
title: this.$t('action.attendantName') + '接单',
time: 'receiving_time',
icon: 'iconjishijiedan'
}, {
pay_type: 6,
title: '开始服务',
time: 'start_service_time',
icon: 'iconjishifuwu'
}, {
pay_type: 7,
title: '服务完成',
time: 'order_end_time',
icon: 'iconjishiwancheng'
}],
detail: {
pay_type: 0
},
check_label: [],
coach_refund_text: '',
lockTap: false,
popupInfo: {
title: '',
type: '',
param: {},
imgs: [],
location: {
lat: 0,
lng: 0,
address: ''
}
},
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
haveOperItem: state => state.technician.haveOperItem,
over_time_text() {
return new Date().getTime() + this.detail.end_time * 1000
}
}),
onLoad(options) {
let {
agent = 0
} = options
options.agent = agent * 1
this.options = options
this.initIndex()
},
onShow() {
if (!this.haveOperItem) return
this.$util.back()
this.updateTechnicianItem({
key: 'haveOperItem',
val: false
})
},
methods: {
...mapActions(['getConfigInfo', 'getCoachInfo']),
...mapMutations(['updateTechnicianItem']),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
if (!this.configInfo.id || refresh) {
await this.getConfigInfo()
}
let {
id,
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
let data = await this.$api[methodKey].orderInfo({
id
})
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
data.is_balance = data.balance * 1 > 0 ? 1 : 0
let {
pay_type,
time_long,
start_service_time,
is_add = 0,
store_id = 0
} = data
let lineList = this.$util.deepCopy(is_add || store_id ? this.base_bell : this.base_service)
if (store_id) {
lineList.push({
pay_type: 7,
title: '签字确认',
time: 'sign_time',
icon: ''
})
}
this.lineList = lineList
this.detail = data
},
initRefresh() {
this.initIndex(true)
},
countEnd() {
this.$util.log("倒计时完了")
setTimeout(() => {
this.initRefresh()
this.$util.back()
}, 1000)
},
// type: -134567
async toConfirm() {
let {
id: order_id,
pay_type,
is_add = 0,
store_id = 0,
true_car_price,
true_service_price
} = this.detail
let type = pay_type === 3 && (is_add || store_id) ?
5 :
pay_type
let refund_price = (true_car_price * 1 + true_service_price * 1).toFixed(2)
this.popupInfo = {
order_id,
type: pay_type == 8 ? -1 : type,
refund_price
}
this.$refs.change_item.open()
},
async confirmChangeOrder() {
let param = this.$util.deepCopy(this.popupInfo)
param.type = param.type + 1
let {
type
} = param
delete param.index
let msg = {
'-1': '退款成功',
3: '接单成功',
4: '已成功出发',
5: '已成功到达',
6: '已开始服务',
7: '服务已完成'
}
let {
activeIndex
} = this
if (this.lockTap) return;
this.lockTap = true;
this.$util.showLoading()
let {
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
try {
await this.$api[methodKey].adminUpdateOrder(param)
this.$refs.change_item.close()
this.$util.showToast({
title: msg[type]
})
this.lockTap = false;
this.updateTechnicianItem({
key: 'haveOperItem',
val: true
})
this.initRefresh()
this.$util.back()
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
return
}
},
//
async toTel() {
let {
id: order_id,
pay_type
} = this.detail
let {
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
if ([2, 3, 4, 5, 6].includes(pay_type)) {
let url = await this.$api[methodKey].getVirtualPhone({
order_id
})
this.$util.goUrl({
url,
openType: `call`
})
} else {
let msg = pay_type == 7 ? '服务结束' : '服务取消'
this.$util.showToast({
title: `${msg}不能联系客户哦`
})
}
},
//
toCopy() {
let {
address,
address_info
} = this.detail.address_info
let url = `${address}${address_info}`
this.$util.goUrl({
url,
openType: 'copy'
})
},
//
async toMap(key) {
let {
address,
address_info = '',
lat,
lng
} = this.detail[key]
await this.$util.checkAuth({
type: 'userLocation'
})
await uni.getLocation({
type: 'gcj02',
})
await uni.openLocation({
latitude: lat * 1,
longitude: lng * 1,
name: address_info ? `${address} ${address_info}` : address,
scale: 28
})
},
}
}
</script>
<style lang="scss">
.avatar.coath {
width: 94rpx;
height: 94rpx;
}
</style>

401
agent/pages/order/list.vue Normal file
View File

@ -0,0 +1,401 @@
<template>
<view class="order-pages" v-if="isLoad">
<fixed>
<view class="fill-base pt-lg pl-md pr-md pb-md">
<search @input="toSearch" type="input" :padding="0" :radius="30" height="70rpx"
placeholder="请输入系统订单号查询">
</search>
</view>
<tab @change="handerTabChange" :list="tabList" :activeIndex="activeIndex*1" :activeColor="primaryColor"
height="100rpx" v-if="options.tab != 8"></tab>
<view class="b-1px-b"></view>
</fixed>
<view @tap.stop="goDetail(index)" class="item-child mt-md ml-lg mr-lg pd-lg fill-base radius-16 rel"
v-for="(item,index) in list.data" :key="index">
<view v-if="item.is_add">
<view class="bell-tag flex-center f-icontext c-base abs" :style="{background:primaryColor}">加钟服务
</view>
<view class="space-md"></view>
</view>
<view class="flex-between pb-lg b-1px-b">
<view class="f-paragraph c-title max-450 ellipsis">订单号{{item.order_code}}</view>
<view class="f-caption text-bold"
:style="{color:item.pay_type==2?primaryColor: [3,4,5].includes(item.pay_type)?subColor: item.pay_type == 6 ? '#11C95E' : '#333'}">
{{statusType[item.pay_type]}}
</view>
</view>
<view class="flex-center mb-lg" :class="[{'mt-lg':aindex==0}]" v-for="(aitem,aindex) in item.order_goods"
:key="aindex">
<!-- #ifdef H5 -->
<view class="avatar lg radius-16">
<view class="h5-image avatar lg radius-16"
:style="{ backgroundImage : `url('${aitem.goods_cover}')`}">
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image mode="aspectFill" class="avatar lg radius-16" :src="aitem.goods_cover"></image>
<!-- #endif -->
<view class="flex-1 ml-md">
<view class="flex-between">
<view class="f-mini-title c-title text-bold max-380 ellipsis">
{{aitem.goods_name}}
</view>
<view class="c-paragraph">x{{aitem.num}}</view>
</view>
<view class="flex-between mt-md">
<view class="f-caption c-caption ellipsis"
:class="[{'max-300':aitem.refund_num>0},{'max-450':aitem.refund_num==0}]">
服务{{$t('action.attendantName')}}{{item.coach_info?item.coach_info.coach_name:'-'}}</view>
<view class="f-caption c-warning" v-if="aitem.refund_num>0">已退x{{aitem.refund_num}}</view>
</view>
<view class="f-caption c-caption" style="margin-top: 5rpx;">服务时间{{item.start_time}}</view>
</view>
</view>
<view class="flex-between pt-lg b-1px-t">
<view class="flex-y-center f-desc c-title">总计
<view class="f-paragraph text-bold">¥{{item.pay_price}}</view>
</view>
<view class="flex-warp">
<!-- // pay_type 234567 -->
<block v-if="[2, 3, 4, 5, 6, 8].includes(item.pay_type)">
<button
@tap.stop="$util.goUrl({url:`/agent/pages/order/change?id=${item.id}&agent=${options.agent}`})"
class="clear-btn order" style="margin-left: 0;" v-if="!options.bell">转单</button>
<button @tap.stop="toConfirm(index)" class="clear-btn order"
:style="{color:'#fff',background:primaryColor,borderColor:primaryColor}">{{ $t( `action.${ technicianStatusOperType[ item.pay_type === 3 && (item.is_add || item.store_id) ? 5 : item.pay_type == 8 ? -1 : item.pay_type ] }` ) }}</button>
</block>
</view>
</view>
</view>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading" v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
<view class="space-footer"></view>
<uni-popup ref="change_item" type="center" :custom="true">
<view class="common-popup-content fill-base pd-lg radius-34">
<view class="title">温馨提示</view>
<view class="desc">
你确认要操作{{$t(`action.${technicianStatusOperType[popupInfo.type]}`)}}?
</view>
<view class="f-caption c-warning" v-if="popupInfo.type == -1">退款金额¥{{popupInfo.refund_price}}</view>
<view class="button">
<view @tap.stop="$refs.change_item.close()" class="item-child">取消</view>
<view @tap.stop="confirmChangeOrder" class="item-child c-base"
:style="{background: primaryColor,color:'#fff'}">确定</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
components: {},
data() {
return {
isLoad: false,
options: {},
activeIndex: 0,
tabList: [],
tabOrderList: [{
title: '全部',
id: 0
}, {
title: '待服务',
id: 2,
number: 0
}, {
title: this.$t('action.attendantName') + '接单',
id: 3,
number: 0
}, {
title: this.$t('action.attendantName') + '出发',
id: 4,
number: 0
}, {
title: this.$t('action.attendantName') + '到达',
id: 5,
number: 0
}, {
title: '服务中',
id: 6,
number: 0
}, {
title: '已完成',
id: 7,
number: 0
}],
tabBellList: [{
title: '全部',
id: 0
}, {
title: '待服务',
id: 2,
number: 0
}, {
title: this.$t('action.attendantName') + '接单',
id: 3,
number: 0
}, {
title: '服务中',
id: 6,
number: 0
}, {
title: '已完成',
id: 7,
number: 0
}],
statusType: {
'-1': '已取消',
1: '待支付',
2: '待服务',
3: this.$t('action.attendantName') + '接单',
4: this.$t('action.attendantName') + '出发',
5: this.$t('action.attendantName') + '到达',
6: '服务中',
7: '已完成',
8: '待转单'
},
technicianStatusOperType: {
'-1': 'agreeRefund',
2: 'orderTaking',
3: 'setOut',
4: 'arrive',
5: 'startService',
6: 'serviceCompletion',
8: 'transferOrder'
},
param: {
page: 1,
pay_type: 0,
order_code: ''
},
list: {
data: []
},
loading: true,
index: -1,
lockTap: false,
popupInfo: {
title: '',
index: '',
type: '',
param: {},
imgs: [],
location: {
lat: 0,
lng: 0,
address: ''
}
},
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
onLoad(options) {
let {
agent = 0,
bell = 0,
tab = 0,
} = options
options.agent = agent * 1
options.bell = bell * 1
this.options = options
uni.setNavigationBarTitle({
title: tab == 8 ? '拒单管理' : bell == 1 ? '加钟管理' : '订单管理'
})
this.tabList = tab == 8 ? [] : bell == 1 ? this.tabBellList : this.tabOrderList
this.activeIndex = tab
this.initIndex()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
this.getList();
},
methods: {
...mapActions(['getConfigInfo']),
...mapMutations(['updateTechnicianItem']),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
if (!this.configInfo.id || refresh) {
await this.getConfigInfo()
}
await this.getList()
this.isLoad = true
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
},
initRefresh() {
this.param.page = 1
this.initIndex(true)
},
toSearch(val) {
this.param.page = 1
this.param.order_code = val
this.getList()
},
async getList(flag = false) {
let {
list: oldList,
param,
tabList,
activeIndex,
} = this
let {
tab = 0,
bell = 0,
agent = 0
} = this.options
let methodKey = agent ? 'agent' : 'admin'
param.pay_type = tab == 8 ? 8 : tabList[activeIndex].id
if (tab != 8) {
param.is_add = bell
}
let newList = await this.$api[methodKey].orderList(param)
if (!flag) {
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
}
this.loading = false
this.$util.hideAll()
},
handerTabChange(index) {
this.activeIndex = index;
this.$util.showLoading()
this.param.page = 1;
this.getList()
},
// type: -134567
async toConfirm(index) {
let {
id: order_id,
pay_type,
true_car_price,
true_service_price,
is_add = 0,
store_id = 0
} = this.list.data[index]
let type = pay_type === 3 && (is_add || store_id) ?
5 :
pay_type
let refund_price = (true_car_price * 1 + true_service_price * 1).toFixed(2)
this.popupInfo = {
index,
order_id,
type: pay_type == 8 ? -1 : type,
refund_price
}
this.$refs.change_item.open()
},
async confirmChangeOrder() {
let param = this.$util.deepCopy(this.popupInfo)
param.type = param.type + 1
let {
index,
type
} = param
delete param.index
delete param.refund_price
let msg = {
'-1': '退款成功',
3: '接单成功',
4: '已成功出发',
5: '已成功到达',
6: '已开始服务',
7: '服务已完成'
}
let {
activeIndex
} = this
if (this.lockTap) return;
this.lockTap = true;
this.$util.showLoading()
let {
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
try {
await this.$api[methodKey].adminUpdateOrder(param)
this.$refs.change_item.close()
this.$util.showToast({
title: msg[type]
})
if (activeIndex == 0) {
this.list.data[index].pay_type = type
} else {
this.list.data.splice(index, 1)
}
this.lockTap = false;
this.$util.hideAll()
this.updateTechnicianItem({
key: 'haveOperItem',
val: true
})
if (activeIndex == 0) return
await this.getList(true)
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
return
}
},
//
goDetail(index) {
let {
id
} = this.list.data[index]
let {
agent
} = this.options
let url = `/agent/pages/order/detail?id=${id}&agent=${agent}`
this.$util.goUrl({
url
})
}
}
}
</script>
<style lang="scss">
</style>

View File

@ -0,0 +1,237 @@
<template>
<view>
<view class="hideCanvasView">
<l-painter class="hideCanvas" ref="painter" useCORS />
</view>
<block v-if="src">
<image :src="src" class="code-img" @tap="previewImage"></image>
<view class="space-max-footer"></view>
<fix-bottom-button @confirm="toPreviewSave" :text="[{text: confirmText,type:'confirm'}]" bgColor="#fff"
:classType="2">
</fix-bottom-button>
</block>
</view>
</template>
<script>
import {
mapState,
mapActions
} from 'vuex';
export default {
components: {},
props: {
},
data() {
return {
options: {},
// #ifdef H5
confirmText: '长按上图保存图片',
// #endif
// #ifndef H5
confirmText: '保存图片至相册',
// #endif
src: ''
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
async onLoad(options) {
this.options = options
// #ifdef H5
if (this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
this.$util.showLoading()
await this.getConfigInfo()
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
let that = this
setTimeout(() => {
that.renderToCanvas()
}, 1000)
},
methods: {
...mapActions(['getConfigInfo']),
async renderToCanvas() {
let that = this;
let {
type = 0
} = that.options
let qr_code = type == 1 ? await this.$api.salesman.salesmanQr() : await this.$api.agent.agentInviteQr({
type: 1
})
let {
channel_poster = ''
} = this.configInfo
let cover = channel_poster || 'https://lbqny.migugu.com/admin/anmo/mine/channel-share.png'
let {
nickName,
avatarUrl
} = this.userInfo
let qr_radius = '0rpx'
// #ifdef MP-WEIXIN
qr_radius = '97rpx'
// #endif
let poster = {
css: {
width: '750rpx',
height: '1280rpx',
},
views: [{
type: 'image',
src: cover,
css: {
width: '750rpx',
height: '1140rpx',
objectFit: "cover",
top: '0rpx',
left: '0rpx',
position: 'absolute'
}
},
{
type: 'view',
css: {
background: '#fff',
width: '750rpx',
height: '140rpx',
bottom: '0rpx',
left: '0rpx',
position: 'absolute'
},
views: [{
type: 'image',
src: avatarUrl,
css: {
position: 'absolute',
width: '94rpx',
height: '94rpx',
objectFit: 'cover',
borderRadius: '50rpx',
bottom: '23rpx',
left: '20rpx'
}
},
{
type: 'text',
text: nickName,
css: {
position: 'absolute',
bottom: '70rpx',
left: '130rpx',
width: '350rpx',
fontSize: '32rpx',
fontWeight: '400',
color: '#000'
}
},
{
type: 'text',
text: '邀请您成为TA的渠道商扫描二维码立即加入吧!',
css: {
position: 'absolute',
bottom: '25rpx',
left: '130rpx',
width: '750rpx',
fontSize: '26rpx',
color: '#999999',
}
}
],
},
{
type: 'image',
src: qr_code,
css: {
position: 'absolute',
width: '200rpx',
height: '200rpx',
bottom: '337rpx',
left: '53rpx',
background: '#ffffff',
borderRadius: qr_radius
}
},
{
type: 'text',
text: '扫一扫',
css: {
position: 'absolute',
bottom: '290rpx',
left: '53rpx',
width: '200rpx',
fontSize: '26rpx',
color: '#999999',
textAlign: 'center'
}
}
]
}
//
this.$refs.painter.render(poster);
//
this.$refs.painter.canvasToTempFilePathSync({
fileType: "jpg",
quality: 1,
success: (res) => {
that.$util.hideAll()
this.src = res.tempFilePath
},
});
},
previewImage() {
let finalPath = this.src;
uni.previewImage({
current: finalPath,
urls: [finalPath]
})
},
async saveImage() {
await this.$util.checkAuth({
type: "writePhotosAlbum"
});
let filePath = this.src;
let [err, success] = await uni.saveImageToPhotosAlbum({
filePath
})
if (err) return;
uni.showToast({
icon: 'none',
title: '保存成功'
})
},
toPreviewSave() {
// #ifdef H5
this.previewImage()
// #endif
// #ifndef H5
this.saveImage()
// #endif
}
}
}
</script>
<style>
.code-img {
width: 750rpx;
height: 1280rpx;
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<view>
<view class="hideCanvasView">
<l-painter class="hideCanvas" ref="painter" useCORS />
</view>
<block v-if="src">
<image :src="src" class="code-img" @tap="previewImage"></image>
<view class="space-max-footer"></view>
<fix-bottom-button @confirm="toPreviewSave" :text="[{text: confirmText,type:'confirm'}]" bgColor="#fff"
:classType="2">
</fix-bottom-button>
</block>
</view>
</template>
<script>
import {
mapState,
mapActions
} from 'vuex';
export default {
components: {},
props: {
},
data() {
return {
// #ifdef H5
confirmText: '长按上图保存图片',
// #endif
// #ifndef H5
confirmText: '保存图片至相册',
// #endif
src: ''
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
async onLoad(options) {
// #ifdef H5
if (this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
this.$util.showLoading()
await this.getConfigInfo()
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
let that = this
setTimeout(() => {
that.renderToCanvas()
}, 1000)
},
methods: {
...mapActions(['getConfigInfo']),
async renderToCanvas() {
let that = this;
let qr_code = await this.$api.agent.agentInviteQr({
type: 2
})
let {
salesman_poster = ''
} = this.configInfo
let cover = salesman_poster || 'https://lbqny.migugu.com/admin/anmo/mine/salesman-share.png'
let {
nickName,
avatarUrl
} = this.userInfo
let qr_radius = '0rpx'
// #ifdef MP-WEIXIN
qr_radius = '145rpx'
// #endif
let poster = {
css: {
width: '750rpx',
height: '1280rpx',
},
views: [{
type: 'image',
src: cover,
css: {
width: '750rpx',
height: '1140rpx',
objectFit: "cover",
top: '0rpx',
left: '0rpx',
position: 'absolute'
}
},
{
type: 'view',
css: {
background: '#fff',
width: '750rpx',
height: '140rpx',
bottom: '0rpx',
left: '0rpx',
position: 'absolute'
},
views: [{
type: 'image',
src: avatarUrl,
css: {
position: 'absolute',
width: '94rpx',
height: '94rpx',
objectFit: "cover",
borderRadius: '50rpx',
bottom: '23rpx',
left: '20rpx'
}
},
{
type: 'text',
text: nickName,
css: {
position: 'absolute',
bottom: '70rpx',
left: '130rpx',
width: '350rpx',
fontSize: '32rpx',
fontWeight: '400',
color: '#000'
}
},
{
type: 'text',
text: '邀请您成为TA的业务员扫描二维码立即加入吧!',
css: {
position: 'absolute',
bottom: '25rpx',
left: '130rpx',
width: '750rpx',
fontSize: '26rpx',
color: '#999999',
}
}
],
},
{
type: 'image',
src: qr_code,
css: {
position: 'absolute',
width: '290rpx',
height: '290rpx',
bottom: '366rpx',
left: '228rpx',
background: '#ffffff',
borderRadius: qr_radius
}
},
]
}
//
this.$refs.painter.render(poster);
//
this.$refs.painter.canvasToTempFilePathSync({
fileType: "jpg",
quality: 1,
success: (res) => {
that.$util.hideAll()
this.src = res.tempFilePath
},
});
},
previewImage() {
let finalPath = this.src;
uni.previewImage({
current: finalPath,
urls: [finalPath]
})
},
async saveImage() {
await this.$util.checkAuth({
type: "writePhotosAlbum"
});
let filePath = this.src;
let [err, success] = await uni.saveImageToPhotosAlbum({
filePath
})
if (err) return;
uni.showToast({
icon: 'none',
title: '保存成功'
})
},
toPreviewSave() {
// #ifdef H5
this.previewImage()
// #endif
// #ifndef H5
this.saveImage()
// #endif
}
}
}
</script>
<style>
.code-img {
width: 750rpx;
height: 1280rpx;
}
</style>

View File

@ -0,0 +1,415 @@
<template>
<view class="order-pages" v-if="detail.id">
<view class="item-child flex-center pd-lg fill-base f-paragraph c-base" style="height:200rpx"
:style="{background:primaryColor}">
<view class="flex-1 flex-y-baseline f-md-title">{{statusType[detail.status]}}
<view class="f-desc ml-md" v-if="detail.status == 2">退款金额¥{{detail.refund_price}}</view>
</view>
</view>
<view class="item-child mt-md ml-lg mr-lg pd-lg fill-base radius-16">
<view class="flex-between pb-lg">
<view class="f-paragraph c-title max-380 ellipsis">服务内容</view>
</view>
<view class="flex-center" :class="[{'mb-lg':aindex != detail.order_goods.length -1}]"
v-for="(aitem,aindex) in detail.order_goods" :key="aindex">
<!-- #ifdef H5 -->
<view class="avatar lg radius-16">
<view class="h5-image avatar lg radius-16"
:style="{ backgroundImage : `url('${aitem.goods_cover}')`}">
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image mode="aspectFill" class="avatar lg radius-16" :src="aitem.goods_cover"></image>
<!-- #endif -->
<view class="flex-1 ml-md">
<view class="flex-between">
<view class="f-mini-title c-title text-bold max-380 ellipsis">
{{aitem.goods_name}}
</view>
<view class="c-paragraph">x{{aitem.num}}</view>
</view>
<view class="f-caption c-caption" v-if="aitem.material_price*1>0">物料费¥{{aitem.material_price}}
</view>
<view class="f-caption mt-md" style="color:#777">服务时长 {{aitem.time_long}}分钟</view>
<view class="f-paragraph c-warning text-bold mt-sm">¥{{aitem.goods_price}}
</view>
</view>
</view>
</view>
<view class="store-info mt-md ml-lg mr-lg pd-lg fill-base radius-16" v-if="detail.store_id">
<view class="f-mini-title c-title text-bold pb-md">
{{detail.store_info.title}}
</view>
<view class="flex-between">
<view class="flex-y-center" style="color: #303030;">
<i class="iconfont icondizhi1 mr-sm"></i>
<view class="c-title flex-1 mr-md">
<span>{{detail.store_info.address || `暂未设置门店地址`}}</span>
<span @tap.stop="$util.goUrl({url:detail.store_info.address,openType:'copy'})"
class="copy-btn span radius-5 f-icontext ml-sm"
:style="{color:primaryColor,borderColor:primaryColor}"
v-if="detail.store_info.address">复制</span>
</view>
</view>
<view class="flex-center">
<view @tap.stop="$util.goUrl({url:detail.store_info.phone,openType:'call'})"
class="item-icon rel flex-center radius-16">
<view class="item-icon radius-16 abs" :style="{background:primaryColor}"></view>
<i class="iconfont icondadianhua_1" :style="{color:primaryColor}"></i>
</view>
<view @tap.stop="toMap('store_info')" class="item-icon rel flex-center radius-16 ml-md"
v-if="detail.store_info.address">
<view class="item-icon radius-16 abs" :style="{background:primaryColor}"></view>
<i class="iconfont icondizhi_1" :style="{color:primaryColor}"></i>
</view>
</view>
</view>
</view>
<view class="order-agent-info store-info mt-md ml-lg mr-lg pd-lg fill-base f-paragraph radius-16">
<view class="flex-between">
<image mode="aspectFill" class="coach-img radius" :src="detail.coach_info.work_img"></image>
<view class="f-title text-bold max-450 ellipsis">
{{detail.coach_info ? detail.coach_info.coach_name : ''}}
</view>
</view>
<view class="flex-center mt-lg">
<view class="title">下单人</view>
<view class="text flex-1 ellipsis">{{detail.address_info.user_name}}</view>
</view>
<view class="flex-center mt-lg">
<view class="title">联系方式</view>
<view class="text flex-1 flex-between">
<view>
{{detail.address_info.mobile.substring(0,3)}}****{{detail.address_info.mobile.substring(7,11)}}
</view>
<view @tap.stop="toTel" class="item-icon rel flex-center radius-16">
<view class="item-icon radius-16 abs" :style="{background:primaryColor}"></view>
<i class="iconfont icondadianhua_1" :style="{color:primaryColor}"></i>
</view>
</view>
</view>
<view class="flex-warp mt-lg" v-if="!detail.store_id">
<view class="title">服务地址</view>
<view class="text flex-1 flex-between">
<view style="max-width: 350rpx;">
<span>{{`${detail.address_info.address}${detail.address_info.address_info}`}}</span>
<span @tap="toCopy" class="copy-btn fill radius-5 f-icontext ml-sm">复制</span>
</view>
<view @tap.stop="toMap('address_info')" class="item-icon rel flex-center radius-16">
<view class="item-icon radius-16 abs" :style="{background:primaryColor}"></view>
<i class="iconfont icondizhi_1" :style="{color:primaryColor}"></i>
</view>
</view>
</view>
<view class="flex-between mt-lg pt-lg b-1px-t">
<view class="title"></view>
<view class="flex-y-baseline f-paragraph c-black text-bold">总计<view class="c-warning">
¥{{detail.apply_price}}</view>
<view class="flex-y-center ml-md" v-if="detail.car_price*1>0">含车费
<view class="f-icontext">¥</view>{{detail.car_price}}
</view>
</view>
</view>
</view>
<view class="order-agent-info mt-md ml-lg mr-lg pd-lg fill-base radius-16">
<view class="flex-center">
<view class="title">退款单号</view>
<view class="text flex-1">
<view class="flex-between">
<view class="max-350 ellipsis">{{detail.order_code}}</view>
<view @tap="$util.goUrl( {url:`${detail.order_code}`,openType:'copy'})"
class="copy-btn flex-center radius-5 f-icontext"
:style="{color:primaryColor,borderColor:primaryColor}">
复制</view>
</view>
</view>
</view>
<view class="flex-center mt-lg" v-if="detail.out_refund_no">
<view class="title">微信退款单号</view>
<view class="text flex-1">
<view class="flex-between">
<view class="max-350 ellipsis">{{detail.out_refund_no}}</view>
<view @tap="$util.goUrl( {url:`${detail.out_refund_no}`,openType:'copy'})"
class="copy-btn flex-center radius-5 f-icontext"
:style="{color:primaryColor,borderColor:primaryColor}">复制</view>
</view>
</view>
</view>
<view class="flex-center mt-lg">
<view class="title">提交日期</view>
<view class="text flex-1 ellipsis">{{detail.create_time}}</view>
</view>
<view class="flex-center mt-lg" v-if="detail.status != 1">
<view class="title">审核日期</view>
<view class="text flex-1 ellipsis">{{detail.refund_time}}</view>
</view>
<view class="mt-lg">
<view class="title">退款原因</view>
<view class="text mt-sm">{{detail.text}}</view>
</view>
<view class="flex-column" v-if="detail.imgs && detail.imgs.length > 0">
<view class="title">上传图片</view>
<view class="flex-warp">
<block v-for="(item,index) in detail.imgs" :key="index">
<image @tap.top="previewImage(item,detail.imgs)" class="refund-img mt-md mr-md radius-10"
:src="item" />
</block>
</view>
</view>
</view>
<view class="space-max-footer"></view>
<fix-bottom-button @cancel="toConfirm(3)" @confirm="toConfirm(2)"
:text="[{text:'拒绝退款',type:'cancel'},{text:'同意退款',type:'confirm'}]" bgColor="#fff" :classType="2"
v-if="detail.status==1">
</fix-bottom-button>
<uni-popup ref="change_item" type="center" :custom="true">
<view class="common-popup-content fill-base pd-lg radius-34">
<view class="title">温馨提示</view>
<view class="desc">
你确认要操作{{statusType[popupInfo.type]}}?
</view>
<view class="mt-lg" v-if="popupInfo.type == 2">
<input v-model="popupInfo.price" type="digit"
class="input flex-y-center pl-lg pr-lg f-sm-title c-title radius-16"
placeholder-class="c-placeholder" placeholder="请输入退款金额" />
<view class="f-desc c-caption mt-md">
<view class="flex-y-center">实际可退款金额<view class="ml-sm c-warning">¥{{popupInfo.apply_price}}
</view>
</view>
<view>退款金额不能大于可退款金额</view>
</view>
</view>
<view class="button">
<view @tap.stop="$refs.change_item.close()" class="item-child">取消</view>
<view @tap.stop="confirmChangeOrder" class="item-child c-base"
:style="{background: primaryColor,color:'#fff'}">确定</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
components: {},
data() {
return {
options: {},
carType: {
0: '公交/地铁',
1: '出租车'
},
payType: {
1: '微信支付',
2: '余额支付',
3: '支付宝支付'
},
statusType: {
1: '退款申请中',
2: '同意退款',
3: '拒绝退款',
},
detail: {
pay_type: 0
},
lockTap: false,
popupInfo: {}
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
over_time_text() {
return new Date().getTime() + this.detail.end_time * 1000
}
}),
onLoad(options) {
let {
agent = 0
} = options
options.agent = agent * 1
this.options = options
this.initIndex()
},
methods: {
...mapActions(['getConfigInfo', 'getCoachInfo']),
...mapMutations(['updateTechnicianItem']),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
if (!this.configInfo.id || refresh) {
await this.getConfigInfo()
}
let {
id,
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
let data = await this.$api[methodKey].refundOrderInfo({
id
})
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
data.is_balance = data.balance * 1 > 0 ? 1 : 0
this.detail = data
},
initRefresh() {
this.initIndex(true)
},
// type: 2退3退
async toConfirm(type) {
let {
id,
apply_price
} = this.detail
this.popupInfo = {
id,
type,
apply_price,
price: type == 2 ? apply_price : '',
text: ''
}
this.$refs.change_item.open()
},
async confirmChangeOrder() {
let {
type,
price,
apply_price
} = this.popupInfo
let param = this.$util.pick(this.popupInfo, ['id', 'price', 'text'])
let reg = /^(([0-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2})))$/
if (type == 2 && (!price || !reg.test(price) || price * 1 > apply_price * 1)) {
this.$util.showToast({
title: !price ? '请输入退款金额' : !reg.test(price) ? '请输入正确的退款金额最多保留2位小数' : '退款金额不能大于可退款金额'
})
return
}
if (this.lockTap) return;
this.lockTap = true;
this.$util.showLoading()
let {
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
let methodModel = type == 2 ? 'passRefund' : 'noPassRefund'
try {
await this.$api[methodKey][methodModel](param)
this.$refs.change_item.close()
this.$util.showToast({
title: '操作成功'
})
this.lockTap = false;
this.updateTechnicianItem({
key: 'haveOperItem',
val: true
})
this.initRefresh()
this.$util.back()
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
return
}
},
//
async toTel() {
let {
id: order_id,
pay_type
} = this.detail
let {
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
if ([2, 3, 4, 5, 6].includes(pay_type)) {
let url = await this.$api[methodKey].getVirtualPhone({
order_id
})
this.$util.goUrl({
url,
openType: `call`
})
} else {
let msg = pay_type == 7 ? '服务结束' : '服务取消'
this.$util.showToast({
title: `${msg}不能联系客户哦`
})
}
},
//
toCopy() {
let {
address,
address_info
} = this.detail.address_info
let url = `${address}${address_info}`
this.$util.goUrl({
url,
openType: 'copy'
})
},
//
async toMap(key) {
let {
address,
address_info = '',
lat,
lng
} = this.detail[key]
await this.$util.checkAuth({
type: 'userLocation'
})
await uni.getLocation({
type: 'gcj02',
})
await uni.openLocation({
latitude: lat * 1,
longitude: lng * 1,
name: address_info ? `${address} ${address_info}` : address,
scale: 28
})
},
}
}
</script>
<style lang="scss">
.avatar.coath {
width: 94rpx;
height: 94rpx;
}
</style>

330
agent/pages/refund/list.vue Normal file
View File

@ -0,0 +1,330 @@
<template>
<view class="order-pages">
<fixed>
<view class="fill-base pt-lg pl-md pr-md pb-md">
<search @input="toSearch" type="input" :padding="0" :radius="30" height="70rpx"
placeholder="请输入系统订单号查询">
</search>
</view>
<tab @change="handerTabChange" :list="tabList" :activeIndex="activeIndex*1" :activeColor="primaryColor"
width="25%" height="100rpx"></tab>
<view class="b-1px-b"></view>
</fixed>
<view @tap.stop="goDetail(index)" class="item-child mt-md ml-lg mr-lg pd-lg fill-base radius-16"
v-for="(item,index) in list.data" :key="index">
<view class="flex-between pb-lg b-1px-b">
<view class="f-paragraph c-title max-450 ellipsis">订单号{{item.order_code}}</view>
<view class="f-caption text-bold"
:style="{color:item.status==1?subColor: item.status == 2 ? '#11C95E' : '#333'}">
{{statusType[item.status]}}
</view>
</view>
<view class="flex-center mb-lg" :class="[{'mt-lg':aindex==0}]" v-for="(aitem,aindex) in item.order_goods"
:key="aindex">
<!-- #ifdef H5 -->
<view class="avatar lg radius-16">
<view class="h5-image avatar lg radius-16"
:style="{ backgroundImage : `url('${aitem.goods_cover}')`}">
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image mode="aspectFill" class="avatar lg radius-16" :src="aitem.goods_cover"></image>
<!-- #endif -->
<view class="flex-1 ml-md">
<view class="flex-between">
<view class="f-mini-title c-title text-bold max-380 ellipsis">
{{aitem.goods_name}}
</view>
<view class="c-paragraph">x{{aitem.num}}</view>
</view>
<view class="mt-md f-caption c-caption max-450 ellipsis">
服务{{$t('action.attendantName')}}{{item.coach_info?item.coach_info.coach_name:'-'}}</view>
<view class="f-caption c-caption" style="margin-top: 5rpx;">服务时间{{item.start_time}}</view>
</view>
</view>
<view class="flex-between pt-lg b-1px-t">
<view class="flex-y-center f-desc c-title">总计
<view class="f-paragraph text-bold">¥{{item.apply_price}}</view>
</view>
<view class="flex-warp">
<!-- 退款申请中 -->
<block v-if="item.status == 1">
<button @tap.stop="toConfirm(index,3)" class="clear-btn order"
style="margin-left: 0;">拒绝退款</button>
<button @tap.stop="toConfirm(index,2)" class="clear-btn order"
:style="{color:'#fff',background:primaryColor,border:`1rpx solid ${primaryColor}`}">同意退款</button>
</block>
<!-- 同意/拒绝退款 -->
<view v-else>
<button class="clear-btn order"
:style="{color:'#fff',background:primaryColor,border:`1rpx solid ${primaryColor}`}">查看详情</button>
</view>
</view>
</view>
</view>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading" v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
<view class="space-footer"></view>
<uni-popup ref="change_item" type="center" :custom="true">
<view class="common-popup-content fill-base pd-lg radius-34">
<view class="title">温馨提示</view>
<view class="desc">
你确认要操作{{statusType[popupInfo.type]}}?
</view>
<view class="mt-lg" v-if="popupInfo.type == 2">
<input v-model="popupInfo.price" type="digit"
class="input flex-y-center pl-lg pr-lg f-sm-title c-title radius-16"
placeholder-class="c-placeholder" placeholder="请输入退款金额" />
<view class="f-desc c-caption mt-md">
<view class="flex-y-center">实际可退款金额<view class="ml-sm c-warning">¥{{popupInfo.apply_price}}
</view>
</view>
<view>退款金额不能大于可退款金额</view>
</view>
</view>
<view class="button">
<view @tap.stop="$refs.change_item.close()" class="item-child">取消</view>
<view @tap.stop="confirmChangeOrder" class="item-child c-base"
:style="{background: primaryColor,color:'#fff'}">确定</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from "vuex"
export default {
components: {},
data() {
return {
options: {},
activeIndex: 0,
tabList: [{
title: '全部',
id: 0
}, {
title: '退款申请中',
id: 1,
}, {
title: '同意退款',
id: 2
}, {
title: '拒绝退款',
id: 3
}],
statusType: {
1: '退款申请中',
2: '同意退款',
3: '拒绝退款',
},
param: {
page: 1,
},
list: {
data: []
},
loading: true,
popupInfo: {},
lockTap: false
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
onLoad(options) {
let {
agent = 0,
bell = 0,
tab = 0
} = options
options.agent = agent * 1
this.options = options
this.activeIndex = tab
uni.setNavigationBarTitle({
title: bell == 1 ? '加钟退款' : '服务退款'
})
this.initIndex()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
this.getList();
},
methods: {
...mapMutations(['updateTechnicianItem']),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
await this.getList()
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
},
initRefresh() {
this.param.page = 1
this.initIndex(true)
},
toSearch(val) {
this.param.page = 1
this.param.order_code = val
this.getList()
},
async getList() {
let {
list: oldList,
param,
tabList,
activeIndex
} = this
param.status = tabList[activeIndex].id
let {
bell = 0,
agent
} = this.options
param.is_add = bell
let methodKey = agent ? 'agent' : 'admin'
let newList = await this.$api[methodKey].refundOrderList(param);
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
this.loading = false
this.$util.hideAll()
},
handerTabChange(index) {
this.activeIndex = index;
this.$util.showLoading()
this.param.page = 1;
this.getList()
},
// type: 2退3退
async toConfirm(index, type) {
let {
id,
apply_price
} = this.list.data[index]
this.popupInfo = {
index,
id,
type,
apply_price,
price: type == 2 ? apply_price : '',
text: ''
}
this.$refs.change_item.open()
},
async confirmChangeOrder() {
let {
type,
index,
price,
apply_price
} = this.popupInfo
let param = this.$util.pick(this.popupInfo, ['id', 'price', 'text'])
let reg = /^(([0-9][0-9]*)|(([0]\.\d{1,2}|[1-9][0-9]*\.\d{1,2})))$/
if (type == 2 && (!price || !reg.test(price) || price * 1 > apply_price * 1)) {
this.$util.showToast({
title: !price ? '请输入退款金额' : !reg.test(price) ? '请输入正确的退款金额最多保留2位小数' : '退款金额不能大于可退款金额'
})
return
}
let {
activeIndex
} = this
if (this.lockTap) return;
this.lockTap = true;
this.$util.showLoading()
let {
agent
} = this.options
let methodKey = agent ? 'agent' : 'admin'
let methodModel = type == 2 ? 'passRefund' : 'noPassRefund'
try {
await this.$api[methodKey][methodModel](param)
this.$refs.change_item.close()
this.$util.showToast({
title: '操作成功'
})
if (activeIndex == 0) {
this.list.data[index].status = type
} else {
this.list.data.splice(index, 1)
}
this.lockTap = false;
this.$util.hideAll()
this.updateTechnicianItem({
key: 'haveOperItem',
val: true
})
if (activeIndex == 0) return
await this.getList(true)
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
return
}
},
//
goDetail(index) {
let {
id
} = this.list.data[index]
let {
agent
} = this.options
let url = `/agent/pages/refund/detail?id=${id}&agent=${agent}`
this.$util.goUrl({
url
})
},
//
toTel() {
let {
mobile: url
} = this.configInfo
this.$util.goUrl({
url,
openType: `call`
})
}
}
}
</script>
<style lang="scss">
</style>

View File

@ -0,0 +1,775 @@
<template>
<view class="apply-pages" v-if="isLoad">
<view class="apply-form">
<view class="fill-base radius-16">
<view @tap.stop="toChooseUser" class="flex-between pl-lg pr-lg b-1px-b">
<view class="item-text">关联用户</view>
<view class="item-input text">
<view class="flex-between">
{{form.user_id ? form.nickName :'请选择'}}
<i class="iconfont icon-right ml-sm" style="font-size: 28rpx;"></i>
</view>
</view>
</view>
<view class="flex-between pl-lg pr-lg b-1px-b">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>姓名</view>
<input v-model="form.coach_name" type="text" class="item-input flex-1" maxlength="20"
:placeholder="rule[0].errorMsg" />
</view>
<view class="flex-between pl-lg pr-lg b-1px-b">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>性别</view>
<view class="item-input flex-1 flex-y-center">
<view @tap.stop="form.sex = index" class="flex-y-center" :class="[{'mr-lg':index==0}]"
:style="{color:form.sex == index ? primaryColor:''}" v-for="(item,index) in ['男','女']"
:key="index"><i class="iconfont icon-xuanze mr-sm"
:class="[{'icon-xuanze-fill':form.sex == index}]"></i>{{item}}
</view>
</view>
</view>
<view class="flex-between ml-lg mr-lg b-1px-b">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>生日</view>
<view class="item-input text flex-1">
<picker @change="pickerChange($event,'birthday')" mode="date" :end="endYear"
:value="form.birthday">
<view class="flex-between">
{{form.birthday||'请选择'}}
<i class="iconfont icon-right ml-sm" style="font-size: 28rpx;"></i>
</view>
</picker>
</view>
</view>
<view class="flex-between pl-lg pr-lg b-1px-b">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>手机号</view>
<input v-model="form.mobile" type="text" class="item-input flex-1"
:placeholder="rule[2].errorMsg" />
</view>
<view class="flex-between pl-lg pr-lg b-1px-b">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>从业年份</view>
<input v-model="form.work_time" type="number" class="item-input flex-1"
:placeholder="rule[3].errorMsg" />
</view>
<view class="flex-between pl-lg pr-lg b-1px-b">
<view class="item-text flex-y-center" style="width:230rpx"><i
class="iconfont icon-required c-warning"></i>意向工作城市</view>
<view class="item-input text">
<picker @change="pickerChange($event,'city')" :value="cityIndex" :range="cityList"
range-key="title">
<view class="flex-between">
{{cityIndex!=-1?cityList[cityIndex].title:'请选择'}}
<i class="iconfont icon-right ml-sm" style="font-size: 28rpx;"></i>
</view>
</picker>
</view>
</view>
<view class="flex-between pl-lg pr-lg b-1px-b" v-if="configInfo.plugAuth.store && storeList.length>0">
<view class="item-text">挂靠门店</view>
<view class="item-input text">
<picker @change="pickerChange($event,'store')" :value="storeIndex" :range="storeList"
range-key="title">
<view class="flex-between">
<view class="max-400 ellipsis">{{storeIndex!=-1?storeList[storeIndex].title:'请选择'}}
</view>
<i class="iconfont icon-right ml-sm" style="font-size: 28rpx;"></i>
</view>
</picker>
</view>
</view>
<view class="flex-between pl-lg pr-lg">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>所在地址</view>
<view class="item-input text flex-1">
<view @tap.stop="toChooseLocation" class="flex-between">
<view>{{form.address || `点击右边图标设置`}}</view>
<i class="iconfont iconjuli ml-sm" :style="{color: primaryColor}"></i>
</view>
</view>
</view>
</view>
<view class="fill-base mt-md radius-16">
<view class="flex-between pl-lg pr-lg">
<view class="item-text flex-y-center" style="width:auto"><i
class="iconfont icon-required c-warning"></i>{{$t('action.attendantName')}}简介</view>
<input :disabled="true" type="text" class="item-input flex-1" />
</view>
<textarea v-model="form.text" class="item-textarea pd-lg" maxlength="300"
:placeholder="'输入'+$t('action.attendantName')+'简介'" />
<view class="text-right pb-lg pr-lg">
{{form.text.length>300?300:form.text.length}}/300
</view>
</view>
<view class="fill-base mt-md radius-16">
<view class="flex-between pl-lg pr-lg">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>身份证号</view>
<input v-model="form.id_code" type="text" class="item-input flex-1"
:placeholder="rule[7].errorMsg" />
</view>
</view>
<view class="fill-base mt-md radius-16">
<view class="flex-between pl-lg pr-lg">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>身份证照片</view>
<view class="item-input flex-1">图片大小不超过10M</view>
</view>
<view class="flex-between pl-lg pr-lg pb-md">
<upload @upload="imgUpload" :imagelist="form.id_card" imgtype="id_card" imgclass="md" text="身份证人像面"
:imgsize="1"></upload>
<upload @upload="imgUpload" :imagelist="form.id_card_fan" imgtype="id_card_fan" imgclass="md"
text="身份证国徽面" :imgsize="1"></upload>
</view>
<view class="flex-between pl-lg pr-lg pb-md">
<upload @upload="imgUpload" :imagelist="form.id_card_people" imgtype="id_card_people" imgclass="md"
text="手持身份证照片" :imgsize="1"></upload>
</view>
</view>
<view class="fill-base mt-md radius-16">
<view class="flex-between pl-lg pr-lg">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>资格证书</view>
<view class="item-input flex-1">图片大小不超过10M</view>
</view>
<view class="flex-between pl-lg pr-lg pb-md">
<upload @upload="imgUpload" @del="imgUpload" :imagelist="form.license" imgtype="license" text="上传图片"
:imgsize="15">
</upload>
</view>
</view>
<view class="fill-base mt-md radius-16">
<view class="flex-between pl-lg pr-lg">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>工作形象照</view>
<view class="item-input flex-1">图片建议尺寸: 750 * 750大小不超过10M</view>
</view>
<view class="flex-between pl-lg pr-lg pb-md">
<upload @upload="imgUpload" :imagelist="form.work_img" imgtype="work_img" text="上传图片" :imgsize="1">
</upload>
</view>
</view>
<view class="fill-base mt-md radius-16">
<view class="flex-between pl-lg pr-lg">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>个人生活照</view>
<view class="item-input flex-1">图片建议尺寸: 750 * n大小不超过10M</view>
</view>
<view class="flex-between pl-lg pr-lg pb-md">
<upload @upload="imgUpload" @del="imgUpload" :imagelist="form.self_img" filetype="picture"
imgtype="self_img" text="上传图片" :imgsize="9">
</upload>
</view>
</view>
<view class="fill-base mt-md radius-16">
<view class="flex-between pl-lg pr-lg">
<view class="item-text">个人视频介绍</view>
<view class="item-input flex-1">视频大小不超过50M</view>
</view>
<view class="flex-between pl-lg pr-lg pb-md">
<upload @upload="imgUpload" @del="imgUpload" :imagelist="form.video" filetype="video"
imgtype="video" text="上传视频" :imgsize="1">
</upload>
</view>
</view>
<view class="fill-base mt-md radius-16">
<view class="flex-between pl-lg pr-lg b-1px-b">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>虚拟订单</view>
<input v-model="form.order_num" type="number" class="item-input flex-1"
:placeholder="rule[14].errorMsg" />
</view>
<view class="flex-between pl-lg pr-lg b-1px-b">
<view class="item-text flex-y-center"><i class="iconfont icon-required c-warning"></i>是否接单</view>
<view class="item-input flex-1 flex-y-center">
<view @tap.stop="toSetItem(index)" class="flex-y-center" :class="[{'mr-lg':item.id==1}]"
:style="{color:form.is_work == item.id ? primaryColor:''}" v-for="(item,index) in workList"
:key="index"><i class="iconfont mr-sm"
:class="[{'icon-xuanze':form.is_work != item.id},{'icon-radio-fill':form.is_work == item.id}]"></i>{{item.title}}
</view>
</view>
</view>
<view class="flex-between pd-lg" v-if="form.is_work===1">
<view @tap.stop="toShowTime('start_time')" class="item-time flex-center flex-column">
<view>开始时间</view>
<view class="mt-sm" :style="{color:form.start_time ? primaryColor : '#999'}">
{{form.start_time || '选择时间'}}
</view>
</view>
<view @tap.stop="toShowTime('end_time')" class="item-time flex-center flex-column b-1px-l">
<view>结束时间</view>
<view class="mt-sm" :style="{color:form.end_time ? primaryColor : '#999'}">
{{form | handleStartEndTime(toDay)}}{{form.end_time || '选择时间'}}
</view>
</view>
</view>
</view>
<!-- TODO 合同 -->
<view @tap.stop="toFddSign" class="fill-base mt-md radius-16"
v-if="fdd_agreement && fdd_agreement.hasOwnProperty('viewpdf_url')">
<view class="flex-between pl-lg pr-lg b-1px-b">
<view class="item-text">电子签约</view>
<view class="item-input flex-1" :style="{color:primaryColor}">
查看签约合同
</view>
</view>
</view>
</view>
<view class="space-max-footer"></view>
<fix-bottom-button @confirm="submit" :text="[{text:'确定提交',type:'confirm',isAuth:true}]" bgColor="#fff">
</fix-bottom-button>
<w-picker :visible.sync="showTime" mode="time" :value="toDayTime" :current="false" :second="false"
:themeColor="primaryColor" @confirm="onConfirm" ref="time"></w-picker>
</view>
</template>
<script>
import $util from "@/utils/index.js"
import wPicker from "@/components/w-picker/w-picker.vue";
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
components: {
wPicker
},
data() {
return {
isLoad: false,
options: {},
cityList: [],
cityIndex: -1,
storeList: [],
storeIndex: -1,
toDay: '',
toDayTime: '',
showKey: '',
showTime: false,
workList: [{
id: 1,
title: '接单'
}, {
id: 0,
title: '休息'
}],
fdd_agreement: {},
form: {
id: 0,
user_id: '',
nickName: '',
coach_name: '', //
mobile: '', //
sex: 0, //
birthday: '',
work_time: '', //
city_id: '', // id
store_id: '', // id
lng: '',
lat: '',
address: '', //
text: '', //
id_code: '', //
id_card: [], //
id_card_fan: [], //
id_card_people: [], //
license: [], //
work_img: [], //
self_img: [], //
city_id: '', // id
video: [],
order_num: 0,
is_work: 1,
start_time: '00:00',
end_time: '23:59'
},
rule: [{
name: "coach_name",
checkType: "isNotNull",
errorMsg: "输入您的姓名",
regType: 2
}, {
name: "birthday",
checkType: "isNotNull",
errorMsg: "请选择您的生日",
regType: 2
},
{
name: "mobile",
checkType: "isMobile",
errorMsg: "输入手机号"
}, {
name: "work_time",
checkType: "isNotNull",
errorMsg: "请输入从业年份例如5"
}, {
name: "city_id",
checkType: "isNotNull",
errorMsg: "请选择意向工作城市"
},
{
name: "address",
checkType: "isNotNull",
errorMsg: "请选择所在地址"
}, {
name: "text",
checkType: "isNotNull",
errorMsg: "请输入" + this.$t('action.attendantName') + "简介",
regType: 2
}, {
name: "id_code",
checkType: "isIdCard",
errorMsg: "输入您的身份证号码"
},
{
name: "id_card",
checkType: "isNotNull",
errorMsg: "请上传身份证人像面"
},
{
name: "id_card_fan",
checkType: "isNotNull",
errorMsg: "请上传身份证国徽面"
},
{
name: "id_card_people",
checkType: "isNotNull",
errorMsg: "请上传手持身份证照片"
},
{
name: "license",
checkType: "isNotNull",
errorMsg: "请上传资格证书"
},
{
name: "work_img",
checkType: "isNotNull",
errorMsg: "请上传工作形象照"
},
{
name: "self_img",
checkType: "isNotNull",
errorMsg: "请上传个人生活照"
},
{
name: "order_num",
checkType: "isNumber",
errorMsg: "请输入虚拟订单量",
regType: 2
},
],
have_user_id: false,
lockTap: false
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
location: state => state.user.location,
}),
async onLoad(options) {
let {
id = 0
} = options
this.options = options
this.$util.showLoading()
await this.initIndex()
let {
coach_status
} = this
uni.setNavigationBarTitle({
title: id ? '编辑' : '新增' + this.$t(
'action.attendantName')
})
this.isLoad = true
},
methods: {
...mapActions(['getConfigInfo', 'getUserInfo']),
...mapMutations(['updateUserItem']),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
if (!this.configInfo.id || !this.configInfo.hasOwnProperty(
'plugAuth') || (this.configInfo.hasOwnProperty(
'plugAuth') && !this.configInfo.plugAuth.hasOwnProperty(
'store')) || refresh) {
await this.getConfigInfo()
}
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
let cur_time = new Date(Math.ceil(new Date().getTime()))
this.toDay = this.$util.formatTime(cur_time, 'YY-M-D')
this.toDayTime = this.$util.formatTime(cur_time, 'h:m')
await Promise.all([this.getCityList(), this.getStoreList()])
let {
id = 0
} = this.options
if (!id) {
this.$util.hideAll()
return
}
let data = await this.$api.agent.coachInfo({
id
})
data.id_card = data.id_card.map(item => {
return {
path: item
}
})
data.id_card_fan = [data.id_card[1]]
data.id_card_people = [data.id_card[2]]
data.id_card.splice(1, 3)
data.license = data.license.map(item => {
return {
path: item
}
})
data.work_img = [{
path: data.work_img
}]
data.self_img = data.self_img.map(item => {
return {
path: item
}
})
data.video = data.video && data.video.length > 0 ? [{
path: data.video
}] : []
this.cityIndex = this.cityList.findIndex(item => {
return item.id == data.city_id
})
this.storeIndex = this.storeList.findIndex(item => {
return item.id == data.store_id
})
data.birthday = data.birthday ? this.$util.formatTime(data.birthday * 1000, 'YY-M-D') : ''
for (let key in this.form) {
this.form[key] = data[key]
}
this.have_user_id = data.id && data.user_id
this.fdd_agreement = data.fdd_agreement
this.$util.hideAll()
},
initRefresh() {
this.initIndex(true)
},
async getStoreList() {
let {
store = false
} = this.configInfo.plugAuth
if (store) {
let data = await this.$api.agent.storeSelect()
data.unshift({
id: 0,
title: '不挂靠门店'
})
this.storeList = data
}
},
async getCityList() {
let {
location
} = this
if (!location.lat) {
// #ifdef H5
if (this.$jweixin.isWechat()) {
this.$util.showLoading()
// await this.$jweixin.initJssdk();
await this.$jweixin.wxReady2();
let {
latitude: lat = 0,
longitude: lng = 0
} = await this.$jweixin.getWxLocation()
location = {
lng,
lat,
address: '定位失败',
province: '',
city: '',
district: ''
}
if (lat && lng) {
let key = `${lat},${lng}`
let data = await this.$api.base.getMapInfo({
location: key
})
let {
status,
result
} = JSON.parse(data)
if (status == 0) {
let {
address,
address_component
} = result
let {
province,
city,
district
} = address_component
location = {
lng,
lat,
address,
province,
city,
district
}
}
}
}
// #endif
// #ifndef H5
location = await this.$util.getBmapLocation()
// #endif
this.updateUserItem({
key: 'location',
val: location
})
}
let {
lng = 0,
lat = 0
} = location
if (lat && lng) {
let city = await this.$api.base.getCity({
lng,
lat
})
this.$util.hideAll()
this.cityList = city
this.cityIndex = city.length > 0 ? 0 : -1
this.form.city_id = city.length > 0 ? city[0].id : ''
}
},
async toFddSign() {
this.updateUserItem({
key: 'fddExtsign',
val: ''
})
let {
viewpdf_url = ''
} = this.fdd_agreement
this.updateUserItem({
key: 'fddExtsign',
val: viewpdf_url
})
this.$util.goUrl({
url: `/user/pages/common/web?url=fddExtsign`
})
},
pickerChange(e, key) {
let ind = e.target.value
if (key === 'birthday') {
let unix = this.$util.DateToUnix(ind)
if (unix > new Date(Math.ceil(new Date().getTime())) / 1000) {
this.$util.showToast({
title: `不能选择未来时间哦`
})
return
}
this.form[key] = ind
return
}
this[`${key}Index`] = ind
this.form[`${key}_id`] = this[`${key}List`][ind].id
},
imgUpload(e) {
let {
imagelist,
imgtype
} = e;
this.form[imgtype] = imagelist;
},
toSetItem(index) {
let {
id
} = this.workList[index]
this.form.is_work = id
},
toShowTime(key) {
this.showKey = key
this.showTime = true
},
onConfirm(val) {
this.form[this.showKey] = val.result;
},
//
async toChooseLocation(e) {
await this.$util.checkAuth({
type: 'userLocation'
})
let {
lat: locaLat = '',
lng: locaLng = ''
} = this.location
let {
id = 0,
lat: addrLat,
lng: addrLng
} = this.form
if (id) {
locaLat = addrLat
locaLng = addrLng
}
let param = {}
if (!locaLat && !locaLng) {
// #ifdef H5
if (this.$jweixin.isWechat()) {
this.$util.showLoading()
await this.$jweixin.wxReady2();
let {
latitude,
longitude
} = await this.$jweixin.getWxLocation()
locaLat = latitude
locaLng = longitude
}
// #endif
// #ifdef APP-PLUS
let location = await this.$util.getBmapLocation()
locaLat = location.lat
locaLng = location.lng
// #endif
}
// #ifndef MP-WEIXIN
param = {
latitude: locaLat,
longitude: locaLng
}
// #endif
let [, {
address = '',
longitude,
latitude
}] = await uni.chooseLocation(param);
if (!address) return
this.form.address = address
this.form.lng = longitude
this.form.lat = latitude
},
toChooseUser() {
let {
have_user_id
} = this
if (have_user_id) return
this.$util.goUrl({
url: `/agent/pages/technician/user`
})
},
//
validate(param) {
let validate = new this.$util.Validate();
this.rule.map(item => {
let {
name,
} = item
validate.add(param[name], item);
})
let message = validate.start();
return message;
},
async submit() {
let param = this.$util.deepCopy(this.form)
let arr = ['id_card', 'id_card_fan', 'id_card_people', 'work_img', 'video']
arr.map(item => {
param[item] = param[item].length > 0 ? param[item][0].path : ''
})
param.license = param.license.map(item => {
return item.path
})
param.self_img = param.self_img.map(item => {
return item.path
})
let msg = this.validate(param);
if (msg) {
this.$util.showToast({
title: msg
});
return;
}
let {
is_work: work = 0,
start_time: start,
end_time: end
} = param
if (work && (!start || !end)) {
this.$util.showToast({
title: !start ? '请选择开始时间' : '请选择结束时间'
})
return
}
param.birthday = this.$util.DateToUnix(param.birthday)
param.id_card = [param.id_card, param.id_card_fan, param.id_card_people]
delete param.id_card_fan
delete param.id_card_people
delete param.nickName
if (param.id) {
delete param.admin_id
}
if (this.lockTap) return
this.lockTap = true
this.$util.showLoading()
try {
let {
is_edit = 0
} = this.options
let methodModel = param.id ? 'coachDataUpdate' : 'coachApply'
await this.$api.agent[methodModel](param)
this.$util.hideAll()
this.$util.showToast({
title: `提交成功`
})
setTimeout(() => {
this.$util.back()
this.$util.goUrl({
url: 1,
openType: `navigateBack`
})
}, 2000)
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
}
}
},
filters: {
handleStartEndTime(val, toDay) {
let text = ''
let {
start_time,
end_time
} = val
let start = `${toDay} ${start_time}`
let end = `${toDay} ${end_time}`
if (start_time && end_time && $util.DateToUnix(start) >= $util.DateToUnix(end)) {
text = '次日'
}
return text
}
}
}
</script>
<style lang="scss">
.item-time {
width: 50%;
}
</style>

View File

@ -0,0 +1,284 @@
<template>
<view class="agent-technician-list" v-if="isLoad">
<fixed>
<view class="search-info fill-base pt-lg pb-md">
<view class="flex-center pl-lg pr-lg pb-md">
<view @tap.stop="$util.goUrl({url:`/agent/pages/technician/apply`})"
class="dynamic-btn flex-center f-caption mr-lg radius"
:style="{color:primaryColor,border:`1rpx solid ${primaryColor}`}">
<i class="iconfont icon-jia-bold"></i>
添加
</view>
<view class="flex-1">
<search @input="toSearch" type="input" :padding="0" :radius="30" backgroundColor="#F0F0F0"
:placeholder="placeholder">
</search>
</view>
</view>
<view style="padding-right:30rpx">
<tab @change="handerTabChange" :isLine="true" :list="tabList" :activeIndex="activeIndex*1"
color="#9D9D9D" :activeColor="primaryColor" :width="100/tabList.length + '%'" height="100rpx"
:numberType="2"></tab>
</view>
</view>
</fixed>
<view @tap.stop="goDetail(index)" class="list-item fill-base flex-warp mt-md ml-md mr-md pd-lg radius-16"
v-for="(item,index) in list.data" :key="index">
<view>
<view class="avatar rel">
<!-- #ifdef H5 -->
<view class="avatar radius">
<view class="h5-image avatar radius" :style="{ backgroundImage : `url('${item.work_img}')`}">
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image mode="aspectFill" class="avatar radius" :src="item.work_img">
</image>
<!-- #endif -->
</view>
<view class="flex-center">
<view class="item-tag flex-center f-icontext c-base radius-20"
:class="[{'have-user':item.auth_status}]">
{{authStatusType[item.auth_status]}}
</view>
</view>
</view>
<view class="flex-1 ml-md">
<view class="flex-between">
<view class="f-icontext c-paragraph">ID{{item.id}}</view>
<view class="f-paragraph"
:style="{color:item.is_update==1?primaryColor:item.status==4?subColor:item.status==3?'#E82F21':'#999999'}">
{{statusType[item.status]}}
</view>
</view>
<view class="flex-y-center pb-lg b-1px-b">
<view class="f-paragraph c-black text-bold max-400 ellipsis">{{item.coach_name}}</view>
<view class="status-btn flex-center f-icontext ml-md"
:style="{color:item.admin_add==1?primaryColor:subColor,border:`1rpx solid ${item.admin_add==1?primaryColor:subColor}`}">
{{item.admin_add==1?'平台':'用户'}}
</view>
</view>
<view class="f-icontext c-paragraph mt-md">{{item.mobile}}</view>
<view class="f-icontext c-paragraph mt-sm">
所属代理商{{item.admin_name || '-'}}{{item.admin_id?` (${cityType[item.city_type]})`:''}}</view>
<view class="f-icontext c-paragraph mt-sm">申请时间{{item.create_time}}</view>
<view class="flex-between mt-md" v-if="!item.is_update">
<view></view>
<view class="edit-btn flex-center f-desc radius"
:style="{color:primaryColor,border:`1rpx solid ${primaryColor}`}">编辑</view>
</view>
</view>
</view>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading" v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
<view class="space-footer"></view>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
data() {
return {
options: {},
placeholder: '请输入' + this.$t('action.attendantName') + '姓名/手机号',
tabList: [{
id: 0,
title: '全部',
number: 0
}, {
id: 1,
title: '申请中',
number: 0
}, {
id: 2,
title: '已授权',
number: 0
}, {
id: 3,
title: '已驳回',
number: 0
}, {
id: 4,
title: '重新审核',
number: 0
}],
activeIndex: 0,
authStatusType: {
0: '未认证',
1: '认证中',
2: '已认证',
3: '认证失败'
},
statusType: {
1: '申请中',
2: '已授权',
3: '取消授权',
4: '已驳回'
},
cityType: {
1: '城市',
2: '区县',
3: '省'
},
loading: true,
isLoad: false,
param: {
page: 1,
limit: 10,
status: 0
},
list: {
data: []
}
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
onLoad(options) {
this.options = options
uni.setNavigationBarTitle({
title: this.$t('action.attendantName') + '管理'
})
this.initIndex()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
this.getList();
},
methods: {
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
this.getList();
},
handerTabChange(index) {
this.activeIndex = index
this.param.status = index
this.getList();
},
initRefresh() {
this.param.page = 1
this.initIndex(true)
},
toSearch(val) {
this.param.page = 1
this.param.name = val
this.getList()
},
async getList() {
this.$util.showLoading()
let {
list: oldList,
param
} = this
let newList = await this.$api.agent.coachList(param)
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
this.tabList[1].number = newList.ing
this.isLoad = true
this.loading = false
this.$util.hideAll()
},
goDetail(index) {
let {
id,
is_update
} = this.list.data[index]
if (is_update) return
this.$util.goUrl({
url: `/agent/pages/technician/apply?id=${id}`
})
}
}
}
</script>
<style lang="scss">
.agent-technician-list {
.search-info {
width: 100%;
.dynamic-btn {
width: 134rpx;
height: 56rpx;
transform: rotateZ(360deg);
.iconfont {
font-size: 24rpx;
margin-right: 6rpx;
}
}
}
.list-item {
.avatar {
width: 124rpx;
height: 124rpx;
}
.item-tag {
width: 100rpx;
height: 32rpx;
color: #4A4A4A;
background: #D8D8D8;
margin-top: 19rpx;
margin-bottom: 6rpx;
}
.have-user {
color: #EBDDB1;
background: linear-gradient(270deg, #4C545A 0%, #282B34 100%);
}
.status-btn {
width: 60rpx;
height: 32rpx;
border-radius: 5rpx;
transform: rotateZ(360deg);
}
.edit-btn {
width: 120rpx;
height: 50rpx;
transform: rotateZ(360deg);
}
}
}
</style>

View File

@ -0,0 +1,167 @@
<template>
<view>
<fixed>
<search @input="toSearch" type="input" :padding="30" :radius="0" placeholder="请输入用户昵称/手机号查找">
</search>
</fixed>
<view @tap.stop="toChangeItem(index)"
class="list-item fill-base pt-lg pb-lg pl-md pr-lg flex-center mt-md ml-md mr-md radius-16"
v-for="(item,index) in list.data" :key="index">
<i class="iconfont mr-md"
:class="[{'icon-xuanze':check_user.id!=item.id},{'icon-radio-fill':check_user.id==item.id}]"
:style="{color:check_user.id==item.id?primaryColor:''}"></i>
<view class="flex-1 flex-center">
<image mode="aspectFill" class="avatar radius" :src="item.avatarUrl"></image>
<view class="flex-1 ml-md">
<view class="f-paragraph c-title text-bold ellipsis">{{item.nickName}}</view>
<view class="phone" v-if="item.phone">{{item.phone}}</view>
</view>
</view>
</view>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading" v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
<view class="space-max-footer"></view>
<fix-bottom-button @confirm="toConfirm" :text="[{text:'确定',type:'confirm'}]" bgColor="#fff">
</fix-bottom-button>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
data() {
return {
loading: true,
isLoad: false,
param: {
page: 1,
limit: 10,
status: 0
},
list: {
data: []
},
check_user: {}
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
onLoad() {
let {
user_id,
nickName
} = this.$util.getPage(-1).form
this.check_user = {
id: user_id,
nickName
}
this.initIndex()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
this.getList();
},
methods: {
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
this.getList();
},
handerTabChange(index) {
this.activeIndex = index
this.param.status = index
this.getList();
},
toSearch(val) {
this.param.page = 1
this.param.nickName = val
this.check_user = {}
this.getList()
},
initRefresh() {
this.param.page = 1
this.initIndex(true)
},
async getList() {
this.$util.showLoading()
let {
list: oldList,
param
} = this
let newList = await this.$api.agent.coachUserList(param)
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
this.isLoad = true
this.loading = false
this.$util.hideAll()
},
toChangeItem(index) {
this.check_user = this.list.data[index]
},
toConfirm() {
let {
id,
nickName
} = this.check_user
if (!id) {
this.$util.showToast({
title: `请选择用户`
})
return
}
this.$util.getPage(-1).form.user_id = id
this.$util.getPage(-1).form.nickName = nickName || `用户${id}`
this.$util.goUrl({
url: 1,
openType: `navigateBack`
})
}
}
}
</script>
<style lang="scss">
.list-item {
.avatar {
width: 72rpx;
height: 72rpx;
}
.phone {
font-size: 25rpx;
color: #ADADAD;
}
}
</style>

38
androidPrivacy.json Normal file
View File

@ -0,0 +1,38 @@
{
"version" : "1.1.2",
"prompt" : "template",
"title" : "服务协议和隐私政策",
"message" : "  请你务必审慎阅读、充分理解“服务协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/><br/>  因本产品首页需根据用户定位信息来推荐附近的热门店铺因此进入APP首页将会弹出获取定位授权。<br/><br/>  你可阅读<a href=\"https://tianjin.tianjinhualong.cn/information.html\">《服务协议》</a>和<a href=\"https://tianjin.tianjinhualong.cn/protocol.html\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
"buttonAccept" : "同意并接受",
"buttonRefuse" : "暂不同意",
"hrefLoader" : "system|default",
"backToExit" : "true",
"second" : {
"title" : "确认提示",
"message" : "  进入应用前,你需先同意<a href=\"https://tianjin.tianjinhualong.cn/information.html\">《服务协议》</a>和<a href=\"https://tianjin.tianjinhualong.cn/protocol.html\">《隐私政策》</a>,否则将退出应用。",
"buttonAccept" : "同意并继续",
"buttonRefuse" : "退出应用"
},
"disagreeMode" : {
"support" : false,
"loadNativePlugins" : false,
"visitorEntry" : false,
"showAlways" : false
},
"styles" : {
"backgroundColor" : "#ffffff",
"borderRadius" : "15px",
"title" : {
"color" : "#000000"
},
"buttonAccept" : {
"color" : "#F3A664"
},
"buttonRefuse" : {
"color" : "#009254"
},
"buttonVisitor" : {
"color" : "#6A5ACD"
}
}
}

9
api/index.js Normal file
View File

@ -0,0 +1,9 @@
const files = require.context('./modules', false, /\.js$/)
const modules = {}
files.keys().forEach(key => {
modules[key.replace(/(\.\/|\.js)/g, '')] = files(key).default
})
export default{
...modules
}

57
api/modules/admin.js Normal file
View File

@ -0,0 +1,57 @@
import {
req
} from '../../utils/req.js';
export default {
// 首页
index(param) {
return req.get("mobilenode/app/IndexAdminOrder/index", param)
},
// 拨打客户电话
getVirtualPhone(param) {
return req.post("mobilenode/app/IndexAdminOrder/getVirtualPhone", param)
},
// 编辑通知状态
noticeUpdate(param) {
return req.post("mobilenode/app/IndexAdminOrder/noticeUpdate", param)
},
// 订单列表
orderList(param) {
return req.get("mobilenode/app/IndexAdminOrder/orderList", param)
},
// 订单详情
orderInfo(param) {
return req.get("mobilenode/app/IndexAdminOrder/orderInfo", param)
},
// 修改订单状态
adminUpdateOrder(param) {
return req.post("mobilenode/app/IndexAdminOrder/adminUpdateOrder", param)
},
// 退款列表
refundOrderList(param) {
return req.get("mobilenode/app/IndexAdminOrder/refundOrderList", param)
},
// 退款详情
refundOrderInfo(param) {
return req.get("mobilenode/app/IndexAdminOrder/refundOrderInfo", param)
},
// 同意退款
passRefund(param) {
return req.post("mobilenode/app/IndexAdminOrder/passRefund", param)
},
// 拒绝退款
noPassRefund(param) {
return req.post("mobilenode/app/IndexAdminOrder/noPassRefund", param)
},
// 代理商列表
adminSelect(param) {
return req.get("mobilenode/app/IndexAdminOrder/adminSelect", param)
},
// 更换技-师
orderChangeCoach(param) {
return req.post("mobilenode/app/IndexAdminOrder/orderChangeCoach", param)
},
// 转派技-师列表
orderChangeCoachList(param) {
return req.get("mobilenode/app/IndexAdminOrder/orderChangeCoachList", param)
},
}

109
api/modules/agent.js Normal file
View File

@ -0,0 +1,109 @@
import {
req
} from '../../utils/req.js';
export default {
// 合作加盟
agentApply(param) {
return req.post("massage/app/IndexUser/agentApply", param)
},
//代理商详情
agentInfo(param) {
return req.get("massage/app/Index/agentInfo", param)
},
// 首页
index(param) {
return req.get("mobilenode/app/IndexAgentOrder/index", param)
},
// 邀请海报
agentInviteQr(param) {
return req.get("mobilenode/app/IndexAgentOrder/agentInviteQr", param)
},
//申请提现
applyWallet(param) {
return req.post("mobilenode/app/IndexAgentOrder/applyWallet", param)
},
//提现记录
walletList(param) {
return req.get("mobilenode/app/IndexAgentOrder/walletList", param)
},
//账号设置
adminInfoData(param) {
return req.get("mobilenode/app/IndexAgentOrder/adminInfoData", param)
},
// 门店下拉列表
storeSelect(param) {
return req.get("mobilenode/app/IndexAgentOrder/storeSelect", param)
},
//技-师列表
coachList(param) {
return req.get("mobilenode/app/IndexAgentOrder/coachList", param)
},
//技-师详情
coachInfo(param) {
return req.get("mobilenode/app/IndexAgentOrder/coachInfo", param)
},
//新增技-师
coachApply(param) {
return req.post("mobilenode/app/IndexAgentOrder/coachApply", param)
},
//编辑技-师
coachDataUpdate(param) {
return req.post("mobilenode/app/IndexAgentOrder/coachDataUpdate", param)
},
//技-师关联用户
coachUserList(param) {
return req.get("mobilenode/app/IndexAgentOrder/coachUserList", param)
},
//佣金列表
commList(param) {
return req.get("mobilenode/app/IndexAgentOrder/commList", param)
},
// 拨打客户电话
getVirtualPhone(param) {
return req.post("mobilenode/app/IndexAgentOrder/getVirtualPhone", param)
},
// 编辑通知状态
noticeUpdate(param) {
return req.post("mobilenode/app/IndexAgentOrder/noticeUpdate", param)
},
// 订单列表
orderList(param) {
return req.get("mobilenode/app/IndexAgentOrder/orderList", param)
},
// 订单详情
orderInfo(param) {
return req.get("mobilenode/app/IndexAgentOrder/orderInfo", param)
},
// 修改订单状态
adminUpdateOrder(param) {
return req.post("mobilenode/app/IndexAgentOrder/adminUpdateOrder", param)
},
// 退款列表
refundOrderList(param) {
return req.get("mobilenode/app/IndexAgentOrder/refundOrderList", param)
},
// 退款详情
refundOrderInfo(param) {
return req.get("mobilenode/app/IndexAgentOrder/refundOrderInfo", param)
},
// 同意退款
passRefund(param) {
return req.post("mobilenode/app/IndexAgentOrder/passRefund", param)
},
// 拒绝退款
noPassRefund(param) {
return req.post("mobilenode/app/IndexAgentOrder/noPassRefund", param)
},
// 代理商下拉列表
adminSelect(param) {
return req.get("mobilenode/app/IndexAgentOrder/adminSelect", param)
},
// 更换技-师
orderChangeCoach(param) {
return req.post("mobilenode/app/IndexAgentOrder/orderChangeCoach", param)
},
// 转派技-师列表
orderChangeCoachList(param) {
return req.get("mobilenode/app/IndexAgentOrder/orderChangeCoachList", param)
},
}

65
api/modules/base.js Normal file
View File

@ -0,0 +1,65 @@
import {
req,
uploadFile
} from '../../utils/req.js';
export default {
// 小程序登录
login(param) {
return req.post("index/login", param)
},
// 公众号登录
webLogin(param) {
return req.post("index/webLogin", param)
},
// app微信登录
appLogin(param) {
return req.post("index/appLogin", param)
},
// app苹果登录
iosLogin(param) {
return req.post("index/iosLogin", param)
},
// app登录配置
getConfig(param) {
return req.get("index/getConfig", param)
},
// 获取配置
getWebConfig(param) {
return req.get("index/getWebConfig", param)
},
// 系统配置
configInfo(param) {
return req.get("massage/app/Index/configInfo", param)
},
// 获取地图定位
getMapInfo(param) {
return req.get("massage/app/Index/getMapInfo", param)
},
// 解析二维码
getWxCodeData(param) {
return req.get("card/app/getWxCodeData", param)
},
// base64ToImg
base64ToImg(param) {
return req.get("massage/app/IndexUser/base64ToImg", param)
},
// 上传文件
uploadFile(param) {
return uploadFile("admin/app/wx/uploadFile", param)
},
uploadFiles(querys, fn) {
return req.post('admin/admin/file/uploadFiles', querys, fn)
},
// 上传视频
uploadVideo(param) {
return uploadFile("admin/app/wx/uploadVideo", param)
},
// 获取城市
getCity(param) {
return req.get("massage/app/Index/getCity", param)
},
// 获取插件授权
plugAuth(param) {
return req.get("massage/app/Index/plugAuth", param)
},
}

37
api/modules/channel.js Normal file
View File

@ -0,0 +1,37 @@
import {
req
} from '../../utils/req.js';
export default {
// 渠道商下拉
channelCateSelect(param) {
return req.get("massage/app/IndexUser/channelCateSelect", param)
},
// 申请渠道商
applyChannel(param) {
return req.post("massage/app/IndexUser/applyChannel", param)
},
// 渠道商信息
channelInfo(param) {
return req.get("massage/app/IndexUser/channelInfo", param)
},
// 渠道商首页
index(param) {
return req.get("massage/app/IndexChannel/index", param)
},
// 渠道商二维码
channelQr(param) {
return req.get("massage/app/IndexChannel/channelQr", param)
},
// 订单列表
orderList(param) {
return req.get("massage/app/IndexChannel/orderList", param)
},
//申请提现
applyWallet(param) {
return req.post("massage/app/IndexChannel/applyWallet", param)
},
//提现记录
walletList(param) {
return req.get("massage/app/IndexChannel/walletList", param)
},
}

82
api/modules/dynamic.js Normal file
View File

@ -0,0 +1,82 @@
import {
req
} from '../../utils/req.js';
export default {
// 动态列表
dynamicList(param) {
return req.get("dynamic/app/IndexDynamicList/dynamicList", param)
},
// 获取关注技-师的最新动态数量
getFollowData(param) {
return req.get("dynamic/app/IndexDynamicList/getFollowData", param)
},
// 关注技-师动态列表
followDynamicList(param) {
return req.get("dynamic/app/IndexDynamicList/followDynamicList", param)
},
// 动态详情
dynamicInfo(param) {
return req.get("dynamic/app/IndexDynamicList/dynamicInfo", param)
},
// 点赞或者取消点赞
thumbsAddOrCancek(param) {
return req.post("dynamic/app/IndexDynamicList/thumbsAddOrCancek", param)
},
// 我的关注
followCoachList(param) {
return req.get("dynamic/app/IndexDynamicList/followCoachList", param)
},
// 关注或者取消关注
followAddOrCancek(param) {
return req.post("dynamic/app/IndexDynamicList/followAddOrCancek", param)
},
// 评论列表
commentList(param) {
return req.get("dynamic/app/IndexDynamicList/commentList", param)
},
// 新增评论
commentAdd(param) {
return req.post("dynamic/app/IndexDynamicList/commentAdd", param)
},
// 删除评论
commentDel(param) {
return req.post("dynamic/app/IndexDynamicList/commentDel", param)
},
// --------- 技-师端
// 动态列表
coachDynamicList(param) {
return req.get("dynamic/app/IndexDynamicCoach/dynamicList", param)
},
// 动态详情
coachDynamicInfo(param) {
return req.get("dynamic/app/IndexDynamicCoach/dynamicInfo", param)
},
// 关注点赞消息详情
dynamicData(param) {
return req.get("dynamic/app/IndexDynamicCoach/dynamicData", param)
},
// 发布动态
dynamicAdd(param) {
return req.post("dynamic/app/IndexDynamicCoach/dynamicAdd", param)
},
// 编辑动态
dynamicUpdate(param) {
return req.post("dynamic/app/IndexDynamicCoach/dynamicUpdate", param)
},
// 删除动态
dynamicDel(param) {
return req.post("dynamic/app/IndexDynamicCoach/dynamicDel", param)
},
// 收获的赞
thumbsList(param) {
return req.get("dynamic/app/IndexDynamicCoach/thumbsList", param)
},
// 新增关注
followList(param) {
return req.get("dynamic/app/IndexDynamicCoach/followList", param)
},
// 收获的评论
coachCommentList(param) {
return req.get("dynamic/app/IndexDynamicCoach/commentList", param)
},
}

177
api/modules/mine.js Normal file
View File

@ -0,0 +1,177 @@
import {
req
} from '../../utils/req.js';
export default {
// 个人中心页面
index(param) {
return req.get("massage/app/IndexUser/index", param)
},
//技师端显示隐藏
isShowwx(param) {
return req.get("massage/CallBack/is_showwx", param)
},
// 认证技-师
attestationCoach(param) {
return req.post("massage/app/IndexUser/attestationCoach", param)
},
//申请分销商
applyReseller(param) {
return req.post("massage/app/IndexUser/applyReseller", param)
},
//分销商详情
resellerInfo(param) {
return req.get("massage/app/IndexUser/resellerInfo", param)
},
//我的收益
capCashInfo(param) {
return req.get("massage/app/IndexUser/userCashInfo", param)
},
//申请提现
applyWallet(param) {
return req.post("massage/app/IndexUser/applyWallet", param)
},
//提现记录
walletList(param) {
return req.get("massage/app/IndexUser/walletList", param)
},
//合伙人首页
partnerIndex(param) {
return req.get("massage/app/IndexReseller/partnerIndex", param)
},
//邀请的技-师
partnerCoachList(param) {
return req.get("massage/app/IndexReseller/partnerCoachList", param)
},
//我的团队
myTeam(param) {
return req.get("massage/app/IndexUser/myTeam", param)
},
//邀请用户
userCommQr(param) {
return req.get("massage/app/IndexUser/userCommQr", param)
},
//代理商绑定技-师
adminCoachQr(param) {
return req.get("massage/app/IndexUser/adminCoachQr", param)
},
//邀请技-师
resellerInvCoachQr(param) {
return req.get("massage/app/IndexReseller/resellerInvCoachQr", param)
},
//选择代理商
adminList(param) {
return req.get("massage/app/IndexReseller/adminList", param)
},
// 获取默认地址
getDefultAddress(param) {
return req.get("massage/app/IndexUser/getDefultAddress", param)
},
// 地址列表
addressList(param) {
return req.get("massage/app/IndexUser/addressList", param)
},
// 地址详情
addressInfo(param) {
return req.get("massage/app/IndexUser/addressInfo", param)
},
// 新增地址
addressAdd(param) {
return req.post("massage/app/IndexUser/addressAdd", param)
},
// 修改地址
addressUpdate(param) {
return req.post("massage/app/IndexUser/addressUpdate", param)
},
// 删除地址
addressDel(param) {
return req.post("massage/app/IndexUser/addressDel", param)
},
// 收藏技-师
coachCollectList(param) {
return req.get("massage/app/IndexUser/coachCollectList", param)
},
// 新增收藏
addCollect(param) {
return req.post("massage/app/IndexUser/addCollect", param)
},
// 删除收藏
delCollect(param) {
return req.post("massage/app/IndexUser/delCollect", param)
},
//卡券列表
userCouponList(param) {
return req.get("massage/app/IndexUser/userCouponList", param)
},
//删除卡券
couponDel(param) {
return req.post("massage/app/IndexUser/couponDel", param)
},
//卡券活动
couponAtvInfo(param) {
return req.post("massage/app/IndexUser/couponAtvInfo", param)
},
//卡券二维码
atvQr(param) {
return req.post("massage/app/IndexUser/atvQr", param)
},
//技-师分享储值套餐
coachBalanceQr(param) {
return req.get("massage/app/IndexCoach/coachBalanceQr", param)
},
//选择技-师
coachList(param) {
return req.get("massage/app/IndexBalance/coachList", param)
},
//储值充值卡列表
cardList(param) {
return req.get("massage/app/IndexBalance/cardList", param)
},
//充值余额(card_id)
payBalanceOrder(param) {
return req.post("massage/app/IndexBalance/payBalanceOrder", param)
},
//充值订单列表(时间筛选 start_time,end_time)
balaceOrder(param) {
return req.get("massage/app/IndexBalance/balaceOrder", param)
},
//消费明细
payWater(param) {
return req.get("massage/app/IndexBalance/payWater", param)
},
//佣金明细
commList(param) {
return req.get("massage/app/IndexUser/commList", param)
},
// 提交反馈
addFeedback(param) {
return req.post("massage/app/IndexCoach/addFeedback", param)
},
// 反馈记录
listFeedback(param) {
return req.get("massage/app/IndexCoach/listFeedback", param)
},
// 反馈详情
feedbackInfo(param) {
return req.get("massage/app/IndexCoach/feedbackInfo", param)
},
// 屏蔽列表
shieldCoachList(param) {
return req.get("massage/app/IndexUser/shieldCoachList", param)
},
// 新增屏蔽
shieldCoachAdd(param) {
return req.post("massage/app/IndexUser/shieldCoachAdd", param)
},
// 删除屏蔽
shieldCoachDel(param) {
return req.post("massage/app/IndexUser/shieldCoachDel", param)
},
// 绑定支付宝账号
bindAlipayNumber(param) {
return req.post("massage/app/IndexUser/bindAlipayNumber", param)
},
// 获取门店数据
getStoreSelect(param) {
return req.get("massage/app/IndexUser/getStoreSelect", param)
},
}

133
api/modules/order.js Normal file
View File

@ -0,0 +1,133 @@
import {
req
} from '../../utils/req.js';
export default {
// 购物车
carInfo(param) {
return req.get("massage/app/Index/carInfo", param)
},
// 加入购物车
addCar(param) {
return req.post("massage/app/Index/addCar", param)
},
// 删除购物车数量
delCar(param) {
return req.post("massage/app/Index/delCar", param)
},
//清空购物车
delSomeCar(param) {
return req.post("massage/app/IndexGoods/delSomeCar", param)
},
//选择购物车商品
carUpdate(param) {
return req.post("massage/app/IndexGoods/carUpdate", param)
},
//获取是否能选择 公交/地铁
getIsBus(param) {
return req.get("massage/app/IndexOrder/getIsBus", param)
},
//下单选择时间
dayText(param) {
return req.get("massage/app/IndexOrder/dayText", param)
},
//下单选择时间(coach_id,day)
timeText(param) {
return req.get("massage/app/IndexOrder/timeText", param)
},
//获取升级订单信息
upOrderInfo(param) {
return req.post("massage/app/IndexOrder/upOrderInfo", param)
},
//升级服务下单
upOrderGoods(param) {
return req.post("massage/app/IndexOrder/upOrderGoods", param)
},
//校验加钟订单是否可下单
checkAddOrder(param) {
return req.post("massage/app/IndexOrder/checkAddOrder", param)
},
//获取下单信息(coach_id有卡券就传 coupon_id)
payOrderInfo(param) {
return req.get("massage/app/IndexOrder/payOrderInfo", param)
},
//下单
payOrder(param) {
return req.post("massage/app/IndexOrder/payOrder", param)
},
//可用卡券
couponList(param) {
return req.get("massage/app/IndexOrder/couponList", param)
},
//订单列表
orderList(param) {
return req.get("massage/app/IndexOrder/orderList", param)
},
//根据主订单查询加钟订单列表
getAddClockOrder(param) {
return req.get("massage/app/IndexOrder/getAddClockOrder", param)
},
//订单详情
orderInfo(param) {
return req.get("massage/app/IndexOrder/orderInfo", param)
},
// 技师已到达
userSureArrivalOrder(param) {
return req.post("massage/app/IndexOrder/userSureArrival", param)
},
// 升级订单记录
orderUpRecord(param) {
return req.get("massage/app/IndexOrder/orderUpRecord", param)
},
// 拨打技-师电话
getVirtualPhone(param) {
return req.get("massage/app/IndexUser/getVirtualPhone", param)
},
//刷新二维码
refreshQr(param) {
return req.post("massage/app/IndexOrder/refreshQr", param)
},
//取消订单
cancelOrder(param) {
return req.post("massage/app/IndexOrder/cancelOrder", param)
},
//删除订单
delOrder(param) {
return req.post("massage/app/IndexOrder/delOrder", param)
},
//确认完成订单
userSignOrder(param) {
return req.post("massage/app/IndexOrder/userSignOrder", param)
},
//重新支付
rePayOrder(param) {
return req.post("massage/app/IndexOrder/rePayOrder", param)
},
//申请退款
applyOrder(param) {
return req.post("massage/app/IndexOrder/applyOrder", param)
},
//再来一单
onceMoreOrder(param) {
return req.post("massage/app/Index/onceMoreOrder", param)
},
//添加评价(order_id,textstar)
addComment(param) {
return req.post("massage/app/IndexOrder/addComment", param)
},
//标签列表
lableList(param) {
return req.get("massage/app/IndexOrder/lableList", param)
},
//我的售后
refundOrderList(param) {
return req.get("massage/app/IndexOrder/refundOrderList", param)
},
//售后详情
refundOrderInfo(param) {
return req.get("massage/app/IndexOrder/refundOrderInfo", param)
},
//取消退款
cancelRefundOrder(param) {
return req.post("massage/app/IndexOrder/cancelRefundOrder", param)
},
}

37
api/modules/salesman.js Normal file
View File

@ -0,0 +1,37 @@
import {
req
} from '../../utils/req.js';
export default {
// 申请业务员
applySalesman(param) {
return req.post("massage/app/IndexUser/applySalesman", param)
},
// 业务员信息
salesmanInfo(param) {
return req.get("massage/app/IndexUser/salesmanInfo", param)
},
// 业务员首页
index(param) {
return req.get("massage/app/IndexSalesman/index", param)
},
// 业务员二维码
salesmanQr(param) {
return req.get("massage/app/IndexSalesman/salesmanQr", param)
},
// 申请提现
applyWallet(param) {
return req.post("massage/app/IndexSalesman/applyWallet", param)
},
// 提现记录
walletList(param) {
return req.get("massage/app/IndexSalesman/walletList", param)
},
// 渠道明细
salesmanChannelCash(param) {
return req.get("massage/app/IndexSalesman/salesmanChannelCash", param)
},
// 业务员渠道商明细详情
salesmanChannelOrderList(param) {
return req.get("massage/app/IndexSalesman/salesmanChannelOrderList", param)
}
}

65
api/modules/service.js Normal file
View File

@ -0,0 +1,65 @@
import {
req
} from '../../utils/req.js';
export default {
// 首页轮播图
index(param) {
return req.get("massage/app/Index/index", param)
},
//文章详情
articleInfo(param) {
return req.get("massage/app/IndexArticle/articleInfo", param)
},
//文章详情-提交表单
subArticleForm(param) {
return req.post("massage/app/IndexArticle/subArticleForm", param)
},
// 服务分类列表
serviceCateList(param) {
return req.get("massage/app/Index/serviceCateList", param)
},
// 服务列表
serviceList(param) {
return req.get("massage/app/Index/serviceList", param)
},
// 服务详情
serviceInfo(param) {
return req.get("massage/app/Index/serviceInfo", param)
},
// 地图找人
mapCoachList(param) {
return req.get("map/app/Index/coachList", param)
},
// 服务技-师列表无筛选项(ser_id服务id,lat,lng)
typeServiceCoachList(param) {
return req.get("massage/app/Index/typeServiceCoachList", param)
},
// 服务技-师列表(ser_id服务id,lat,lng,type)
serviceCoachList(param) {
return req.get("massage/app/Index/serviceCoachList", param)
},
// 技-师服务列表(coach_id)
coachServiceList(param) {
return req.get("massage/app/Index/coachServiceList", param)
},
// 技-师评价
commentList(param) {
return req.get("massage/app/Index/commentList", param)
},
// 技-师信息
coachInfo(param) {
return req.get("massage/app/Index/coachInfo", param)
},
//优惠券列表
couponList(param) {
return req.get("massage/app/Index/couponList", param)
},
//领取优惠券
userGetCoupon(param) {
return req.post("massage/app/Index/userGetCoupon", param)
},
//获取可升级的服务
getUpOrderGoods(param) {
return req.get("massage/app/IndexOrder/getUpOrderGoods", param)
},
}

21
api/modules/shopstore.js Normal file
View File

@ -0,0 +1,21 @@
import {
req
} from '../../utils/req.js';
export default {
// 门店列表
storeList(param) {
return req.get("store/app/IndexStore/storeList", param)
},
// 门店详情
storeInfo(param) {
return req.get("store/app/IndexStore/storeInfo", param)
},
// 门店服务列表
storeServiceList(param) {
return req.get("store/app/IndexStore/storeServiceList", param)
},
// 门店评价列表
commentList(param) {
return req.get("store/app/IndexStore/commentList", param)
}
}

185
api/modules/technician.js Normal file
View File

@ -0,0 +1,185 @@
import {
req
} from '../../utils/req.js';
export default {
// 申请技-师
coachApply(param) {
return req.post("massage/app/IndexUser/coachApply", param)
},
// 获取是否开启合同
getFddStatus(param) {
return req.get("massage/app/IndexCoach/getFddStatus", param)
},
// 获取是否已签合同
getFddRecord(param) {
return req.get("massage/app/IndexCoach/getFddRecord", param)
},
// 获取发大大实名认证地址
getPersonVerifyUrl(param) {
return req.get("massage/app/IndexCoach/getPersonVerifyUrl", param)
},
// 获取用户实名注册信息
getAttestationInfo(param) {
return req.get("massage/app/IndexCoach/getAttestationInfo", param)
},
// 签署合同
Extsign(param) {
return req.post("massage/app/IndexCoach/Extsign", param)
},
// 技-师信息
coachInfo(param) {
return req.get("massage/app/IndexUser/coachInfo", param)
},
// 编辑技-师
coachUpdate(param) {
return req.post("massage/app/IndexCoach/coachUpdate", param)
},
// 编辑技-师
coachUpdateV2(param) {
return req.post("massage/app/IndexCoach/coachUpdateV2", param)
},
// 技-师等级
coachLevel(param) {
return req.get("massage/app/IndexCoach/coachLevel", param)
},
// 技-师首页
coachIndex(param) {
return req.get("massage/app/IndexCoach/coachIndex", param)
},
// 技-师报警
police(param) {
return req.post("massage/app/IndexCoach/police", param)
},
// 订单列表
orderList(param) {
return req.get("massage/app/IndexCoach/orderList", param)
},
// 订单详情
orderInfo(param) {
return req.get("massage/app/IndexCoach/orderInfo", param)
},
// 修改订单状态(type,order_id)
updateOrder(param) {
return req.post("massage/app/IndexCoach/updateOrder", param)
},
// 客户爽约
breakThePromiseOrder(param) {
return req.post("massage/app/IndexCoach/breakThePromise", param)
},
// 拨打客户电话
getVirtualPhone(param) {
return req.post("massage/app/IndexCoach/getVirtualPhone", param)
},
//佣金信息
capCashInfo(param) {
return req.get("massage/app/IndexCoach/capCashInfo", param)
},
//佣金信息(车费)
capCashInfoCar(param) {
return req.get("massage/app/IndexCoach/capCashInfoCar", param)
},
//申请提现 (apply_price,text,type1服务费提现2车费提现)
applyWallet(param) {
return req.post("massage/app/IndexCoach/applyWallet", param)
},
//提现记录
capCashList(param) {
return req.get("massage/app/IndexCoach/capCashList", param)
},
//时间管理回显
timeConfig(param) {
return req.get("massage/app/IndexCoach/timeConfig", param)
},
//时间管理设置
setTimeConfig(param) {
return req.post("massage/app/IndexCoach/timeConfig", param)
},
//根据接单时间获取时间节点
getTime(param) {
return req.get("massage/app/IndexCoach/getTime", param)
},
//根据接单时间获取时间节点
getOrderNum(param) {
return req.get("massage/app/IndexCoach/getOrderNum", param)
},
//物料商城-商品列表
goodsList(param) {
return req.get("massage/app/IndexCoach/goodsList", param)
},
//物料商城-分类列表
carteList(param) {
return req.get("massage/app/IndexCoach/carteList", param)
},
//物料商城-商品详情
goodsInfo(param) {
return req.get("massage/app/IndexCoach/goodsInfo", param)
},
//车费明细列表
carMoneyList(param) {
return req.get("massage/app/IndexCoach/carMoneyList", param)
},
//差评申诉 订单列表
appealOrder(param) {
return req.get("massage/app/IndexCoach/appealOrder", param)
},
//差评申诉 提交申诉
addAppeal(param) {
return req.post("massage/app/IndexCoach/addAppeal", param)
},
//差评申诉 申诉记录列表
appealList(param) {
return req.get("massage/app/IndexCoach/appealList", param)
},
//标签列表
labelList(param) {
return req.get("massage/app/IndexCoach/labelList", param)
},
//添加用户评价
userLabelAdd(param) {
return req.post("massage/app/IndexCoach/userLabelAdd", param)
},
//获取用户当前标签
userLabelList(param) {
return req.get("massage/app/IndexCoach/userLabelList", param)
},
//储值返佣列表
balanceCommissionList(param) {
return req.get("massage/app/IndexCoach/balanceCommissionList", param)
},
//储值返佣金额统计
balanceCommissionData(param) {
return req.get("massage/app/IndexCoach/balanceCommissionData", param)
},
//分成明细
coachCommissionList(param) {
return req.get("massage/app/IndexCoach/coachCommissionList", param)
},
//分成明细金额统计
coachCommissionData(param) {
return req.get("massage/app/IndexCoach/coachCommissionData", param)
},
//收益详情
coachCommissionInfo(param) {
return req.get("massage/app/IndexCoach/coachCommissionInfo", param)
},
//拉黑用户
shieldUserAdd(param) {
return req.post("massage/app/IndexCoach/shieldUserAdd", param)
},
//移除拉黑用户
shieldUserDel(param) {
return req.post("massage/app/IndexCoach/shieldUserDel", param)
},
//拉黑用户列表
shieldCoachList(param) {
return req.get("massage/app/IndexCoach/shieldCoachList", param)
},
// 订单服务录音
recordingAdd(param) {
return req.post("recording/app/Recording/recordingAdd", param)
},
// 修改技师白天夜间免费出行距离
updateCoachRidingForfree(param) {
return req.post("massage/app/IndexCoach/updateCoachRidingForfree", param)
},
}

33
api/modules/user.js Normal file
View File

@ -0,0 +1,33 @@
import {
req
} from '../../utils/req.js';
export default {
// 用户信息
userInfo(param) {
return req.get("massage/app/IndexUser/userInfo", param)
},
// 更新用户信息
userUpdate(param) {
return req.post("massage/app/IndexUser/userUpdate", param)
},
// 绑定渠道商
bindChannel(param) {
return req.post("massage/app/IndexUser/bindChannel", param)
},
// 注销用户
delUserInfo(param) {
return req.post("/massage/app/IndexUser/delUserInfo", param)
},
// 获取手机号
reportPhone(param) {
return req.post("massage/app/IndexUser/reportPhone", param)
},
// 验证码
sendShortMsg(param) {
return req.post("massage/app/IndexUser/sendShortMsg", param)
},
// 绑定手机号
bindUserPhone(param) {
return req.post("massage/app/IndexUser/bindUserPhone", param)
}
}

322
components/abnor.vue Normal file
View File

@ -0,0 +1,322 @@
<template>
<view class="abnor" :style="{paddingBottom: percent}">
<view class="abnor-box">
<image mode="aspectFit" class="abnor-image" v-if="pimage" :src="pimage"></image>
<view class="abnor-text" v-if="ptitle">{{ptitle}}</view>
<view @tap.stop="emitAbnorTipTap" class="abnor-tip" :class="[{'tip-flex':!tipMax}]"
:style="{maxWidth:tipMax,textAlign:tipMax?'left':''}" v-if="ptip">
<text v-for="(item,index) in ptip" :key="index"
:style="{color:item.color == 1 ? primaryColor:''}">{{item.text}}</text>
</view>
<block v-if="pbutton">
<block v-for="(item,index) in pbutton" :key="index">
<view class="abnor-btn" :class="btnSize"
:style="{background:item.type == 'confirm' ? primaryColor : '',color:item.type == 'confirm'?'white':''}"
@tap.stop="emitAbnorTap(index)">{{item.text}}</view>
</block>
</block>
</view>
</view>
</template>
<script>
const Types = {
'REQUEST_ERROR': {
image: 'https://lbqny.migugu.com/admin/public/request-error.png',
title: '网络加载失败',
button: [{
text: '点击刷新',
type: 'confirm'
}],
tip: []
},
'NOT_FOUND': {
image: 'https://lbqny.migugu.com/admin/public/not-found.png',
title: '很抱歉,找不到你要访问的页面',
button: [{
text: '返回',
type: 'confirm'
}],
tip: []
},
'DATA': {
image: '/static/img/qs_dingdan.png',
title: '',
button: [],
tip: [{
text: '没有相关数据哦',
color: 0
}]
},
'FOLLOW': {
image: 'https://lbqny.migugu.com/admin/public/no-follow.png',
title: '关注有趣的人',
button: [],
tip: [{
text: '不再错过他们每一条动态',
color: 0
}]
},
'FEED': {
image: 'https://lbqny.migugu.com/admin/public/no-feed.png',
title: '还没有任何反馈哦',
button: [],
tip: []
},
'SHOP': {
image: 'https://lbqny.migugu.com/admin/public/no-shop.png',
title: '稍后再来试试吧~',
button: [],
tip: []
},
'WEIBO': {
image: 'https://lbqny.migugu.com/admin/public/no-weibo.png',
title: '',
button: [],
tip: []
},
'SEARCH': {
image: 'https://lbqny.migugu.com/admin/public/no-search.png',
title: '抱歉!没找到相关商品~',
button: [],
tip: []
},
'TAG': {
image: 'https://lbqny.migugu.com/admin/public/no-tag.png',
title: '',
button: [],
tip: []
},
'MESSAGE': {
image: 'https://lbqny.migugu.com/admin/public/no-message.png',
title: '消息通知空空如也',
button: [],
tip: []
},
'LIVE': {
image: 'https://lbqny.migugu.com/admin/public/no-live.png',
title: '',
button: [],
tip: []
},
'ORDER': {
image: 'https://lbqny.migugu.com/admin/public/no-order.png',
title: "还没有相关订单哦",
button: [],
tip: []
},
'CART': {
image: 'https://lbqny.migugu.com/admin/public/no-car.png',
title: '购物车还是空的哦~',
button: [{
text: '去逛逛',
type: 'confirm'
}],
button: [],
tip: []
},
'FOOTPRINT': {
image: 'https://lbqny.migugu.com/admin/public/no-footprint.png',
title: '你还没有足迹~',
button: [],
tip: []
},
'COUPON': {
image: 'https://lbqny.migugu.com/admin/public/no-coupon.png',
title: '你还没有可用的卡券哦',
button: [],
tip: []
},
'REDUCTION': {
image: 'https://lbqny.migugu.com/admin/reduction/nodata.png',
title: '您当前没有满减券,请向工作人员索取',
button: [],
tip: []
},
'MYTEAM': {
image: 'https://lbqny.migugu.com/admin/public/no-coupon.png',
title: '没有更多了',
button: [],
tip: []
}
}
import {
mapState,
mapActions,
} from 'vuex';
export default {
name: 'abnor',
props: {
type: {
type: String,
default () {
return 'DATA'
}
},
image: {
type: String,
default () {
return ''
}
},
title: {
type: String,
default () {
return ''
}
},
tip: {
type: Array,
default () {
return []
}
},
button: {
type: Array,
default () {
return []
}
},
tipTap: {
type: String,
default () {
return ''
}
},
tipMax: {
type: String,
default () {
return '100%'
}
},
btnSize: {
type: String,
default () {
return 'big'
}
},
percent: {
type: String,
default () {
return '100%'
}
},
},
created() {
this.init();
},
data() {
return {
pimage: '',
ptitle: '',
ptip: '',
pbutton: ''
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
}),
methods: {
emitAbnorTipTap() {
let {
tipTap = ''
} = this
if (!tipTap) return
this.$emit(tipTap);
},
emitAbnorTap(index) {
let {
type
} = this.pbutton[index]
this.$emit(type);
},
init() {
let type = this.type;
if (Types[type]) {
this.pimage = this.image || Types[type].image;
this.ptitle = this.title || Types[type].title;
this.pbutton = this.button && this.button.length > 0 ? this.button : Types[type].button;
this.ptip = this.tip && this.tip.length > 0 ? this.tip : Types[type].tip;
}
}
},
}
</script>
<style lang="scss">
.abnor {
position: relative;
display: block;
width: 100%;
height: 0;
padding-bottom: 100%;
overflow: hidden;
.abnor-box {
position: absolute;
display: flex;
top: 0;
bottom: 0;
left: 0;
right: 0;
flex-direction: column;
justify-content: center;
align-items: center;
.abnor-image {
width: 520rpx;
height: 400rpx;
background: transparent;
}
.abnor-text {
margin-top: 30rpx;
color: #333;
font-size: 32rpx;
font-weight: 400;
}
.abnor-tip {
margin: 15rpx 0 10rpx 0;
color: #999;
font-size: 26rpx;
text-align: center;
max-height: 30vh;
overflow: auto;
}
.tip-flex {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}
.abnor-btn {
min-width: 228rpx;
height: 66rpx;
margin-top: 30rpx;
padding: 0 30rpx;
background-color: #eee;
border: 0 none;
border-radius: 10rpx;
color: #333;
font-size: 28rpx;
text-align: center;
// overflow: hidden;
line-height: 66rpx;
}
.abnor-btn.big {
width: 690rpx;
height: 100rpx;
line-height: 100rpx;
font-size: 32rpx;
margin-top: 40rpx;
border-radius: 50rpx;
}
}
}
</style>

542
components/auth.vue Normal file
View File

@ -0,0 +1,542 @@
<template>
<view style="width:100%">
<block v-if="needAuth">
<view @tap.stop="toShowAuth">
<slot></slot>
</view>
</block>
<block v-else>
<view @tap.stop="go(1)">
<slot></slot>
</view>
</block>
<uni-popup ref="show_auth_item" :maskClick="false">
<view @tap.stop.prevent class="auth-box fill-base flex-column flex-center text-center radius-26">
<view class="space-md"></view>
<block v-if="pType == 'phone'">
<image mode="aspectFill" class="auth-img" :src="`https://lbqny.migugu.com/admin/public/auth.png`">
</image>
</block>
<view class="space-sm"></view>
<view class="f-caption" :style="{color:primaryColor}">{{contentList[pType][0]}}</view>
<view class="space-lg"></view>
<view class="space-lg"></view>
<block v-if="pType === 'phone'">
<button open-type="getPhoneNumber" hover-class="btn-hover" @getphonenumber="authPhone"
class="clear-btn flex-center auth-btn" :style="{background: `linear-gradient(68deg, ${primaryColor}, ${subColor})`,color:'white'}">
{{btn_text||contentList[pType][1]}}
</button>
</block>
<block v-if="pType === 'setting'">
<button open-type="openSetting" hover-class="btn-hover" @opensetting="openSetting"
class="clear-btn flex-center auth-btn" :style="{background: `linear-gradient(68deg, ${primaryColor}, ${subColor})`,color:'white'}">
{{btn_text||contentList[pType][1]}}
</button>
</block>
<view @tap.stop="go(pType == 'phone' && !userInfo.phone ? 2 : 1)" class="f-caption c-caption mt-md"
v-if="!pMust">{{pType=='userInfo'?'暂不授权':'暂不登录'}}</view>
<view class="space-md"></view>
</view>
</uni-popup>
<uni-popup ref="show_info_item" :maskClick="false">
<view @tap.stop.prevent class="common-popup-content popup-phone pd-lg flex-center flex-column fill-base">
<view class="f-md-title c-black">请填写用户信息</view>
<view class="space-lg pb-lg"></view>
<view class="space-lg pb-lg"></view>
<view class="flex-between" style="width:100%">
<view class="flex-y-center c-title">
<view class="c-warning">*</view>昵称
</view>
<!-- #ifdef MP-WEIXIN -->
<input v-model="infoForm.nickName" type="nickname" maxlength="15"
class="f-mini-title c-title text-right" style="width:80%" placeholder-class="c-placeholder"
:placeholder="infoRule[0].errorMsg" />
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<input v-model="infoForm.nickName" type="text" maxlength="15"
class="f-mini-title c-title text-right" style="width:80%" placeholder-class="c-placeholder"
:placeholder="infoRule[0].errorMsg" />
<!-- #endif -->
</view>
<view class="flex-between mt-md pt-md b-1px-t" style="width:100%">
<view class="flex-y-center c-title">
<view class="c-warning">*</view>头像
</view>
<!-- #ifdef MP-WEIXIN -->
<button open-type="chooseAvatar" @chooseavatar="onChooseAvatar">
<image mode="aspectFill" class="avatar radius" :src="infoForm.avatarUrl || '/static/mine/default_user.png'"></image>
</button>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<image @tap.stop="toChooseImg" mode="aspectFill" class="avatar radius" :src="infoForm.avatarUrl">
</image>
<!-- #endif -->
</view>
<view class="button">
<view @tap.stop="$refs.show_info_item.close()" class="item-child">
取消
</view>
<view @tap.stop="submit('info')" class="item-child"
:style="{background: primaryColor,color:'#fff'}">
确定
</view>
</view>
</view>
</uni-popup>
<uni-popup ref="show_phone_item" :maskClick="false">
<view @tap.stop.prevent class="common-popup-content popup-phone pd-lg flex-center flex-column fill-base">
<view class="f-md-title c-black">请输入手机号</view>
<view class="space-lg pb-lg"></view>
<view class="space-lg pb-lg"></view>
<view class="flex-center mb-lg">
<view class="input-info sm mr-md radius-16">
<input v-model="subForm.phone" type="number"
class="item-input flex-y-center pl-lg pr-lg f-sm-title c-title"
placeholder-class="c-placeholder" :placeholder="subRule[0].errorMsg" />
</view>
<view @tap="toSend" class="send-btn flex-center c-base radius-16"
:style="{background:primaryColor}">
{{authTime>0?`(${authTime}s)`:'发送'}}
</view>
</view>
<view class="input-info radius-16">
<input v-model="subForm.short_code" type="number"
class="item-input flex-y-center pl-lg pr-lg f-sm-title c-title" maxlength="6"
placeholder-class="c-placeholder" :placeholder="subRule[1].errorMsg" />
</view>
<view class="button">
<view @tap.stop="go(3)" class="item-child">
取消
</view>
<view @tap.stop="submit('sub')" class="item-child" :style="{background: primaryColor,color:'#fff'}">
确定
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations,
} from "vuex"
export default {
components: {},
name: 'auth',
props: {
needAuth: {
type: Boolean,
default () {
return false
}
},
must: {
type: Boolean,
default () {
return false
}
},
userMust: {
type: Boolean,
default () {
return true
}
},
showAuth: {
type: Boolean,
default () {
return false
}
},
type: {
type: String,
default () {
return 'phone'
}
},
btn_text: {
type: String,
default () {
return ''
}
},
haveGo: {
type: Boolean,
default () {
return true
}
},
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
commonOptions: state => state.user.commonOptions,
userInfo: state => state.user.userInfo,
}),
created() {
this.init();
},
data() {
return {
contentList: {
userInfo: ['尊贵的用户,获取授权是为了能更好的为你服务', '立即授权'],
phone: ['尊贵的用户,登录后我们才能更好的为你服务', '立即登录'],
setting: ['为了功能正常使用,你需要打开设置并开启获取相应权限', '打开设置'],
},
pType: '',
pMust: '',
authTime: 0,
timer: null,
subForm: {
phone: '',
short_code: ''
},
subRule: [{
name: "phone",
checkType: "isMobile",
errorMsg: "请输入手机号",
regText: "手机号"
}, {
name: "short_code",
checkType: "isNotNull",
errorMsg: "请输入短信验证码"
}],
infoForm: {
nickName: '',
avatarUrl: ''
},
infoRule: [{
name: "nickName",
checkType: "isNotNull",
errorMsg: "请输入用户昵称",
regType: 2
}, {
name: "avatarUrl",
checkType: "isNotNull",
errorMsg: "请上传用户头像"
}],
lockTap: false
}
},
methods: {
...mapActions(['getUserInfo', 'getMineInfo', 'getAuthUserProfile', 'getAuthPhone', ]),
...mapMutations(['updateConfigItem', 'updateUserItem']),
init() {
let {
type,
must,
showAuth
} = this
this.$set(this, 'pType', type)
this.$set(this, 'pMust', must)
if (!showAuth) return
let refs_key = type === 'userInfo' ? 'show_info_item' : 'show_auth_item'
this.$refs[refs_key].open()
},
toShowAuth() {
let {
id: uid = 0,
phone = '',
} = this.userInfo
if (!uid) {
this.updateUserItem({
key: 'loginPage',
val: `/pages/mine?type=1`
})
this.$util.goUrl({
url: `/pages/login`
})
return
}
this.infoForm = this.$util.pick(this.userInfo, ['nickName',
'avatarUrl'
])
let type = !phone ? 'phone' : 'userInfo'
this.$set(this, 'pType', type)
let refs_key = 'show_info_item'
if (!phone) {
// #ifdef MP-WEIXIN
refs_key = 'show_auth_item'
// #endif
// #ifndef MP-WEIXIN
if (this.haveGo) {
refs_key = 'show_phone_item'
let {
short_code_status = 0
} = this.configInfo
if (!short_code_status) {
this.go(1)
return
}
}
// #endif
}
this.$refs[refs_key].open()
},
//
async authPhone(e) {
let {
pMust
} = this
let phone = await this.getAuthPhone({
e,
})
if (!phone) {
this.go(pMust ? 2 : 1)
return false
} else {
let {
nickName = '',
} = this.userInfo
if (nickName) {
this.go(1)
return
}
this.$set(this, 'pType', 'userInfo')
this.$set(this, 'pMust', this.userMust)
if (!this.pMust) return
this.$refs.show_info_item.open()
}
},
go(type = 1) {
this.lockTap = false
this.$emit(type == 1 ? 'go' : 'hide')
let refs_key = type == 3 ? 'show_phone_item' : type == 4 ? 'show_info_item' : 'show_auth_item'
let {
id: uid = 0
} = this.userInfo
if (uid) {
this.$refs[refs_key].close();
}
if (type != 3) return
this.toResetItem('sub')
},
//
toResetItem(type) {
if (type == 'sub') {
this.timer && clearTimeout(this.timer)
this.authTime = 0
this.subForm = {
phone: '',
short_code: ''
}
return
}
this.infoForm = this.$util.pick(this.userInfo, ['nickName',
'avatarUrl'
])
},
//
validate(param, ruleType, is_send = false) {
let validate = new this.$util.Validate();
this[`${ruleType}Rule`].map(item => {
let {
name,
} = item
if (ruleType == 'sub' && name == 'short_code' && is_send) return
validate.add(param[name], item);
})
let message = validate.start();
return message;
},
//
async toSend() {
let {
authTime
} = this
if (authTime) return
let {
phone = ''
} = this.subForm
let msg = this.validate({
phone
}, 'sub', true);
if (msg) {
this.$util.showToast({
title: msg
});
return;
}
if (this.lockTap) return
this.lockTap = true
this.$util.showLoading()
try {
await this.$api.user.sendShortMsg({
phone
})
this.$util.hideAll()
this.lockTap = false
let time = 60
this.timer = setInterval(() => {
if (time === 0) {
clearInterval(this.timer)
return
}
time--
this.authTime = time
}, 1000)
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
}
},
//
onChooseAvatar(e) {
let {
avatarUrl
} = e.detail
this.infoForm.avatarUrl = avatarUrl
},
async toChooseImg() {
let param = {
count: 1,
sizeType: ['compressed'],
sourceType: ['album'],
}
let [res_upload, res_info] = await uni.chooseImage(param)
if (res_upload) return
let {
size = 0,
tempFiles,
tempFilePath = ''
} = res_info
//
this.$util.showLoading({
title: "上传中"
})
let {
attachment_path: path
} = await this.$api.base.uploadFile({
filePath: tempFiles[0].path,
formData: {
type: 'picture'
}
})
this.infoForm.avatarUrl = path
this.$util.hideAll()
},
// sub info
async submit(formType) {
let param = this.$util.deepCopy(this[`${formType}Form`])
let msg = this.validate(param, formType);
if (msg) {
this.$util.showToast({
title: msg
});
return;
}
if (formType == 'sub' && param.short_code.length != 6) {
this.$util.showToast({
title: `请输入6位数短信验证码`
})
return
}
if (formType == 'info' && (param.avatarUrl.includes('wxfile://') || param.avatarUrl.includes(
'//tmp/'))) {
let {
attachment_path: path
} = await this.$api.base.uploadFile({
filePath: param.avatarUrl,
formData: {
type: 'picture'
}
})
param.avatarUrl = path
}
if (this.lockTap) return
this.lockTap = true
this.$util.showLoading()
let methodModel = formType == 'sub' ? 'bindUserPhone' : 'userUpdate'
let refs_key = formType == 'sub' ? 'show_phone_item' : 'show_info_item'
try {
await this.$api.user[methodModel](param)
this.$util.hideAll()
this.lockTap = false
this.$refs[refs_key].close()
this.toResetItem(formType)
await this.getUserInfo()
setTimeout(() => {
this.$emit('go')
}, 500)
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
}
}
},
}
</script>
<style lang="scss">
.auth-box {
width: 630rpx;
height: auto;
padding: 30rpx;
.auth-img {
width: 322rpx;
height: 341rpx;
/* background: #f4f6f8; */
}
.auth-btn {
width: 367rpx;
height: 90rpx;
border-radius: 90rpx;
}
.auth-info {
width: 80rpx;
height: 80rpx;
background: #f4f6f8;
border-radius: 50%;
}
}
.popup-phone {
width: 630rpx;
.input-info {
width: 570rpx;
height: 90rpx;
background: #F7F7F7;
.item-input {
height: 90rpx;
font-size: 32rpx;
text-align: left;
}
}
.input-info.sm {
width: 400rpx;
}
.send-btn {
width: 150rpx;
height: 90rpx;
}
}
</style>

285
components/banner.vue Normal file
View File

@ -0,0 +1,285 @@
<template>
<!-- 轮播图广告 -->
<!-- :current="activeIndex" -->
<view class='swiper-box' :style='{margin:`0 ${margin}rpx`}' v-if="showSwiper">
<swiper class="swiper-ad fill-base" :indicator-dots="false" :autoplay="playVideo ? false : autoplay"
:indicator-color="indicatorColor" :indicator-active-color="indicatorActiveColor" :circular="circular"
:previous-margin="list.length>1?previousMargin:0" :next-margin="list.length>1?nextMargin:0"
@change="handerChange" :style="{height:height+'rpx'}" easing-function="linear">
<swiper-item v-for="(item,index) in list" :key="index" :style="{borderRadius:borderRadius+'rpx'}">
<view class='img-box' @tap.stop='changeItem(item)'>
<block v-if="item.jump_type == 'video'">
<block v-if="!playVideo">
<view @tap.stop="playCurrent" class="play-video-info flex-center c-base abs">
<view class="play-video flex-center c-base radius">
<i class="iconfont icon-play-video"></i>
</view>
</view>
<image mode="aspectFill" :src='item.img' class="swiper-ad__img radius-16"
:style="{borderRadius:borderRadius+'rpx'}" />
</block>
<view class="video-box" v-if="playVideo">
<video :id="`video_id`" class="my-video" object-fit="contain" :loop="false"
enable-play-gesture :enable-progress-gesture="false" :src="item.jump_url"
:autoplay="playVideo" @play="onPlay" @pause="onPause" @ended="onEnded"
@timeupdate="onTimeUpdate" @waiting="onWaiting" @progress="onProgress"
@loadedmetadata="onLoadedMetaData"></video>
</view>
</block>
<image mode="aspectFill" :src='item.img || item' class="swiper-ad__img"
:style="{borderRadius:borderRadius+'rpx'}" v-else />
</view>
</swiper-item>
</swiper>
<view class='numbers' v-if="list.length>1&&indicatorType=='number' && !playVideo"
:style="{textAlign:indicatorStyle}">
<view class="number">{{activeIndex+1}}/{{list.length}}</view>
</view>
<view class='dots' v-if="list.length>1&&indicatorType=='dot'"
:style="{textAlign:indicatorStyle,bottom:`${dotBottom}rpx`}">
<view class='dot' v-for="(item,index) in list" :key="index"
:style='{backgroundColor:index==activeIndex?indicatorActiveColor:indicatorColor,width:index==activeIndex? `${dotWidth}rpx` :"12rpx"}'>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from 'vuex';
export default {
name: 'banner',
props: {
list: {
type: Array,
default () {
return []
}
},
height: {
type: Number,
default () {
return 400
}
},
indicatorType: {
type: String,
default () {
return "dot"
}
},
indicatorColor: {
type: String,
default () {
return "#FEFFFE"
}
},
indicatorActiveColor: {
type: String,
default () {
return "#fff"
}
},
indicatorStyle: {
type: String,
default () {
return 'center'
}
},
circular: {
type: Boolean,
default () {
return true
}
},
autoplay: {
type: Boolean,
default () {
return false
}
},
previousMargin: {
type: Number,
default () {
return 0
}
},
nextMargin: {
type: Number,
default () {
return 0
}
},
dotWidth: {
type: Number,
default () {
return 12
}
},
dotBottom: {
type: Number,
default () {
return 20
}
},
margin: {
type: Number,
default () {
return 0
}
},
borderRadius: {
type: Number,
default () {
return 0
}
}
},
created() {
this.videoContexts = uni.createVideoContext(`video_id`, this)
},
data() {
return {
showSwiper: true,
activeIndex: 0,
videoContexts: {},
playVideo: false,
}
},
watch: {
list(newlist, oldlist) {
this.activeIndex = 0
this.showSwiper = false;
setTimeout(() => {
this.$nextTick(() => {
this.showSwiper = true
});
})
}
},
computed: mapState({
configInfo: state => state.config.configInfo,
commonOptions: state => state.user.commonOptions,
}),
methods: {
handerChange: function(e) {
let {
current
} = e.detail
this.activeIndex = current
let ind = this.list.findIndex(item => {
return item.jump_type == 'video'
})
if (ind == -1) return
if (current == ind + 1 || current == this.list.length - 1) {
this.videoContexts.pause()
this.playVideo = false
}
},
changeItem(item) {
let {
jump_type = ''
} = item
if (jump_type == 'video') return
this.$emit("change", item)
},
playCurrent() {
this.videoContexts.play()
this.playVideo = true
},
onPlay(e) {},
onPause(e) {},
onEnded(e) {},
onError(e) {},
onTimeUpdate(e) {},
onWaiting(e) {},
onProgress(e) {},
onLoadedMetaData(e) {}
}
}
</script>
<style lang="scss">
.swiper-box {
position: relative;
.img-box {
width: 100%;
height: 100%;
.swiper-ad__img {
width: 100%;
height: 100%;
background: #fff;
}
.play-video-info {
top: 0rpx;
width: 100%;
height: 100%;
z-index: 9;
.play-video {
width: 66rpx;
height: 66rpx;
background: rgba(2, 2, 2, 0.5);
.iconfont {
font-size: 28rpx;
}
}
}
.video-box {
width: 100%;
height: 100%;
.my-video {
width: 100%;
height: 100%;
}
}
}
.dots {
position: absolute;
z-index: 20rpx;
text-align: right;
width: 100%;
padding: 0 20rpx;
.dot {
display: inline-block;
height: 12rpx;
width: 12rpx;
background-color: #FEFFFE;
border-radius: 6rpx;
margin: 0 8rpx;
}
}
.numbers {
position: absolute;
z-index: 20rpx;
text-align: right;
width: 100%;
transform: translateY(-70rpx);
padding: 0 15rpx;
.number {
display: inline-block;
width: 66rpx;
line-height: 40rpx;
font-size: 24rpx;
color: #fff;
text-align: center;
border-radius: 33rpx;
background: rgba(0, 0, 0, 0.4);
}
}
}
</style>

337
components/column.vue Normal file
View File

@ -0,0 +1,337 @@
<template>
<view>
<!-- 轮播图 -->
<view v-if="type=='swiper'" class='column-box'
:style='{paddingTop:whiteSpace+"rpx",paddingBottom:whiteSpace+"rpx",paddingLeft:wingBlank+"rpx",paddingRight:wingBlank+"rpx"}'>
<swiper class="swiper-category" @change="handerChange"
:style="{height:(146*formatRowNum+20*(formatRowNum-1))+'rpx'}">
<swiper-item class="swiper-category-item" v-for="(pitem,pindex) in formatList" :key="pindex">
<view v-for="(item,index) in pitem" :key="index" class="column-item"
:style="{width:100/colNum + '%',marginTop:index<colNum? '0' : '20rpx'}" @tap='change(item)'>
<image mode="aspectFill" class="column-img radius" :src="item.cover" v-if="item.cover"></image>
<view v-else class="column-no-img"
:style="{background:item.icon?primaryColor:colorList[index%4],borderRadius:'50%',margin:'0rpx auto'}">
<i class="iconfont" :class="item.icon" v-if="item.icon"></i>
<view v-else>{{getFirstText(item.title)}}</view>
</view>
<view class='column-text'>{{item.title}}</view>
</view>
</swiper-item>
</swiper>
<view class='dots' v-if="formatList.length>1">
<view class='dot' v-for="(item,index) in formatList.length" :key="index"
:style="{background:index==current?indicatorActiveColor:indicatorColor,width:index==current?'20rpx':'8rpx'}">
</view>
</view>
</view>
<!-- 滑动 -->
<view v-if="type=='scroll'" class='column-box'
:style='{paddingTop:whiteSpace+"rpx",paddingBottom:whiteSpace+"rpx",paddingLeft:wingBlank+"rpx",paddingRight:wingBlank+"rpx"}'>
<scroll-view scroll-x class='scroll-x' @scroll="handerScroll">
<view class='scroll-x-item' v-for="(pItem,pindex) in formatList" :key="pindex">
<view class='column-item' v-for="(item,index) in pItem" :key="index"
:style='{width:(750/colNum)+"rpx"}' @tap='change(item)'>
<image mode="aspectFill" class='column-img' :src='item.icon'></image>
<view class='column-text'>{{item.cate_name}}</view>
</view>
</view>
</scroll-view>
<view class='ink-bar-box' v-if="list.length>formatRowNum*colNum">
<view class="ui-tabs-ink-bar-wrapper">
<view class="ui-tabs-ink-bar" :style='{left:left+"rpx",background:indicatorActiveColor}'></view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
name: 'column',
props: {
type: {
type: String,
default () {
return "swiper"
}
},
list: {
type: Array,
default () {
return []
}
},
colNum: {
type: Number,
default () {
return 4
}
},
rowNum: {
type: Number,
default () {
return 2
}
},
indicatorActiveColor: {
type: String,
default () {
return '#f6f5fa'
}
},
indicatorColor: {
type: String,
default () {
return '#f6f5fa'
}
},
wingBlank: {
type: Number,
default () {
return 0
}
},
whiteSpace: {
type: Number,
default () {
return 30
}
},
borderRadius: {
type: Number,
default () {
return 0
}
}
},
created() {
},
data() {
return {
activeIndex: 0,
newList: [],
left: 0,
current: 0,
colorList: ["#fc7f87", "#56b4fc", "#f8ae41", "#11dd9e"]
}
},
computed: {
...mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
}),
formatRowNum() {
let {
colNum,
rowNum,
list
} = this;
let length = list.length;
let newRowNum = length <= colNum ? 1 : rowNum;
return newRowNum
},
formatList() {
let {
colNum,
rowNum,
type,
list,
formatRowNum
} = this;
let index = 0;
let length = list.length;
let newList = [];
let count = formatRowNum * colNum
// let count = type == 'scroll' ? colNum : formatRowNum * colNum
while (index < length) {
newList.push(list.slice(index, index += count));
}
return newList
},
},
methods: {
handerScroll: function(e) {
let {
scrollLeft,
scrollWidth
} = e.detail;
let windowWidth = uni.getSystemInfoSync().windowWidth;
let left = scrollLeft * 30 / (scrollWidth - windowWidth);
this.left = left
},
handerChange: function(e) {
this.current = e.detail.current;
},
change(item) {
this.$emit("change", item)
},
getFirstText(d) {
d = d || '名称'
return d.slice(0, 1)
},
},
}
</script>
<style lang="scss">
/* 轮播图 */
.column-box {
background: #fff;
font-size: 24rpx;
color: #888;
.swiper-category {
width: 100%;
background: #fff;
font-size: 24rpx;
color: #666;
border-radius: 10rpx;
.swiper-category-item {
box-sizing: border-box;
display: flex;
align-items: flex-start;
flex-wrap: wrap;
background: #fff;
.column-item {
margin-top: 20rpx;
float: left;
.column-img {
width: 94rpx;
height: 94rpx;
margin: 0 auto;
}
.column-no-img {
width: 94rpx;
height: 94rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 36rpx;
color: #fff;
.iconfont {
font-size: 58rpx;
}
}
.column-text {
margin-top: 10rpx;
text-align: center;
color: #484848;
width: 100%;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
}
.scroll-x {
white-space: nowrap;
background: #fff;
padding: 20rpx 0;
.scroll-x-item {
position: relative;
display: inline-block;
vertical-align: text-top;
display: flex;
flex-direction: column;
}
.column-item {
margin-top: 20rpx;
float: left;
.column-img {
width: 94rpx;
height: 94rpx;
margin: 0 auto;
}
.column-text {
margin-top: 10rpx;
text-align: center;
color: #484848;
width: 100%;
padding: 0 10%;
display: block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
}
.dots {
background: white;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding-top: 20rpx;
.dot {
height: 8rpx;
width: 8rpx;
background: #E5E5E5;
border-radius: 8rpx;
margin: 0 8rpx;
}
}
.ink-bar-box {
background: #fff;
padding-bottom: 20rpx;
.ui-tabs-ink-bar-wrapper {
width: 60rpx;
background: #ddd;
position: relative;
left: 345rpx;
bottom: 0;
height: 8rpx;
transform-origin: 50% 50%;
transition: width 250ms ease-out, left 250ms ease-out;
overflow: hidden;
border-radius: 4rpx;
.ui-tabs-ink-bar {
position: absolute;
width: 30rpx;
background: #e74d45;
height: 8rpx;
border-radius: 4rpx;
}
}
}
}
</style>

161
components/common-popup.vue Normal file
View File

@ -0,0 +1,161 @@
<template>
<uni-popup type="center" :maskClick="maskClick" ref="common_popup">
<view class="common-popup-content fill-base">
<view class="title" v-if="ptitle">{{ptitle}}</view>
<view class="desc" v-if="pdesc">{{pdesc}}</view>
<image mode="aspectFill" class="image" :class="imgSize" :src="info.image" v-if="info.image"></image>
<view class="name" v-if="info.name">{{info.name}}</view>
<view class="button">
<block v-for="(item,index) in pbutton" :key="index">
<view @tap.stop="toEmit(index)" class="item-child"
:style="{background: item.type == 'confirm' ? primaryColor : '',color:item.type == 'confirm' ?'white':''}">
{{item.title}}
</view>
</block>
</view>
</view>
</uni-popup>
</template>
<script>
const Types = {
'REFUSE_ORDER': {
title: '拒绝接单',
desc: '请确认是否拒绝接单',
text: '确认拒绝'
},
'CANCEL_ORDER': {
title: '取消订单',
desc: '请确认是否取消订单,取消后将无法恢复',
text: '确认取消'
},
'DELETE_ORDER': {
title: '删除订单',
desc: '请确认是否删除订单,删除后将无法恢复',
text: '确认删除'
},
'CANCEL_REFUND_ORDER': {
title: '取消退款',
desc: '请确认是否取消退款',
text: '确认取消'
},
'NO_PASS_REFUND': {
title: '拒绝退款',
desc: '请确认是否拒绝退款',
text: '确认拒绝'
},
'HX_CODE': {
title: '核销码',
desc: '请出示二维码给核销人员',
},
'HX_ORDER': {
title: '核销订单',
desc: '请确认是否核销订单',
text: '确认核销'
},
}
import {
mapState,
mapMutations
} from "vuex"
export default {
components: {},
props: {
maskClick: {
type: Boolean,
default () {
return false
}
},
type: {
type: String,
default () {
return 'CANCEL_ORDER'
}
},
title: {
type: String,
default () {
return ''
}
},
desc: {
type: String,
default () {
return ''
}
},
info: {
type: Object,
default () {
return {}
}
},
button: {
type: Array,
default () {
return [{
title: '取消',
type: 'cancel'
}, {
title: '确定取消',
type: 'confirm'
}]
}
},
imgSize: {
type: String,
default () {
return ''
}
}
},
created() {
this.init();
},
data() {
return {
ptitle: '',
pdesc: '',
pbutton: [],
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
}),
methods: {
init() {
let {
type,
} = this;
if (Types[type]) {
this.ptitle = this.title || Types[type].title
this.pdesc = this.desc || Types[type].desc
this.pbutton = this.button
if (!Types[type].text) return
this.pbutton[1].title = Types[type].text
}
},
open() {
this.$refs.common_popup.open()
},
close() {
this.$refs.common_popup.close()
},
toEmit(index) {
let {
type
} = this.button[index]
if (type == 'cancel') {
this.close()
} else {
this.$emit(type)
}
}
},
}
</script>
<style scoped lang="scss">
</style>

View File

@ -0,0 +1,98 @@
<template>
<view class="content" :style="{background: bgColor}">
<block v-for="(item,index) in text" :key="index">
<!-- 无间隔的按钮 -->
<auth @tap.stop.prevent :needAuth="item.isAuth && (userInfo && (!userInfo.phone || !userInfo.nickName))"
:must="true" :type="!userInfo.phone ? 'phone' : 'userInfo'" @go="confirm(item)" :style="{
width:(100/ text.length)+ '%'}" v-if="classType === 1">
<view class="bottom-view" :style="{
margin:text.length==1?'0 30rpx':index==0?'0 0 0 30rpx':'0 30rpx 0 0',
borderRadius:text.length==1?'88rpx':index==0?'88rpx 0 0 88rpx':'0 88rpx 88rpx 0',
background: item.type == 'confirm'? `linear-gradient(68deg, ${primaryColor}, ${subColor})` : subColor}">
{{ item.text }}
</view>
</auth>
<!-- 有间隔的圆弧形按钮 -->
<auth @tap.stop.prevent :needAuth="item.isAuth && (userInfo && (!userInfo.phone || !userInfo.nickName))"
:must="true" :type="!userInfo.phone ? 'phone' : 'userInfo'" @go="confirm(item)" :style="{
width:text.length==2 ? index==0 ? '390rpx' : '360rpx' : (100/ text.length) + '%'}" v-if="classType === 2">
<view class="bottom-view" :style="{
margin:text.length==1?'0 30rpx':index==0?'0 30rpx 0 30rpx':'0 30rpx 0 0',
borderRadius:'88rpx',
background: item.type == 'confirm'? `linear-gradient(68deg, ${primaryColor}, ${subColor})` : '',
color: item.type === 'confirm' ? '#fff' : '#000',
transform: 'rotateZ(360deg)',
border: item.type == 'confirm'? `1rpx solid ${primaryColor}` : '1rpx solid #C7C7C7'}">
{{ item.text }}
</view>
</auth>
</block>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from "vuex"
export default {
props: {
text: {
type: Array,
default () {
return [{
text: '保存',
type: 'confirm'
}]
}
},
bgColor: {
type: String,
default () {
return '#F8F8F8'
}
},
classType: {
type: Number,
default () {
return 1
}
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
userInfo: state => state.user.userInfo,
}),
methods: {
confirm(item) {
this.$emit(item.type)
}
}
}
</script>
<style scoped lang="scss">
.content {
position: fixed;
bottom: 0rpx;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 128rpx;
z-index: 997;
height: calc(128rpx + env(safe-area-inset-bottom) / 2);
padding-bottom: calc(env(safe-area-inset-bottom) / 2);
.bottom-view {
width: auto;
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 32rpx;
font-weight: bold;
color: #FFFFFF;
}
}
</style>

86
components/fixed.vue Normal file
View File

@ -0,0 +1,86 @@
<template>
<view>
<view class="ui-fixed" :class="{'fixed-top':position=='top','fixed-bottom':position=='bottom'}"
:style="{top:position=='top'?`${top}px`:`` , zIndex: zIndex}">
<slot></slot>
</view>
<view :style="{height:height+'px'}" v-if="position=='top'"></view>
</view>
</template>
<script>
export default {
name: 'fixed',
props: {
position: {
type: [String],
default () {
return 'top'
}
},
top: {
type: [Number],
default () {
return 0
}
},
zIndex: {
type: [Number],
default () {
return 999
}
},
initHeight: {
type: String || Number,
default () {
return ''
}
},
},
data() {
return {
height: uni.getSystemInfoSync().windowWidth * 100 / 750
}
},
mounted() {
setTimeout(() => {
this.setHeight();
}, 100)
},
watch: {
initHeight(newVal, oldval) {
this.$nextTick(() => {
this.setHeight()
})
}
},
methods: {
setHeight() {
var _this = this;
var query = uni.createSelectorQuery().in(_this);
query.select('.ui-fixed').boundingClientRect(function(res) {
_this.height = res.height;
_this.$emit('height', res.height)
}).exec();
}
},
}
</script>
<style>
.ui-fixed {
position: fixed;
left: 0;
width: 100%;
z-index: 999;
}
.fixed-top {
top: 0;
}
.fixed-bottom {
bottom: 0;
}
</style>

View File

@ -0,0 +1,62 @@
var inlineTags = {
abbr: true,
b: true,
big: true,
code: true,
del: true,
em: true,
font: true,
i: true,
ins: true,
label: true,
mark: true,
q: true,
s: true,
small: true,
span: true,
strong: true,
u: true
}
export default {
getStyle: function(style, display) {
var res = "";
var reg = getRegExp("float\s*:\s*[^;]*", "i");
if (reg.test(style)) res += reg.exec(style)[0];
reg = getRegExp("margin[^;]*", "gi");
var margin = reg.exec(style);
while (margin) {
res += (';' + margin[0]);
margin = reg.exec(style);
}
reg = getRegExp("display\s*:\s*([^;]*)", "i");
if (reg.test(style) && reg.exec(style)[1] != "flex") res += (';' + reg.exec(style)[0]);
else res += (';display:' + display);
reg = getRegExp("flex\s*:[^;]*", "i");
if (reg.test(style)) res += (';' + reg.exec(style)[0]);
reg = getRegExp("[^;\s]*width[^;]*", "ig");
var width = reg.exec(style);
while (width) {
res += (';' + width[0]);
width = reg.exec(style);
}
return res;
},
setImgStyle: function(item, imgMode) {
if (imgMode == "widthFix")
item.attrs.style = (item.attrs.style || '') + ";height:auto !important";
if (getRegExp("[^-]width[^pev;]+").test(";" + item.attrs.style))
item.attrs.style = (item.attrs.style || '') + ";width:100%";
if (item.attrs.style)
item.attrs.style = item.attrs.style.replace(getRegExp('margin[^;]*', "gi"), "");
return [item];
},
setStyle: function(item) {
if (item.attrs.style)
item.attrs.style = item.attrs.style.replace(getRegExp("width[^;]*?%", "gi"), "width:100%").replace(getRegExp(
'margin[^;]+', "gi"), "");
return [item];
},
notContinue: function(item) {
return !(item.c || inlineTags[item.name] || item["continue"]);
}
}

View File

@ -0,0 +1,58 @@
var inlineTags = {
abbr: true,
b: true,
big: true,
code: true,
del: true,
em: true,
font: true,
i: true,
ins: true,
label: true,
mark: true,
q: true,
s: true,
small: true,
span: true,
strong: true,
u: true
}
module.exports = {
getStyle: function(style, display) {
var res = "";
var reg = getRegExp("float[^;]+(?![\s\S]*?float)", "i");
if (reg.test(style)) res += reg.exec(style)[0];
reg = getRegExp("margin[^;]+", "gi");
if (reg.test(style)) res += (';' + style.match(reg).join(';'));
reg = getRegExp("display\s*:\s*([^;]*)(?![\s\S]*?display)", "i");
if (reg.test(style) && reg.exec(style)[1] != "flex") res += (';' + reg.exec(style)[0]);
else res += (';display:' + display);
reg = getRegExp("flex[^;]*:[^;]+", "ig");
if (reg.test(style)) res += (';' + style.match(reg).join(';'));
reg = getRegExp("[^;\s]*width[^;]+", "ig");
if (reg.test(style)) res += (';' + style.match(reg).join(';'));
return res;
},
setImgStyle: function(item, imgMode, imgLoad) {
if (imgMode == "widthFix") item.attrs.style = (item.attrs.style || '') + ";height:auto !important";
if (item.attrs.style)
item.attrs.style = item.attrs.style.replace(getRegExp("width[^;]*?%", "gi"), "width:100%").replace(
getRegExp(
'margin[^;]+', "gi"), "");
if (!imgLoad) {
delete item.attrs.src;
item.attrs.style += ";width:20px !important;height:20px !important";
}
return [item];
},
setStyle: function(item) {
if (item.attrs.style)
item.attrs.style = item.attrs.style.replace(getRegExp("width[^;]*?%", "gi"), "width:100%").replace(
getRegExp(
'margin[^;]+', "gi"), "");
return [item];
},
notContinue: function(item) {
return !(item.c || inlineTags[item.name] || item["continue"]);
}
}

View File

@ -0,0 +1,617 @@
<!--
parser 主模块组件
github地址https://github.com/jin-yufeng/Parser
文档地址https://jin-yufeng.github.io/Parser
插件市场https://ext.dcloud.net.cn/plugin?id=805
authorJinYufeng
-->
<template>
<view>
<!--#ifdef H5-->
<slot v-if="!html && !nodes.length"></slot>
<div :id="'rtf' + uid" :style="(selectable ? 'user-select:text;-webkit-user-select:text;' : '') + (showWithAnimation ? ('opacity:0;' + showAnimation) : '')"></div>
<!--#endif-->
<!--#ifndef H5-->
<slot v-if="!html[0].name && !html[0].type && !nodes.length"></slot>
<!--#endif-->
<!--#ifdef MP-ALIPAY-->
<view class="_contain" :style="(selectable ? 'user-select:text;-webkit-user-select:text;' : '') + (showWithAnimation ? ('opacity:0;' + showAnimation) : '')">
<trees :nodes="nodes.length ? nodes : (html[0].name || html[0].type ? html : [])" :imgMode="imgMode" />
</view>
<!--#endif-->
<!--#ifndef MP-ALIPAY || H5-->
<trees class="_contain" :style="'display:block;' + (selectable ? 'user-select:text;-webkit-user-select:text;' : '') + (showWithAnimation ? ('opacity:0;' + showAnimation) : '')"
:nodes="nodes.length ? nodes : (html[0].name || html[0].type ? html : [])" :imgMode="imgMode" :loadVideo="loadVideo" />
<!--#endif-->
</view>
</template>
<script>
// #ifndef H5
import trees from "./trees"
var document; // document https://jin-yufeng.github.io/Parser/#/instructions?id=document
const parseHtmlSync = require('./libs/MpHtmlParser.js').parseHtmlSync;
const cache = getApp().parserCache = {};
const CssHandler = require("./libs/CssHandler.js");
// cache key
const Hash = (str) => {
for (var i = 0, hash = 5381, len = str.length; i < len; i++)
hash += (hash << 5) + str.charCodeAt(i);
return hash;
};
// #endif
//
const showAnimation =
"transition:400ms ease 0ms;transition-property:transform,opacity;transform-origin:50% 50% 0;-webkit-transition:400ms ease 0ms;-webkit-transform:;-webkit-transition-property:transform,opacity;-webkit-transform-origin:50% 50% 0;opacity: 1"
const config = require('./libs/config.js');
// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
//
const Deduplication = (src) => {
if (src.indexOf("http") != 0) return src;
var newSrc = '';
for (var i = 0; i < src.length; i++) {
newSrc += (Math.random() >= 0.5 ? src[i].toUpperCase() : src[i].toLowerCase());
if (src[i] == '/' && src[i - 1] != '/' && src[i + 1] != '/') break;
}
newSrc += src.substring(i + 1);
return newSrc;
}
// #endif
export default {
name: 'parser',
data() {
return {
// #ifdef APP-PLUS
loadVideo: false,
// #endif
// #ifdef H5
uid: this._uid,
showAnimation: '',
// #endif
// #ifndef H5
showAnimation: {},
controls: {},
// #endif
nodes: []
}
},
// #ifndef H5
components: {
trees
},
// #endif
props: {
'html': {
type: null,
default: null
},
'autocopy': {
type: Boolean,
default: true
},
// #ifndef MP-ALIPAY
'autopause': {
type: Boolean,
default: true
},
// #endif
'autopreview': {
type: Boolean,
default: true
},
'autosetTitle': {
type: Boolean,
default: true
},
'domain': {
type: String,
default: null
},
'imgMode': {
type: String,
default: 'default'
},
// #ifdef MP-WEIXIN || MP-QQ || H5 || APP-PLUS
'lazyLoad': {
type: Boolean,
default: false
},
// #endif
'selectable': {
type: Boolean,
default: false
},
'tagStyle': {
type: Object,
default: () => {
return {};
}
},
'showWithAnimation': {
type: Boolean,
default: false
},
'useAnchor': {
type: Boolean,
default: false
},
'useCache': {
type: Boolean,
default: false
}
},
watch: {
html(html) {
this.setContent(html, undefined, true);
}
},
mounted() {
this.imgList = [];
this.imgList.each = function(f) {
for (var i = 0; i < this.length; i++) {
// #ifdef MP-ALIPAY || APP-PLUS
this[i] = f(this[i], i, this) || this[i];
// #endif
// #ifndef MP-ALIPAY || APP-PLUS
var newSrc = f(this[i], i, this);
if (newSrc) {
if (this.includes(newSrc)) this[i] = Deduplication(newSrc);
else this[i] = newSrc;
}
// #endif
}
}
this.setContent(this.html, undefined, true);
},
// #ifdef H5
beforeDestroy() {
if (this._observer) this._observer.disconnect();
},
// #endif
methods: {
// #ifdef H5
setContent(html, options, observed) {
if (typeof options == "object")
for (var key in options) {
key = key.replace(/-(\w)/g, function() {
return arguments[1].toUpperCase();
})
this[key] = options[key];
}
html = html || '';
if (!html) {
if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
return;
}
if (typeof html != 'string') html = this.Dom2Str(html.nodes || html);
// rpx
if (/[0-9.]*?rpx/.test(html)) {
const rpx = uni.getSystemInfoSync().screenWidth / 750;
html = html.replace(/([0-9.]*?)rpx/g, function() {
return parseFloat(arguments[1]) * rpx + "px";
})
}
// tag-style userAgentStyles
var style = "<style>";
for (var item in config.userAgentStyles)
style += (item + '{' + config.userAgentStyles[item] + '}');
for (var item in this.tagStyle)
style += (item + '{' + this.tagStyle[item] + '}');
style += "</style>";
html = style + html;
if (this.rtf) this.rtf.parentNode.removeChild(this.rtf);
this.rtf = document.createElement('div');
this.rtf.innerHTML = html;
for (var style of this.rtf.getElementsByTagName("style")) {
style.innerHTML = style.innerHTML.replace(/\s*body/g, "#rtf" + this._uid);
style.setAttribute("scoped", "true");
}
//
if (this.lazyLoad && IntersectionObserver) {
if (this._observer) this._observer.disconnect();
this._observer = new IntersectionObserver(changes => {
for (var change of changes) {
if (change.isIntersecting) {
change.target.src = change.target.getAttribute("data-src");
change.target.removeAttribute("data-src");
this._observer.unobserve(change.target);
}
}
}, {
rootMargin: "1000px 0px 1000px 0px"
})
}
var component = this;
//
var title = this.rtf.getElementsByTagName("title");
if (title.length && this.autosetTitle)
uni.setNavigationBarTitle({
title: title[0].innerText
})
//
this.imgList.length = 0;
var imgs = this.rtf.getElementsByTagName("img");
for (var i = 0; i < imgs.length; i++) {
var img = imgs[i];
img.style.maxWidth = "100%";
img.i = i;
if (this.domain && img.getAttribute("src")[0] == "/") {
if (img.getAttribute("src")[1] == "/")
img.src = (this.domain.includes("://") ? this.domain.split("://")[0] : "http") + ':' + img.getAttribute("src");
else img.src = this.domain + img.getAttribute("src");
}
component.imgList.push(img.src);
if (img.parentElement.nodeName != 'A') {
img.onclick = function() {
if (!this.hasAttribute('ignore')) {
var preview = true;
this.ignore = () => preview = false;
component.$emit('imgtap', this);
if (preview && component.autopreview) {
uni.previewImage({
current: this.i,
urls: component.imgList
});
}
}
}
}
img.onerror = function() {
component.$emit('error', {
source: "img",
target: this
});
}
if (component.lazyLoad && this._observer) {
img.setAttribute("data-src", img.src);
img.removeAttribute("src");
this._observer.observe(img);
}
}
//
var links = this.rtf.getElementsByTagName("a");
for (var link of links) {
link.onclick = function(e) {
var jump = true,
href = this.getAttribute("href");
component.$emit('linkpress', {
href,
ignore: () => jump = false
});
if (jump && href) {
if (href[0] == '#') {
if (component.useAnchor) {
component.navigateTo({
id: href.substring(1)
})
}
} else if (href.indexOf("http") == 0 || href.indexOf("//") == 0)
return true;
else {
uni.navigateTo({
url: href
})
}
}
return false;
}
}
//
var videos = this.rtf.getElementsByTagName("video");
component.videoContexts = videos;
for (var video of videos) {
video.style.maxWidth = "100%";
video.onerror = function() {
component.$emit('error', {
source: "video",
target: this
});
}
video.onplay = function() {
if (component.autopause) {
for (var video of component.videoContexts) {
if (video != this)
video.pause();
}
}
}
}
//
var audios = this.rtf.getElementsByTagName("audios");
for (var audio of audios) {
audio.onerror = function(e) {
component.$emit('error', {
source: "audio",
target: this
});
}
}
document.getElementById("rtf" + this._uid).appendChild(this.rtf);
if (this.showWithAnimation)
this.showAnimation = showAnimation;
if (!observed) this.nodes = [0];
this.$nextTick(() => {
this.$emit("ready", this.rtf.getBoundingClientRect());
})
},
Dom2Str(nodes) {
var str = "";
for (var node of nodes) {
if (node.type == "text")
str += node.text;
else {
str += ('<' + node.name);
for (var attr in node.attrs || {})
str += (' ' + attr + '="' + node.attrs[attr] + '"');
if (!node.children || !node.children.length) str += "/>";
else str += ('>' + this.Dom2Str(node.children) + "</" + node.name + '>');
}
}
return str;
},
getText(whiteSpace = true) {
if (!whiteSpace) return this.rtf.innerText.replace(/\s/g, '');
return this.rtf.innerText;
},
navigateTo(obj) {
if (!obj.id) {
window.scrollTo(0, this.rtf.offsetTop);
return obj.success ? obj.success({
errMsg: "pageScrollTo:ok"
}) : null;
}
var target = document.getElementById(obj.id);
if (!target) return obj.fail ? obj.fail({
errMsg: "Label Not Found"
}) : null;
uni.pageScrollTo({
scrollTop: this.rtf.offsetTop + target.offsetTop,
success: obj.success,
fail: obj.fail
});
},
// #endif
// #ifndef H5
setContent(html, options, observed) {
if (typeof options == "object")
for (var key in options) {
key = key.replace(/-(\w)/g, function() {
return arguments[1].toUpperCase();
})
this[key] = options[key];
}
if (this.showWithAnimation)
this.showAnimation = showAnimation;
if (!html) {
if (observed) return;
else this.nodes = [];
} else if (typeof html == "string") {
var res;
//
if (this.useCache) {
var hash = Hash(html);
if (cache[hash])
res = cache[hash];
else {
res = parseHtmlSync(html, this);
cache[hash] = res;
}
} else res = parseHtmlSync(html, this);
this.nodes = res;
this.$emit('parse', res);
} else if (html.constructor == Array) {
if (!observed) this.nodes = html;
else this.nodes = [];
// array
if (html.length && html[0].PoweredBy != "Parser") {
const Parser = {
_imgNum: 0,
_videoNum: 0,
_audioNum: 0,
_domain: this.domain,
_protocol: this.domain ? (this.domain.includes("://") ? this.domain.split("://")[0] : "http") : undefined,
_STACK: [],
CssHandler: new CssHandler(this.tagStyle)
};
Parser.CssHandler.getStyle('');
const DFS = (nodes) => {
for (var node of nodes) {
if (node.type == "text") continue;
node.attrs = node.attrs || {};
for (var item in node.attrs) {
if (!config.trustAttrs[item]) node.attrs[item] = undefined;
else if (typeof node.attrs[item] != "string") node.attrs[item] = node.attrs[item].toString();
}
config.LabelAttrsHandler(node, Parser);
if (config.blockTags[node.name]) node.name = 'div';
else if (!config.trustTags[node.name]) node.name = 'span';
if (node.children && node.children.length) {
Parser._STACK.push(node);
DFS(node.children);
Parser._STACK.pop();
} else node.children = undefined;
}
}
DFS(html);
this.nodes = html;
}
} else if (typeof html == 'object' && html.nodes) {
this.nodes = html.nodes;
console.warn("Parser 类型错误object 类型已废弃,请直接将 html 设置为 object.nodes array 类型)");
} else {
return this.$emit('error', {
source: "parse",
errMsg: "传入的nodes数组格式不正确应该传入的类型是array实际传入的类型是" + typeof html.nodes
});
}
// #ifdef APP-PLUS
this.loadVideo = false;
// #endif
if (document) this.document = new document("html", this.html || html, this);
this.$nextTick(() => {
this.imgList.length = 0;
this.videoContexts = [];
const getContext = (components) => {
for (let component of components) {
if (component.$options.name == "trees") {
var observered = false;
for (var item of component.nodes) {
if (item.continue) continue;
if (item.name == 'img') {
if (item.attrs.src && item.attrs.i) {
// #ifndef MP-ALIPAY || APP-PLUS
if (this.imgList.indexOf(item.attrs.src) == -1)
this.imgList[item.attrs.i] = item.attrs.src;
else this.imgList[item.attrs.i] = Deduplication(item.attrs.src);
// #endif
// #ifdef MP-ALIPAY || APP-PLUS
this.imgList[item.attrs.i] = item.attrs.src;
// #endif
}
// #ifndef MP-ALIPAY
if (!observered) {
observered = true;
if (this.lazyLoad && uni.createIntersectionObserver) {
if (component._observer) component._observer.disconnect();
component._observer = uni.createIntersectionObserver(component);
component._observer.relativeToViewport({
top: 1000,
bottom: 1000
}).observe('.img', res => {
component.imgLoad = true;
component._observer.disconnect();
component._observer = null;
})
} else
component.imgLoad = true;
}
// #endif
}
// #ifndef MP-ALIPAY
else if (item.name == 'video') {
var context = uni.createVideoContext(item.attrs.id, component);
context.id = item.attrs.id;
this.videoContexts.push(context);
}
// #endif
// #ifdef MP-WEIXIN
else if (item.name == 'audio' && item.attrs.autoplay)
wx.createAudioContext(item.attrs.id, component).play();
// #endif
//
else if (item.name == "title") {
if (item.children[0].type == "text" && item.children[0].text && this.autosetTitle)
uni.setNavigationBarTitle({
title: item.children[0].text
})
}
// #ifdef MP-BAIDU || MP-ALIPAY
if (item.attrs && item.attrs.id) {
this.anchors = this.anchors || [];
this.anchors.push({
id: item.attrs.id,
node: component
})
}
// #endif
}
}
if (component.$children.length)
getContext(component.$children)
}
}
// #ifdef MP-TOUTIAO
setTimeout(() => {
// #endif
getContext(this.$children)
uni.createSelectorQuery().in(this).select("._contain").boundingClientRect(res => {
this.$emit("ready", res);
}).exec();
// #ifdef MP-TOUTIAO
}, 200)
// #endif
// #ifdef APP-PLUS
setTimeout(() => {
this.loadVideo = true;
}, 3000);
// #endif
})
},
getText(whiteSpace = true) {
var text = "";
const DFS = (node) => {
if (node.type == "text") return text += node.text;
else {
if (whiteSpace && (((node.name == 'p' || node.name == "div" || node.name == "tr" || node.name == "li" ||
/h[1-6]/.test(node.name)) && text && text[text.length - 1] != '\n') || node.name == "br"))
text += '\n';
for (var child of node.children || [])
DFS(child);
if (whiteSpace && (node.name == 'p' || node.name == "div" || node.name == "tr" || node.name == "li" || /h[1-6]/.test(
node.name)) && text && text[text.length - 1] != '\n')
text += '\n';
else if (whiteSpace && node.name == "td") text += '\t';
}
}
var nodes = ((this.nodes && this.nodes.length) ? this.nodes : (this.html[0] && (this.html[0].name || this.html[0].type) ?
this.html : []));
if (!nodes.length) return "";
for (var node of nodes)
DFS(node);
return text;
},
navigateTo(obj) {
var Scroll = (selector, component) => {
const query = uni.createSelectorQuery().in(component ? component : this);
query.select(selector).boundingClientRect();
query.selectViewport().scrollOffset();
query.exec(res => {
if (!res || !res[0])
return obj.fail ? obj.fail({
errMsg: "Label Not Found"
}) : null;
uni.pageScrollTo({
scrollTop: res[1].scrollTop + res[0].top,
success: obj.success,
fail: obj.fail
})
})
}
if (!obj.id) Scroll("._contain");
else {
// #ifndef MP-BAIDU || MP-ALIPAY
Scroll('._contain >>> #' + obj.id + ', ._contain >>> .' + obj.id);
// #endif
// #ifdef MP-BAIDU || MP-ALIPAY
for (var anchor of this.anchors) {
if (anchor.id == obj.id) {
Scroll("#" + obj.id + ", ." + obj.id, anchor.node);
}
}
// #endif
}
},
// #endif
getVideoContext(id) {
if (!id) return this.videoContexts;
else {
for (var video of this.videoContexts) {
if (video.id == id) return video;
}
}
return null;
}
}
}
</script>
<style>
/* #ifndef MP-BAIDU */
:host {
display: block;
overflow: scroll;
-webkit-overflow-scrolling: touch;
}
/* #endif */
</style>

View File

@ -0,0 +1,149 @@
/*
解析和匹配 Css 的选择器
github地址https://github.com/jin-yufeng/Parser
文档地址https://jin-yufeng.github.io/Parser
authorJinYufeng
*/
const userAgentStyles = require("./config.js").userAgentStyles;
class CssHandler {
constructor(tagStyle = {}) {
this.styles = Object.assign({}, tagStyle);
};
getStyle(data) {
var style = '';
data = data.replace(/<[sS][tT][yY][lL][eE][\s\S]*?>([\s\S]*?)<\/[sS][tT][yY][lL][eE][\s\S]*?>/g, function() {
style += arguments[1];
return '';
})
this.styles = new CssParser(style, this.styles).parse();
return data;
};
parseCss(css) {
return new CssParser(css, {}, true).parse();
};
match(name, attrs) {
var tmp, matched = ((tmp = this.styles[name]) ? (tmp + ';') : '');
if (attrs.class) {
var classes = attrs.class.split(' ');
for (var i = 0; i < classes.length; i++)
if (tmp = this.styles['.' + classes[i]])
matched += (tmp + ';');
}
if (tmp = this.styles['#' + attrs.id])
matched += tmp;
return matched;
};
}
module.exports = CssHandler;
function isBlankChar(c) {
return c == ' ' || c == '\u00A0' || c == '\t' || c == '\r' || c == '\n' || c == '\f';
};
class CssParser {
constructor(data, tagStyle, api) {
this.data = data;
this.res = tagStyle;
if (!api)
for (var item in userAgentStyles) {
if (tagStyle[item]) tagStyle[item] = userAgentStyles[item] + ';' + tagStyle[item];
else tagStyle[item] = userAgentStyles[item];
}
this._floor = 0;
this._i = 0;
this._list = [];
this._comma = false;
this._sectionStart = 0;
this._state = this.Space;
};
parse() {
for (; this._i < this.data.length; this._i++)
this._state(this.data[this._i]);
return this.res;
};
// 状态机
Space(c) {
if (c == '.' || c == '#' || (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
this._sectionStart = this._i;
this._state = this.StyleName;
} else if (c == '/' && this.data[this._i + 1] == '*')
this.Comment();
else if (!isBlankChar(c) && c != ';')
this._state = this.Ignore;
};
Comment() {
this._i = this.data.indexOf("*/", this._i);
if (this._i == -1) this._i = this.data.length;
this._i++;
this._state = this.Space;
};
Ignore(c) {
if (c == '{') this._floor++;
else if (c == '}' && --this._floor <= 0) {
this._list = [];
this._state = this.Space;
}
};
StyleName(c) {
if (isBlankChar(c)) {
this._list.push(this.data.substring(this._sectionStart, this._i));
this._state = this.NameSpace;
} else if (c == '{') {
this._list.push(this.data.substring(this._sectionStart, this._i));
this._floor = 1;
this._sectionStart = this._i + 1;
this.Content();
} else if (c == ',') {
this._list.push(this.data.substring(this._sectionStart, this._i));
this._sectionStart = this._i + 1;
this._comma = true;
} else if (!(c >= 'a' && c <= 'z') && !(c >= 'A' && c <= 'Z') && !(c >= '0' && c <= '9') && c != '.' && c != '#' &&
c != '-' && c != '_')
this._state = this.Ignore;
};
NameSpace(c) {
if (c == '{') {
this._floor = 1;
this._sectionStart = this._i + 1;
this.Content();
} else if (c == ',') {
this._comma = true;
this._sectionStart = this._i + 1;
this._state = this.StyleName;
} else if (!isBlankChar(c)) {
if (this._comma) {
this._state = this.StyleName;
this._sectionStart = this._i;
this._i--;
this._comma = false;
} else this._state = this.Ignore;
}
};
Content() {
this._i = this.data.indexOf('}', this._i);
if (this._i == -1) this._i = this.data.length;
// 去除空白符
var flag = false,
pos, content = this.data.substring(this._sectionStart, this._i);
for (var i = 0; i < content.length; i++) {
if (isBlankChar(content[i])) {
if (!flag) {
pos = i;
flag = true;
}
} else {
if (flag) {
if (pos == 0) content = content.substring(i);
else if (i - pos > 1) content = content.substring(0, pos) + (content[pos - 1] == ';' ? (pos--, '') : ' ') +
content.substring(i);
i = pos;
flag = false;
}
}
}
if (flag) content = content.substring(0, pos);
for (var i = 0; i < this._list.length; i++)
this.res[this._list[i]] = (this.res[this._list[i]] || '') + content;
this._list = [];
this._state = this.Space;
}
}

View File

@ -0,0 +1,441 @@
/*
html 解析为适用于小程序 rich-text DOM 结构
github地址https://github.com/jin-yufeng/Parser
文档地址https://jin-yufeng.github.io/Parser
authorJinYufeng
*/
const CssHandler = require("./CssHandler.js");
const config = require("./config.js");
var emoji; // 需要使用 emoji 补丁包时将此行改为 const emoji = require("./emoji.js");
function isBlankChar(c) {
return c == ' ' || c == '\u00A0' || c == '\t' || c == '\r' || c == '\n' || c == '\f';
};
class MpHtmlParser {
constructor(data, options = {}, cb) {
this.cb = cb;
this.CssHandler = new CssHandler(options.tagStyle);
this.data = data;
this.DOM = [];
// #ifdef MP-BAIDU || MP-TOUTIAO
this._imgMode = options.imgMode;
// #endif
this._attrName = '';
this._attrValue = '';
this._attrs = {};
this._domain = options.domain;
this._protocol = options.domain ? (options.domain.includes("://") ? this._domain.split("://")[0] : "http") :
undefined;
this._i = 0;
this._sectionStart = 0;
this._state = this.Text;
this._STACK = [];
this._tagName = '';
this._audioNum = 0;
this._imgNum = 0;
this._videoNum = 0;
this._useAnchor = options.useAnchor;
this._whiteSpace = false;
};
parse() {
if (this.CssHandler) this.data = this.CssHandler.getStyle(this.data);
if (emoji) this.data = emoji.parseEmoji(this.data);
// 高亮处理
if (config.highlight)
this.data = this.data.replace(/<[pP][rR][eE]([\s\S]*?)>([\s\S]*?)<\/[pP][rR][eE][\s\S]*?>/g,
function() {
return "<pre" + arguments[1] + '>' + config.highlight(arguments[2], "<pre" + arguments[1] +
'>') + "</pre>";
})
for (var len = this.data.length; this._i < len; this._i++)
this._state(this.data[this._i]);
if (this._state == this.Text) this.setText();
while (this._STACK.length)
this.popNode(this._STACK.pop());
// #ifdef MP-BAIDU || MP-TOUTIAO
const inlineTags = config.makeMap(
"abbr,b,big,code,del,em,font,i,ins,label,mark,q,s,small,span,strong,sub,sup,u")
// 将顶层标签的一些样式提取出来给 rich-text
const setContain = function(nodes) {
for (var element of nodes) {
if (element.type == "text")
continue;
if (!element.c) {
var res = "";
var style = element.attrs.style;
var reg = /float[^;]+(?![\s\S]*?float)/i;
if (reg.test(style)) res += reg.exec(style)[0];
reg = /margin[^;]+/gi;
if (reg.test(style)) res += (';' + style.match(reg).join(';'));
reg = /display\s*:\s*([^;]*)(?![\s\S]*?display)/i;
if (reg.test(style) && reg.exec(style)[1] != "flex") res += (';' + reg.exec(style)[0]);
else if (inlineTags[element.name]) res += ";display:inline";
else res += (";display:" + (element.name == 'img' ? 'inline-block' : 'block'));
reg = /flex[^;]*:[^;]+/gi;
if (reg.test(style)) res += (';' + style.match(reg).join(';'));
reg = /[^;\s]*width[^;]+/gi;
if (reg.test(style)) res += (';' + style.match(reg).join(';'));
element.attrs.containStyle = res;
if (/[^-]width[^pev;]+/.test(";" + style))
element.attrs.style += ";width:100%";
let addMargin = "";
if (/margin\s*:/.test(style)) addMargin = ';margin:0';
else if (/margin-top/.test(style)) addMargin = ';margin-top:0';
else if (/margin-bottom/.test(style)) addMargin = ';margin-bottom:0';
element.attrs.style = (element.attrs.style || '').replace(/margin[^;]*/gi, "");
element.attrs.style += addMargin;
} else setContain(element.children);
}
};
setContain(this.DOM);
// #endif
if (this.DOM.length) this.DOM[0].PoweredBy = "Parser";
// console.log(this.DOM)
if (this.cb)
this.cb(this.DOM)
else return this.DOM;
};
// 设置属性
setAttr() {
if (config.trustAttrs[this._attrName])
this._attrs[this._attrName] = (this._attrValue ? this._attrValue : (this._attrName == "src" ? "" :
"true"));
this._attrValue = '';
while (isBlankChar(this.data[this._i])) this._i++;
if (this.checkClose()) this.setNode();
else this._state = this.AttrName;
};
// 设置文本节点
setText() {
var text = this.getSelection();
if (text) {
if (!this._whiteSpace) {
// 移除空白符
var flag = false,
has = false,
pos;
for (var i = 0; i < text.length; i++) {
if (isBlankChar(text[i])) {
if (!flag) {
pos = i;
flag = true;
}
} else {
has = true;
if (flag) {
if (i - pos > 1) text = text.substring(0, pos) + ' ' + text.substring(i);
i = pos;
flag = false;
}
}
}
if (flag) text = text.substring(0, pos) + ' ';
if (!text || !has) return;
}
// 检查实体
// #ifdef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO
var entities = {
lt: "<",
gt: ">",
amp: "&",
quot: '"',
apos: "'",
nbsp: "\u00A0",
ensp: "\u2002",
emsp: "\u2003",
ndash: "",
mdash: "—",
middot: "·",
lsquo: "",
rsquo: "",
ldquo: "“",
rdquo: "”",
bull: "•",
hellip: "…",
permil: "‰",
copy: "©",
reg: "®",
trade: "™",
times: "×",
divide: "÷",
cent: "¢",
pound: "£",
yen: "¥",
euro: "€",
sect: "§"
};
// #endif
var i = text.indexOf('&'),
j, decode;
while (i != -1 && i < text.length) {
j = text.indexOf(';', i);
if (j - i >= 2 && j - i <= 7) {
var entity = text.substring(i + 1, j);
// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
if (!entity.includes("sp") && !entity.includes("lt") && !entity.includes("gt")) {
decode = true
break;
}
// #endif
// #ifdef MP-BAIDU || MP-ALIPAY || MP-TOUTIAO
if (entities[entity]) text = text.substring(0, i) + entities[entity] + text.substring(j + 1);
// #endif
}
i = text.indexOf('&', i + 1);
}
var slibings = this._STACK.length ? this._STACK[this._STACK.length - 1].children : this.DOM;
if (slibings.length && slibings[slibings.length - 1].type == "text") {
slibings[slibings.length - 1].text += text;
if (decode) slibings[slibings.length - 1].decode = true;
} else
slibings.push({
type: "text",
text,
decode
})
}
};
// 设置元素节点
setNode() {
var slibings = this._STACK.length ? this._STACK[this._STACK.length - 1].children : this.DOM;
var node = {
name: this._tagName.toLowerCase(),
attrs: this._attrs
}
config.LabelAttrsHandler(node, this);
this._attrs = {};
if (this.data[this._i] == '>') {
if (!config.selfClosingTags[this._tagName]) {
if (config.ignoreTags[node.name]) {
var j = this._i;
// 处理要被移除的标签
while (this._i < this.data.length) {
this._i = this.data.indexOf("</", this._i);
if (this._i == -1) return this._i = this.data.length;
this._i += 2;
this._sectionStart = this._i;
while (!isBlankChar(this.data[this._i]) && this.data[this._i] != '>' && this.data[this
._i] != '/') this._i++;
if (this.data.substring(this._sectionStart, this._i).toLowerCase() == node.name) {
this._i = this.data.indexOf('>', this._i);
if (this._i == -1) this._i = this.data.length;
else this._sectionStart = this._i + 1;
this._state = this.Text;
// 处理svg
if (node.name == "svg") {
var src = this.data.substring(j, this._i + 1);
if (!node.attrs.xmlns) src = " xmlns=\"http://www.w3.org/2000/svg\"" + src;
this._i = j;
while (this.data[j] != '<') j--;
src = this.data.substring(j, this._i) + src;
this._i = this._sectionStart - 1;
node.name = "img";
node.attrs = {
src: "data:image/svg+xml;utf8," + src.replace(/#/g, "%23"),
ignore: "true"
}
slibings.push(node);
}
break;
}
}
return;
} else this._STACK.push(node);
node.children = [];
}
} else this._i++;
this._sectionStart = this._i + 1;
this._state = this.Text;
if (!config.ignoreTags[node.name]) {
// 检查空白符是否有效
if (node.name == "pre" || (node.attrs.style && node.attrs.style.toLowerCase().includes("white-space") &&
node.attrs
.style.toLowerCase().includes("pre"))) {
this._whiteSpace = true;
node.pre = true;
}
slibings.push(node);
}
};
// 标签出栈处理
popNode(node) {
// 替换一些标签名
if (config.blockTags[node.name]) node.name = 'div';
else if (!config.trustTags[node.name]) node.name = 'span';
// 空白符处理
if (node.pre) {
this._whiteSpace = false;
node.pre = undefined;
for (var i = 0; i < this._STACK.length; i++)
if (this._STACK[i].pre)
this._whiteSpace = true;
}
// 处理表格的边框
if (node.name == 'table') {
if (node.attrs.border)
node.attrs.style = "border:" + node.attrs.border + "px solid gray;" + (node.attrs.style || '');
if (node.attrs.hasOwnProperty("cellspacing"))
node.attrs.style = "border-spacing:" + node.attrs.cellspacing + "px;" + (node.attrs.style || '');
function setBorder(elem) {
if (elem.name == 'th' || elem.name == 'td') {
if (node.attrs.border)
elem.attrs.style = "border:" + node.attrs.border + "px solid gray;" + (elem.attrs.style ||
'');
if (node.attrs.hasOwnProperty("cellpadding"))
elem.attrs.style = "padding:" + node.attrs.cellpadding + "px;" + (elem.attrs.style || '');
return;
}
if (elem.type == 'text') return;
for (var i = 0; i < elem.children.length; i++)
setBorder(elem.children[i]);
}
if (node.attrs.border || node.attrs.hasOwnProperty("cellpadding"))
for (var i = 0; i < node.children.length; i++)
setBorder(node.children[i]);
}
// 合并一些不必要的层,减小节点深度
if (node.children.length == 1 && node.name == "div" && node.children[0].name == "div") {
var child = node.children[0];
node.attrs.style = node.attrs.style || '';
child.attrs.style = child.attrs.style || '';
if (node.attrs.style.includes("padding") && (node.attrs.style.includes("margin") || child.attrs.style
.includes(
"margin")) && node.attrs.style.includes("display") && child.attrs.style.includes(
"display") && !(node.attrs.id &&
node.attrs.id) && !(node.attrs.class && child.attrs.class)) {
if (child.attrs.style.includes("padding"))
child.attrs.style = "box-sizing:border-box;" + child.attrs.style;
node.attrs.style = node.attrs.style + ";" + child.attrs.style;
node.attrs.id = (child.attrs.id || '') + (node.attrs.id || '');
node.attrs.class = (child.attrs.class || '') + (node.attrs.class || '');
node.children = child.children;
}
}
// 多层样式处理
if (this.CssHandler.pop)
this.CssHandler.pop(node);
};
// 工具函数
checkClose() {
if (this.data[this._i] == '>' || (this.data[this._i] == '/' && this.data[this._i + 1] == '>'))
return true;
return false;
};
getSelection(trim) {
var str = (this._sectionStart == this._i ? '' : this.data.substring(this._sectionStart, this._i));
while (trim && isBlankChar(this.data[++this._i]));
if (trim) this._i--;
this._sectionStart = this._i + 1;
return str;
};
// 状态机
Text(c) {
if (c == '<') {
var next = this.data[this._i + 1];
if ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z')) {
this.setText();
this._state = this.TagName;
} else if (next == '/') {
this.setText();
this._i++;
next = this.data[this._i + 1];
if ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z')) {
this._sectionStart = this._i + 1;
this._state = this.EndTag;
} else
this._state = this.Comment;
} else if (next == '!') {
this.setText();
this._state = this.Comment;
}
}
};
Comment() {
if (this.data.substring(this._i + 1, this._i + 3) == "--" || this.data.substring(this._i + 1, this._i +
7) ==
"[CDATA[") {
this._i = this.data.indexOf("-->", this._i + 1);
if (this._i == -1) return this._i = this.data.length;
else this._i = this._i + 2;
} else {
this._i = this.data.indexOf(">", this._i + 1);
if (this._i == -1) return this._i = this.data.length;
}
this._sectionStart = this._i + 1;
this._state = this.Text;
};
TagName(c) {
if (isBlankChar(c)) {
this._tagName = this.getSelection(true);
if (this.checkClose()) this.setNode();
else this._state = this.AttrName;
} else if (this.checkClose()) {
this._tagName = this.getSelection();
this.setNode();
}
};
AttrName(c) {
if (isBlankChar(c)) {
this._attrName = this.getSelection(true).toLowerCase();
if (this.data[this._i] == '=') {
while (isBlankChar(this.data[++this._i]));
this._sectionStart = this._i;
this._i--;
this._state = this.AttrValue;
} else this.setAttr();
} else if (c == '=') {
this._attrName = this.getSelection().toLowerCase();
while (isBlankChar(this.data[++this._i]));
this._sectionStart = this._i;
this._i--;
this._state = this.AttrValue;
} else if (this.checkClose()) {
this._attrName = this.getSelection().toLowerCase();
this.setAttr();
}
};
AttrValue(c) {
if (c == '"' || c == "'") {
this._sectionStart++;
if ((this._i = this.data.indexOf(c, this._i + 1)) == -1) return this._i = this.data.length;
} else
for (; !isBlankChar(this.data[this._i] && this.data[this._i] != '/' && this.data[this._i] != '>'); this
._i++);
this._attrValue = this.getSelection();
while (this._attrValue.includes("&quot;")) this._attrValue = this._attrValue.replace("&quot;", '');
this.setAttr();
};
EndTag(c) {
if (isBlankChar(c) || c == '>' || c == '/') {
var name = this.getSelection().toLowerCase();
var flag = false;
for (var i = this._STACK.length - 1; i >= 0; i--)
if (this._STACK[i].name == name) {
flag = true;
break;
}
if (flag) {
var node;
while (flag) {
node = this._STACK.pop();
if (node.name == name) flag = false;
this.popNode(node);
}
} else if (name == 'p' || name == "br") {
var slibings = this._STACK.length ? this._STACK[this._STACK.length - 1].children : this.DOM;
var node = {
name
}
slibings.push(node);
}
this._i = this.data.indexOf('>', this._i);
if (this._i == -1) this._i = this.data.length;
else this._state = this.Text;
}
};
};
module.exports = {
parseHtml: (data, options) => new Promise((resolve) => new MpHtmlParser(data, options, resolve).parse()),
parseHtmlSync: (data, options) => new MpHtmlParser(data, options).parse()
};

View File

@ -0,0 +1,252 @@
/* 配置文件 */
function makeMap(str) {
var map = Object.create(null),
list = str.split(',');
for (var item of list)
map[item] = true;
return map;
}
// 信任的属性列表,不在列表中的属性将被移除
const trustAttrs = makeMap(
"align,alt,app-id,appId,"
// #ifdef MP-BAIDU
+
"appid,apid,"
// #endif
+
"author,autoplay,border,cellpadding,cellspacing,class,color,colspan,controls,data-src,dir,face,height,href,id,ignore,loop,muted,name,path,poster,rowspan,size,span,src,start,style,type,lbType,lbtype,"
// #ifdef MP-WEIXIN || MP-QQ
+
"unit-id,unitId,"
// #endif
+
"width,xmlns"
);
// 信任的标签,将保持标签名不变
const trustTags = makeMap(
"a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,u,ul,video,iframe"
);
// 块级标签,将被转为 div
const blockTags = makeMap("address,article,aside,body,center,cite,footer,header,html,nav,pre,section");
// 被移除的标签(其中 svg 系列标签会被转为图片)
const ignoreTags = makeMap(
"area,base,basefont,canvas,circle,command,ellipse,embed,frame,head,input,isindex,keygen,line,link,map,meta,param,path,polygon,rect,script,source,svg,textarea,track,use,wbr,"
);
// 只能用 rich-text 显示的标签(其中图片不能预览、不能显示视频、音频等)
const richOnlyTags = makeMap(
"a,ad,audio,colgroup,fieldset,legend,li,ol,sub,sup,table,tbody,td,tfoot,th,thead,tr,ul,video,iframe,");
// 自闭合标签
const selfClosingTags = makeMap(
"area,base,basefont,br,col,circle,ellipse,embed,frame,hr,img,input,isindex,keygen,line,link,meta,param,path,polygon,rect,source,track,use,wbr,"
);
// 默认的标签样式
var userAgentStyles = {
a: "color:#366092;word-break:break-all;padding:1.5px 0 1.5px 0",
address: "font-style:italic",
blockquote: "background-color:#f6f6f6;border-left:3px solid #dbdbdb;color:#6c6c6c;padding:5px 0 5px 10px",
center: "text-align:center",
cite: "font-style:italic",
code: "padding:0 1px 0 1px;margin-left:2px;margin-right:2px;background-color:#f8f8f8;border-radius:3px",
dd: "margin-left:40px",
img: "max-width:100%",
mark: "background-color:yellow",
pre: "font-family:monospace;white-space:pre;overflow:scroll",
s: "text-decoration:line-through",
u: "text-decoration:underline"
};
// #ifndef MP-ALIPAY || H5
const SDKVersion = uni.getSystemInfoSync().SDKVersion;
function versionHigherThan(version = '') {
var v1 = SDKVersion.split('.'),
v2 = version.split('.');
while (v1.length != v2.length)
v1.length < v2.length ? v1.push('0') : v2.push('0');
for (var i = 0; i < v1.length; i++) {
if (v1[i] == v2[i]) continue;
if (parseInt(v1[i]) > parseInt(v2[i])) return true;
return false;
}
return true;
};
// #endif
// #ifdef MP-WEIXIN || MP-QQ
// 版本兼容
if (versionHigherThan("2.7.1")) {
trustTags.bdi = true;
trustTags.bdo = true;
trustTags.caption = true;
trustTags.rt = true;
trustTags.ruby = true;
ignoreTags.rp = true;
trustTags.big = true;
trustTags.small = true;
trustTags.pre = true;
trustTags.iframe = true;
richOnlyTags.bdi = true;
richOnlyTags.bdo = true;
richOnlyTags.caption = true;
richOnlyTags.rt = true;
richOnlyTags.ruby = true;
richOnlyTags.pre = true;
blockTags.pre = undefined;
} else {
blockTags.caption = true;
userAgentStyles.big = "display:inline;font-size:1.2em";
userAgentStyles.small = "display:inline;font-size:0.8em";
}
// #endif
function bubbling(Parser) {
for (var i = Parser._STACK.length - 1; i >= 0; i--) {
if (!richOnlyTags[Parser._STACK[i].name])
Parser._STACK[i].c = 1;
else return false;
}
return true;
}
module.exports = {
// 高亮处理函数
highlight: null,
// 处理标签的属性,需要通过组件递归方式显示的标签需要调用 bubbling(Parser)
LabelAttrsHandler(node, Parser) {
let default_style = "max-width: 100% !important;display:block;"
node.attrs.style = Parser.CssHandler.match(node.name, node.attrs, node) + (node.attrs.style || '');
switch (node.name) {
case "ul":
case "ol":
case "li":
case "dd":
case "dl":
case "dt":
case "div":
case "span":
case "em":
case 'p':
if (node.name === 'span') {
default_style = 'white-space:normal;'
}
if (node.name === 'p' && (!node.attrs.style || !node.attrs.style.includes('margin-top:'))) {
default_style += 'margin-top:10px;'
}
if (node.attrs.style) {
node.attrs.style = node.attrs.style.includes('width:') ? default_style : node.attrs.style +
`;${default_style}`
}
if (node.attrs.align) {
node.attrs.style = "text-align:" + node.attrs.align + ';' + node.attrs.style;
node.attrs.align = undefined;
}
break;
case "img":
if (node.attrs.height) {
node.attrs.height = 'auto'
}
if (node.attrs.style) {
node.attrs.style = node.attrs.style.includes('height:') ? default_style : node.attrs.style +
`;${default_style}`
}
if (node.attrs["data-src"]) {
node.attrs.src = node.attrs.src || node.attrs["data-src"];
node.attrs["data-src"] = undefined;
}
// #ifdef MP-BAIDU || MP-TOUTIAO
if (Parser._imgMode == "widthFix") node.attrs.style = node.attrs.style + ";height:auto !important;";
// #endif
if (node.attrs.src) {
if (!node.attrs.ignore) {
if (bubbling(Parser)) node.attrs.i = (Parser._imgNum++).toString();
else node.attrs.ignore = "true";
}
if (Parser._domain && node.attrs.src[0] == '/') {
if (node.attrs.src[1] == '/') node.attrs.src = Parser._protocol + ":" + node.attrs.src;
else node.attrs.src = Parser._domain + node.attrs.src;
}
}
break;
case 'a':
case "ad":
bubbling(Parser);
break;
case "font":
if (node.attrs.color) {
node.attrs.style = "color:" + node.attrs.color + ';' + node.attrs.style;
node.attrs.color = undefined;
}
if (node.attrs.face) {
node.attrs.style = "font-family:" + node.attrs.face + ';' + node.attrs.style;
node.attrs.face = undefined;
}
if (node.attrs.size) {
var size = parseInt(node.attrs.size);
if (size < 1) size = 1;
else if (size > 7) size = 7;
var map = ["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large"];
node.attrs.style = "font-size:" + map[size - 1] + ';' + node.attrs.style;
node.attrs.size = undefined;
}
break;
case 'iframe':
case "video":
case "audio":
node.attrs.loop = node.attrs.hasOwnProperty('loop') || false;
node.attrs.controls = node.attrs.hasOwnProperty(
'controls') || true;
node.attrs.autoplay = node.attrs.hasOwnProperty('autoplay') || false;
if (node.attrs.id) Parser['_' + node.name + "Num"]++;
else node.attrs.id = (node.name + (++Parser['_' + node.name + "Num"]));
if (node.name == "video") {
node.attrs.style = node.attrs.style || '';
if (node.attrs.width) {
node.attrs.style = "width:" + parseFloat(node.attrs.width) + (node.attrs.height.includes(
'%') ? '%' : "px") +
';' + node.attrs.style;
node.attrs.width = undefined;
}
if (node.attrs.height) {
node.attrs.style = "height:" + parseFloat(node.attrs.height) + (node.attrs.height.includes(
'%') ? '%' : "px") +
';' + node.attrs.style;
node.attrs.height = undefined;
}
if (Parser._videoNum > 3) node.lazyLoad = true;
}
// 新增iframe【create_by_xx】
if (node.name == 'iframe') {
// console.log(node.attrs, "====iframe attrs");
}
node.attrs.source = [];
if (node.attrs.src) node.attrs.source.push(node.attrs.src);
if (!node.attrs.controls && !node.attrs.autoplay)
console.warn("存在没有controls属性的 " + node.name + " 标签,可能导致无法播放", node);
bubbling(Parser);
break;
case "source":
var parent = Parser._STACK[Parser._STACK.length - 1];
if (parent && (parent.name == "video" || parent.name == "audio")) {
parent.attrs.source.push(node.attrs.src);
if (!parent.attrs.src) parent.attrs.src = node.attrs.src;
}
break;
}
if (Parser._domain && node.attrs.style.includes("url"))
node.attrs.style = node.attrs.style.replace(/url\s*\(['"\s]*(\S*?)['"\s]*\)/, function() {
var src = arguments[1];
if (src && src[0] == '/') {
if (src[1] == '/') return "url(" + Parser._protocol + ':' + src + ')';
else return "url(" + Parser._domain + src + ')';
} else return arguments[0];
})
if (!node.attrs.style) node.attrs.style = undefined;
if (Parser._useAnchor && node.attrs.id) bubbling(Parser);
},
trustAttrs,
trustTags,
blockTags,
ignoreTags,
selfClosingTags,
userAgentStyles,
// #ifndef MP-ALIPAY || H5
versionHigherThan,
// #endif
makeMap
}

View File

@ -0,0 +1,407 @@
<!--
trees 递归显示组件
github地址https://github.com/jin-yufeng/Parser
文档地址https://jin-yufeng.github.io/Parser
插件市场https://ext.dcloud.net.cn/plugin?id=805
authorJinYufeng
-->
<template>
<view style="display: inherit; white-space: inherit;word-break:normal">
<block v-for="(item, index) in nodes" v-bind:key="index">
<!-- {{item.name}} {{item}} -->
<!--#ifdef MP-WEIXIN || MP-QQ || MP-ALIPAY || APP-PLUS-->
<block v-if="handler.notContinue(item)">
<!--#endif-->
<!--#ifdef MP-BAIDU || MP-TOUTIAO-->
<block v-if="!item.c">
<!--#endif-->
<!--图片-->
<!--#ifdef MP-WEIXIN || MP-QQ || MP-ALIPAY || APP-PLUS-->
<rich-text v-if="item.name=='img'" :id="item.attrs.id" class="img"
:style="'text-indent:0;'+handler.getStyle(item.attrs.style, 'inline-block')"
:nodes='handler.setImgStyle(item, imgMode, imgLoad)' :data-attrs="item.attrs"
@tap='previewEvent' />
<!--#endif-->
<!--#ifdef MP-BAIDU || MP-TOUTIAO-->
<rich-text v-if="item.name=='img'" :id="item.attrs.id"
:style="'text-indent:0;'+item.attrs.containStyle" :nodes='[item]' :data-attrs="item.attrs"
@tap='previewEvent' />
<!--#endif-->
<!--文本-->
<!--#ifdef MP-WEIXIN || MP-QQ || APP-PLUS-->
<block v-else-if="item.type=='text'">
<text v-if="!item.decode" decode>{{item.text}}</text>
<rich-text v-else style="display:inline-block;word-break:normal" :nodes="[item]"></rich-text>
</block>
<!--#endif-->
<!--#ifdef MP-ALIPAY-->
<text v-else-if="item.type=='text'" decode>{{item.text}}</text>
<!--#endif-->
<text v-else-if="item.name=='br'">\n</text>
<!--视频-->
<block v-else-if="item.name=='video'">
<!--#ifdef APP-PLUS-->
<view v-if="(!loadVideo||item.lazyLoad)&&!controls[item.attrs.id].play" :id="item.attrs.id"
:class="'_video '+(item.attrs.class||'')" :style="item.attrs.style||''" @tap="_loadVideo" />
<!--#endif-->
<!--#ifndef APP-PLUS-->
<view v-if="item.lazyLoad&&!controls[item.attrs.id].play" :id="item.attrs.id"
:class="'_video '+(item.attrs.class||'')" :style="item.attrs.style||''" @tap="_loadVideo" />
<!--#endif-->
<video v-else
:src="controls[item.attrs.id]?item.attrs.source[controls[item.attrs.id].index] : item.attrs.src"
:id="item.attrs.id" :loop="item.attrs.loop" :controls="item.attrs.controls"
:autoplay="item.attrs.autoplay||(controls[item.attrs.id]&&controls[item.attrs.id].play)"
:unit-id="item.attrs['unit-id']" :class="item.attrs.class" :muted="item.attrs.muted"
:style="item.attrs.style||''" :data-source="item.attrs.source" @play="playEvent"
@error="videoError" />
</block>
<!-- iframe create_by_xx-->
<block v-else-if="item.name=='iframe'">
<!-- #ifdef MP-WEIXIN -->
<!-- 腾讯视频 -->
<txv-video :vid="item.attrs.src" :playerid="item.attrs.src" width="100%" height="100%"
:controls="true" :autoplay="false" :isHiddenStop="true"
v-if="item.attrs.lbType=='vid' || item.attrs.lbtype=='vid'">
</txv-video>
<!-- #endif -->
</block>
<!--音频-->
<audio v-else-if="item.name=='audio'"
:src="controls[item.attrs.id]?item.attrs.source[controls[item.attrs.id].index] : item.attrs.src"
:id="item.attrs.id" :loop="item.attrs.loop" :controls="item.attrs.controls"
:poster="item.attrs.poster" :name="item.attrs.name" :author="item.attrs.author"
:class="item.attrs.class" :style="item.attrs.style||''" :data-source="item.attrs.source"
@error="audioError" />
<!--链接-->
<view v-else-if="item.name=='a'" :class="'_a '+(item.attrs.class||'')" :style="item.attrs.style||''"
:data-attrs="item.attrs" hover-class="navigator-hover" :hover-start-time="25"
:hover-stay-time="300" @tap="tapEvent">
<trees :nodes="item.children" :imgMode="imgMode" />
</view>
<!--广告-->
<!--#ifdef MP-WEIXIN || MP-QQ-->
<ad v-else-if="item.name=='ad'" :unit-id="item.attrs['unit-id']" :class="item.attrs.class||''"
:style="item.attrs.style||''" @error="adError"></ad>
<!--#endif-->
<!--#ifdef MP-BAIDU-->
<ad v-else-if="item.name=='ad'" :appid="item.attrs.appid" :apid="item.attrs.apid"
:type="item.attrs.type" :class="item.attrs.class||''" :style="item.attrs.style"
@error="adError"></ad>
<!--#endif-->
<!--富文本-->
<!--#ifdef MP-WEIXIN || MP-QQ || MP-ALIPAY || APP-PLUS-->
<rich-text v-else :id="item.attrs.id" :class="'__'+item.name"
:style="'word-break:normal;'+handler.getStyle(item.attrs.style, 'block')" :nodes="handler.setStyle(item)" />
<!--#endif-->
<!--#ifdef MP-BAIDU || MP-TOUTIAO-->
<rich-text v-else :id="item.attrs.id" :style="item.attrs?item.attrs.containStyle:''"
:nodes="[item]" />
<!--#endif-->
</block>
<!--#ifdef MP-ALIPAY-->
<view v-else :id="item.attrs.id" :class="'_'+item.name+' '+(item.attrs.class||'')"
:style="item.attrs.style||''">
<trees :nodes="item.children" :imgMode="imgMode" />
</view>
<!--#endif-->
<!--#ifndef MP-ALIPAY-->
<trees v-else :class="item.attrs.id+' _'+item.name+' '+(item.attrs.class||'')"
:style="item.attrs.style||''" :nodes="item.children" :imgMode="imgMode" :loadVideo="loadVideo" />
<!--#endif-->
</block>
</view>
</template>
<script module="handler" lang="wxs" src="./handler.wxs"></script>
<script module="handler" lang="sjs" src="./handler.sjs"></script>
<script>
import trees from "./trees"
export default {
components: {
trees
},
name: 'trees',
data() {
return {
controls: {},
// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
imgLoad: true
// #endif
}
},
props: {
nodes: {
type: Array,
default: []
},
// #ifdef APP-PLUS
loadVideo: {
type: Boolean,
default: false
},
// #endif
imgMode: {
type: String,
default: "default"
}
},
mounted() {
//
this._top = this.$parent;
while (this._top.$options.name != 'parser') {
if (this._top._top) {
this._top = this._top._top;
break;
}
this._top = this._top.$parent;
}
},
// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS
beforeDestroy() {
if (this._observer)
this._observer.disconnect();
},
// #endif
methods: {
// #ifndef MP-ALIPAY
playEvent(e) {
if ((this._top.videoContexts || []).length > 1 && this._top.autopause) {
for (var video of this._top.videoContexts) {
if (video.id == e.currentTarget.id) continue;
video.pause();
}
}
},
// #endif
previewEvent(e) {
var attrs = e.currentTarget.dataset.attrs;
if (!attrs.ignore) {
var preview = true;
this._top.$emit('imgtap', {
id: e.currentTarget.id,
src: attrs.src,
ignore: () => preview = false
})
if (preview && this._top.autopreview) {
var urls = this._top.imgList || [],
current = urls[attrs.i] ? attrs.i : (urls = [attrs.src], 0);
uni.previewImage({
current,
urls
})
}
}
},
tapEvent(e) {
var jump = true,
attrs = e.currentTarget.dataset.attrs;
attrs.ignore = () => jump = false;
this._top.$emit('linkpress', attrs);
if (jump) {
// #ifdef MP
if (attrs['app-id'] || attrs.appId) {
return uni.navigateToMiniProgram({
appId: attrs['app-id'] || attrs.appId,
path: attrs.path || ''
})
}
// #endif
if (attrs.href) {
if (attrs.href[0] == "#") {
if (this._top.useAnchor)
this._top.navigateTo({
id: attrs.href.substring(1)
})
} else if (attrs.href.indexOf("http") == 0 || attrs.href.indexOf("//") == 0) {
if (this._top.autocopy) {
uni.setClipboardData({
data: attrs.href,
success() {
uni.showToast({
title: '链接已复制'
});
}
});
}
} else
uni.navigateTo({
url: attrs.href
})
}
}
},
triggerError(source, target, errMsg, errCode, context) {
this._top.$emit('error', {
source,
target,
errMsg,
errCode,
context
});
},
loadSource(target) {
// console.log(target)
var index = (this.controls[target.id] ? this.controls[target.id].index : 0) + 1;
if (index < target.dataset.source.length) {
this.$set(this.controls[target.id], "index", index);
return true;
}
return false;
},
adError(e) {
this.triggerError("ad", e.currentTarget, "", e.detail.errorCode);
},
videoError(e) {
if (!this.loadSource(e.currentTarget) && this._top)
this.triggerError("video", e.currentTarget, e.detail.errMsg, undefined, uni.createVideoContext(e
.currentTarget.id,
this));
},
audioError(e) {
if (!this.loadSource(e.currentTarget))
this.triggerError("audio", e.currentTarget, e.detail.errMsg);
},
_loadVideo(e) {
this.$set(this.controls, e.currentTarget.id, {
play: true,
index: 0
})
}
}
}
</script>
<style>
/* 可以在这里引入自定义的外部样式 */
/* 链接受到点击的hover-class可自定义修改 */
.navigator-hover {
opacity: 0.7;
text-decoration: underline;
}
/* 以下内容不建议修改 */
/* #ifndef MP-BAIDU */
:host {
display: inherit;
float: inherit;
}
/* #endif */
._b,
._strong {
font-weight: bold;
}
._big {
font-size: 1.2em;
}
._small {
font-size: 0.8em;
}
._blockquote,
._div,
._p {
display: block;
}
._code {
font-family: monospace;
}
._del {
text-decoration: line-through;
}
._em,
._i {
font-style: italic;
}
._h1 {
font-size: 2em;
}
._h2 {
font-size: 1.5em;
}
._h3 {
font-size: 1.17em;
}
._h5 {
font-size: 0.67em;
}
._h1,
._h2,
._h3,
._h4,
._h5,
._h6 {
font-weight: bold;
}
._ins {
text-decoration: underline;
}
._q::before {
content: '"';
}
._q::after {
content: '"';
}
._a,
._abbr,
._b,
._big,
._small,
._code,
._del,
._em,
._i,
._ins,
._label,
._q,
._span,
._strong {
display: inline;
}
/* #ifdef MP-WEIXIN || MP-QQ || MP-ALIPAY */
.__sub,
.__sup,
.__bdo,
.__bdi,
.__ruby,
.__rt {
display: inline-block !important;
}
/* #endif */
._video {
background-color: black;
width: 300px;
height: 225px;
display: inline-block;
position: relative;
}
._video::after {
content: '';
border-width: 15px 0 15px 30px;
border-style: solid;
border-color: transparent transparent transparent white;
position: absolute;
left: 50%;
top: 50%;
margin: -15px 0 0 -15px;
}
</style>

131
components/load-more.vue Normal file
View File

@ -0,0 +1,131 @@
<template>
<view class='load-more'>
<block v-if="!loading&&noMore">
<view class="loadmore__line"></view>
<text class="loadmore__text">{{noMoreText}}</text>
<view class="loadmore__line"></view>
</block>
<block v-if="loading">
<view class="weui-loading"></view>
<text class="loadmore__text">{{loadText}}</text>
</block>
</view>
</template>
<script>
export default {
name: 'load-more',
props: {
loadText: {
type: String,
default () {
return '努力加载中'
}
},
noMoreText: {
type: String,
default () {
return '没有更多了'
}
},
noMore: {
type: Boolean,
default () {
return false
}
},
loading: {
type: Boolean,
default () {
return true
}
}
},
created() {
},
data() {
return {
}
},
methods: {
},
}
</script>
<style>
.load-more {
display: flex;
width: 100%;
height: 90rpx;
justify-content: center;
align-items: center;
}
.loading-icon {
width: 30rpx;
height: 30rpx;
margin-right: 8rpx;
display: inline-block;
vertical-align: middle;
animation: weuiLoading 1s steps(12, end) infinite;
background: url('https://s10.mogucdn.com/mlcdn/c45406/171016_4a61e09hcadd157gadhdeje55e82c_32x32.png') no-repeat;
-webkit-background-size: 100%;
background-size: 100%;
}
.loadmore__text {
margin: 0 8rpx 0 8rpx;
color: #999;
font-size: 28rpx;
}
.loadmore__line {
width: 100rpx;
height: 1rpx;
border-top: 1rpx solid #d2d2d2;
}
/* 加载中动画 */
.weui-loading {
margin: 0 5px;
width: 20px;
height: 20px;
display: inline-block;
vertical-align: middle;
-webkit-animation: a 1s steps(12) infinite;
animation: a 1s steps(12) infinite;
background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat;
background-size: 100%
}
.weui-loading.weui-loading_transparent {
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120' viewBox='0 0 100 100'%3E%3Cpath fill='none' d='M0 0h100v100H0z'/%3E%3Crect xmlns='http://www.w3.org/2000/svg' width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.56)' rx='5' ry='5' transform='translate(0 -30)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.5)' rx='5' ry='5' transform='rotate(30 105.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.43)' rx='5' ry='5' transform='rotate(60 75.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.38)' rx='5' ry='5' transform='rotate(90 65 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.32)' rx='5' ry='5' transform='rotate(120 58.66 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.28)' rx='5' ry='5' transform='rotate(150 54.02 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.25)' rx='5' ry='5' transform='rotate(180 50 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.2)' rx='5' ry='5' transform='rotate(-150 45.98 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.17)' rx='5' ry='5' transform='rotate(-120 41.34 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.14)' rx='5' ry='5' transform='rotate(-90 35 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.1)' rx='5' ry='5' transform='rotate(-60 24.02 65)'/%3E%3Crect width='7' height='20' x='46.5' y='40' fill='rgba(255,255,255,.03)' rx='5' ry='5' transform='rotate(-30 -5.98 65)'/%3E%3C/svg%3E")
}
@-webkit-keyframes a {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg)
}
to {
-webkit-transform: rotate(1turn);
transform: rotate(1turn)
}
}
@keyframes a {
0% {
-webkit-transform: rotate(0deg);
transform: rotate(0deg)
}
to {
-webkit-transform: rotate(1turn);
transform: rotate(1turn)
}
}
</style>

112
components/login-info.vue Normal file
View File

@ -0,0 +1,112 @@
<template>
<view>
<view class="login-info pd-lg flex-center rel" :style="{bottom:`${configInfo.tabbarHeight}px`}" v-if="pShow">
<view @tap.stop="toClose" class="login-close flex-center radius abs">
<i class="iconfont icon-add c-base"></i>
</view>
<image mode="aspectFill" lazy-load class="logo-img radius" :src="configInfo.app_logo"></image>
<view class="flex-center flex-1 ml-md">
<view class="flex-1">
<view class="f-title c-base max-380 ellipsis">{{`欢迎来到${configInfo.app_text}`}}</view>
<view class="text f-caption">登录后获取更多精彩内容</view>
</view>
<view @tap.stop="toLogin" class="login-btn flex-center f-desc c-base radius"
:style="{background:primaryColor}">去登录</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
components: {},
props: {},
mounted() {
this.init()
},
data() {
return {
pShow: false
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
isShowLogin: state => state.user.isShowLogin
}),
methods: {
...mapActions(['getConfigInfo', 'getUserInfo']),
...mapMutations(['updateUserItem']),
init() {
let {
isShowLogin
} = this
this.$set(this, 'pShow', isShowLogin)
},
toClose() {
this.updateUserItem({
key: 'isShowLogin',
val: false
})
this.$set(this, 'pShow', false)
},
toLogin() {
let pages = getCurrentPages();
let {
route
} = pages[pages.length - 1]
this.updateUserItem({
key: 'loginPage',
val: `/${route}`
})
this.$util.goUrl({
url: `/pages/login`
})
}
},
}
</script>
<style scoped lang="scss">
.login-info {
position: fixed;
left: 20rpx;
width: 710rpx;
height: 139rpx;
background: rgba(0, 0, 0, 0.8);
border-radius: 16rpx;
.logo-img {
width: 90rpx;
height: 90rpx;
}
.text {
color: #9E9E9E;
}
.login-btn {
width: 150rpx;
height: 54rpx;
}
.login-close {
top: -10rpx;
right: -10rpx;
width: 40rpx;
height: 40rpx;
background: #000;
.iconfont {
font-size: 24rpx;
transform: rotate(45deg);
}
}
}
</style>

121
components/mask.vue Normal file
View File

@ -0,0 +1,121 @@
<template>
<view :class="['ui-mask ','center',effect,{'show':show}]" @tap="handleMaskTap" :style='{top:top,background:background}'>
<slot></slot>
</view>
</template>
<script>
export default {
name: 'mask',
props: {
background: {
type: String,
default: "rgba(0, 0, 0, 0.5)"
},
show: {
type: Boolean
},
top: {
type: String,
default () {
return '0'
}
},
effect: {
type: String
},
hideDelay: {
type: Number
},
hideOnTap: {
type: Number,
default () {
return 1
}
},
blur: {
type: String
}
},
created() {
},
data() {
return {
}
},
watch: {
show(newValue, oldValue) {
var _this = this;
if (newValue) {
this.selfShow = false
} else {
if (this.hideDelay) {
setTimeout(function() {
_this.selfShow = false
}, this.hideDelay);
} else {
_this.selfShow = false
}
}
}
},
methods: {
handleMaskTap(e) {
if (this.hideOnTap) {
this.show = false;
this.$emit('hide')
}
}
},
}
</script>
<style>
.ui-mask {
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
width: 100%;
visibility: hidden;
opacity: 0;
transition: all 0.25s ease-in;
-webkit-backface-visibility: hidden;
z-index: 0;
}
.center {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.ui-mask.scale-out {
transition: all 0.25s ease-in;
transform: scale(0);
}
.ui-mask.scale-out.show {
transform: scale(1);
}
.ui-mask.scale-in {
transition: all 0.25s ease-in;
transform: scale(1.5);
}
.ui-mask.scale-in.show {
transform: scale(1);
}
.ui-mask.show {
opacity: 1;
visibility: visible;
}
</style>

View File

@ -0,0 +1,176 @@
<template>
<view>
<view class="min-countdown" :class="countdownClass">
<rich-text :nodes="time" v-if="type == 1"></rich-text>
<view class="flex-y-center" :style="{color:primaryColor}" v-if="type == 2">
<view class="count-tag flex-center">{{time.d}}</view><view class="count-tag flex-center">{{time.h}}
</view>
<view class="count-tag flex-center">{{time.m}}</view><view class="count-tag flex-center">{{time.s}}
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from 'vuex';
export default {
name: 'min-countdown',
props: {
type: {
type: Number,
default: 1
},
targetTime: {
type: Number,
default: 0
},
format: {
type: String,
default: '{%d}天{%h}小时{%m}分{%s}秒'
},
countdownClass: {
type: String,
default: ''
},
bgColor: {
type: String,
default: ''
},
isPlay: {
type: Boolean,
default: false
}
},
data() {
return {
time: '00:00:00',
audioBg: {},
playBg: false,
}
},
async mounted() {
this.getTime()
if (this.isPlay && !this.configInfo.id) {
await this.getConfigInfo()
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
isHaveAudio: state => state.user.isHaveAudio,
}),
methods: {
...mapActions(['getConfigInfo', 'toPlayAudio']),
...mapMutations(['updateUserItem']),
initIndex() {
setTimeout(() => {
this.getTime()
}, 1000)
},
getTime() {
let time = {}
let format = this.format
function formatNumber(num) {
return num > 9 ? `${num}` : `0${num}`
}
const gapTime = Math.ceil((this.targetTime - new Date().getTime()) / 1000)
if (gapTime >= 0) {
time.d = formatNumber(parseInt(gapTime / 86400))
let lastTime = gapTime % 86400;
time.h = formatNumber(parseInt(lastTime / 3600))
lastTime = lastTime % 3600;
time.m = formatNumber(parseInt(lastTime / 60))
time.s = formatNumber(lastTime % 60);
['d', 'h', 'm', 's'].forEach(item => {
const day = time[item].split('');
format = format.replace('{%' + item + '}', time[item])
format = format.replace('{%' + item + '0}', day[0])
format = format.replace('{%' + item + '1}', day[1])
format = format.replace('{%' + item + '2}', day[2] ? day[2] : '0')
})
this.time = this.type == 1 ? format : time
this.initIndex()
if (this.isPlay && gapTime == 60 * 5) {
this.toPlayAudio({
key: 'countdown_voice'
})
}
if (this.isPlay && gapTime == 0) {
this.toPlayAudio({
key: 'service_end_recording'
})
this.$emit('end')
}
} else {
this.$emit('callback')
}
},
async toInitPlay() {
if (!this.configInfo.id) {
await this.getConfigInfo()
}
console.log("======toInitPlay")
let {
countdown_voice
} = this.configInfo
this.audioBg = uni.createInnerAudioContext();
this.audioBg.src = countdown_voice
this.audioBg.obeyMuteSwitch = false
this.updateUserItem({
key: 'isHaveAudio',
val: true
})
console.log(this.audioBg, "=======this.audioBg")
this.audioBg.onPlay(() => {
console.log('onPlay')
this.playBg = true
})
this.audioBg.onStop(() => {
console.log('onStop')
this.playBg = false
})
this.audioBg.onError(() => {
console.log('onError')
this.playBg = false
this.audioBg.destroy()
})
this.audioBg.onEnded(() => {
console.log('onEnded')
this.playBg = false
})
},
toPlay() {
console.log("=====toPlay", this.playBg, this.audioBg)
if (this.playBg) {
this.audioBg.stop()
}
this.audioBg.play()
}
}
}
</script>
<style scoped>
.min-countdown {
display: inline-flex;
justify-content: center;
align-items: center;
}
.count-tag {
width: 56rpx;
height: 56rpx;
margin: 0 10rpx;
background: linear-gradient(0deg, #F7D9AD 0%, #FBEACB 100%);
border-radius: 8rpx;
}
</style>

207
components/search.vue Normal file
View File

@ -0,0 +1,207 @@
<template>
<view class='search-box'
:style='{padding:`${padding}rpx`,margin:`${margin}rpx`,borderRadius:`${radius}rpx`,background:backgroundColor}'>
<view class='search-item' :class="[{'flex-y-center': type == 'text'},{'flex-between': type == 'input'}]"
:style='{borderRadius:searchStyleObj[searchStyle],background:frontColor,justifyContent:textAlign,height:height}'>
<block v-if="type=='text'">
<image src='https://lbqny.migugu.com/admin/public/search.png' class="search-icon"></image>
<view class='ml-md placeholder'>{{placeholder}}</view>
</block>
<block v-if="type=='input'">
<view class="flex-y-center flex-1">
<image src='https://lbqny.migugu.com/admin/public/search.png' class="search-icon"></image>
<input type='text' class="flex-1 ml-md mr-md f-paragraph c-title" :disabled="disabled"
:placeholder='placeholder' placeholder-class='f-paragraph c-caption' confirm-type="search"
@input="handerInput" :value="keyword" @confirm="confirm" :auto-focus="autofocus"></input>
</view>
<view class='search-item-btn flex-center radius' :style="{background:primaryColor}" @tap='confirm'
v-if="confirmSearch">搜索</view>
</block>
</view>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from "vuex"
export default {
name: 'search',
props: {
type: {
type: String,
default () {
return 'text'
}
},
placeholder: {
type: String,
default () {
return "输入关键字进行搜索"
}
},
searchStyle: {
type: String,
default () {
return 'circle'
}
},
textAlign: {
type: String,
default () {
return 'center'
}
},
padding: {
type: Number,
default () {
return 30
}
},
margin: {
type: Number,
default () {
return 0
}
},
radius: {
type: Number,
default () {
return 0
}
},
height: {
type: String,
default () {
return '64rpx'
}
},
backgroundColor: {
type: String,
default () {
return '#fff'
}
},
frontColor: {
type: String,
default () {
return '#f4f6f8'
}
},
autofocus: {
type: Boolean,
default () {
return false
}
},
disabled: {
type: Boolean,
default () {
return false
}
},
focus: {
type: Boolean,
default () {
return false
}
},
keyword: {
type: String,
default () {
return ''
}
},
confirmSearch: {
type: Boolean,
default () {
return false
}
}
},
created() {
},
data() {
return {
searchStyleObj: {
square: '0rpx',
radius: '10rpx',
circle: '30rpx'
},
text: ''
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
}),
methods: {
confirm(e) {
let val = this.text;
this.$emit("confirm", val)
},
handerInput(e) {
let val = e.detail.value;
this.text = val;
this.$emit("input", val)
}
},
}
</script>
<style>
.search-box {
padding: 16rpx 16rpx;
display: flex;
align-items: center;
background: #efeff5;
}
.search-item {
width: 100%;
background: #ffffff;
border-radius: 30rpx;
padding: 0 0 0 25rpx;
line-height: 1;
font-size: 28rpx;
color: #888;
}
.placeholder {
font-size: 28rpx;
color: #999;
}
.search-item-btn {
width: 110rpx;
height: 64rpx;
font-size: 26rpx;
font-weight: 400;
color: #ffffff;
}
.search-btn {
padding: 0 20rpx 0 40rpx;
}
.search-icon {
width: 30rpx;
height: 30rpx;
}
.flex-1 {
flex: 1;
}
.icon-md {
width: 40rpx;
height: 40rpx;
}
.ml-md {
margin-left: 16rpx;
}
</style>

View File

@ -0,0 +1,180 @@
<template>
<view class="service-list-item">
<view @tap.stop="goDetail" class="list-item flex-warp">
<!-- #ifdef H5 -->
<view class="cover radius-16">
<view class="h5-image cover radius-16" :style="{ backgroundImage : `url('${info.cover}')`}">
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image mode="aspectFill" lazy-load class="cover radius-16" :src="info.cover"></image>
<!-- #endif -->
<view class="flex-1 ml-md" :style="{maxWidth:maxWidth}">
<view class="flex-between">
<view class="f-title c-title text-bold max-270 ellipsis">{{ info.title }}</view>
<view class="f-caption c-caption">{{ info.total_sale }}人选择</view>
</view>
<view class="f-caption c-caption mt-sm mb-sm ellipsis" style="height: 36rpx;">{{ info.sub_title || '' }}
</view>
<view class="f-caption c-caption" v-if="info.material_price*1>0">物料费¥{{info.material_price}}</view>
<view class="flex-between">
<view class="flex-y-center f-desc c-caption max-350 ellipsis">
<view class="flex-y-baseline f-icontext c-orange text-bold mr-sm">
<b class="f-caption c-orange"></b>
<view class="f-md-title">
{{ info.price }}
</view>
</view>
<view class="text-delete" v-if="info.init_price">{{ info.init_price }}</view>
<view class="servefc ml-md">
<i class="iconfont icon-shijian" :style="{ color: primaryColor }"></i>
<span class="f-caption c-title ml-sm">{{ info.time_long }}分钟</span>
</view>
</view>
<auth @tap.stop.prevent :needAuth="userInfo && (!userInfo.phone || !userInfo.nickName)" :must="true"
:type="!userInfo.phone ? 'phone' : 'userInfo'" @go="toChoose" style="width:190rpx">
<view class="flex-between">
<view></view>
<view class="item-btn flex-center f-caption c-base" :style="{background: `linear-gradient(68deg, ${primaryColor}, ${subColor})`}">
{{ from == 'technician-info' ? `立即预约` : `选择${$t('action.attendantName')}`}}
</view>
</view>
</auth>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from "vuex"
export default {
components: {},
props: {
from: {
type: String,
default () {
return 'list'
}
},
sid: {
type: Number,
default () {
return 0
}
},
info: {
type: Object,
default () {
return {}
}
},
maxWidth: {
type: String,
default () {
return '490rpx'
}
}
},
data() {
return {
textType: {
1: '可服务',
2: '服务中',
3: '可预约',
4: '不可预约'
},
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
userInfo: state => state.user.userInfo,
}),
methods: {
//
goDetail() {
let {
id
} = this.info
let {
sid: store_id = 0
} = this
let url = `/user/pages/detail?id=${id}&store_id=${store_id}`
this.$util.goUrl({
url
})
},
// -
toChoose() {
let {
from
} = this
if (from == 'technician-info') {
this.$emit('order')
return
}
let {
id
} = this.info
let {
sid: store_id = 0
} = this
let url = `/user/pages/choose-technician?id=${id}&store_id=${store_id}`
this.$util.goUrl({
url
})
},
toEmit(key) {
this.$emit(key)
}
}
}
</script>
<style scoped lang="scss">
.service-list-item {
.list-item {
.cover {
width: 180rpx;
height: 180rpx;
}
.time-long {
min-width: 72rpx;
height: 30rpx;
padding: 0 5rpx;
background: linear-gradient(270deg, #4C545A 0%, #282B34 100%);
border-radius: 4rpx;
font-size: 20rpx;
color: #FFEEB9;
margin-right: 16rpx;
}
.f-icontext {
font-size: 18rpx;
}
.text-delete {
font-size: 24rpx;
color: #B9B9B9;
}
.item-btn {
min-width: 150rpx;
height: 52rpx;
padding: 0 10rpx;
border-radius: 100rpx;
}
}
}
.servefc{
display: flex;
align-items: center;
}
</style>

233
components/shop-banner.vue Normal file
View File

@ -0,0 +1,233 @@
<template>
<view class="rel">
<view class="abs" style="z-index: 99;" :style="{top:statusBarHeight + 'px'}">
<!-- #ifdef MP-WEIXIN -->
<view @tap.stop="goBack" class="flex-center circle home-return-btn" v-if="!isShare"
:class="[{'back-user-ios': configInfo.isIos},{'back-user-android': !configInfo.isIos}]">
<view class="iconfont icon-left c-base text-bold" style="font-size: 40rpx;"></view>
</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view @tap="$util.goUrl({url:`/pages/service`,openType:`reLaunch`})"
:class="[{'back-user-ios': configInfo.isIos},{'back-user-android': !configInfo.isIos}]" v-if="isShare">
<view class="iconshouye iconfont"></view>
<view class="back-user_text">回到首页</view>
</view>
<!-- #endif -->
</view>
<view class='banner'>
<swiper class='banner-swiper' :style="{background: playVideo && !detail.video_vid ? '#000':'#f4f6f8'}"
@change='handerSwiperChange' @transition="swiperTransition" :autoplay="playVideo ? false : true"
:current="current">
<swiper-item v-for="(item,index) in detail.images" :key="index" @tap='handerBannerClick(index)'>
<block v-if="index == 0 && detail.video_url">
<block v-if="!playVideo">
<view @tap.stop="playCurrent"
class="banner-swiper c-base iconfont icontushucxuanzebofangtiaozhuan abs flex-center"
style="top: 0rpx;font-size: 80rpx;z-index: 9;"></view>
<image mode="aspectFill" class='banner-img' :src='item'></image>
</block>
<!-- #ifdef MP-WEIXIN -->
<txv-video :vid="detail.video_vid" :playerid="detail.video_vid" width="100%" height="100%"
:controls="true" :autoplay="true" :isHiddenStop="true" v-if="playVideo && detail.video_vid">
</txv-video>
<!-- #endif -->
<view class="video-box" v-if="playVideo && !detail.video_vid">
<video :id="`video_id`" class="my-video" :loop="false" enable-play-gesture
:enable-progress-gesture="false" :src="detail.video_url" :autoplay="playVideo"
@play="onPlay" @pause="onPause" @ended="onEnded" @timeupdate="onTimeUpdate"
@waiting="onWaiting" @progress="onProgress" @loadedmetadata="onLoadedMetaData"></video>
</view>
</block>
<image mode="aspectFill" class='banner-img' :src='item' v-else></image>
</swiper-item>
</swiper>
<view class='banner-tagitem banner-tagitem_count' v-if="!playVideo && detail.images.length">
{{current+1}}/{{detail.images.length}}
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex"
export default {
props: {
detail: {
type: Object,
default () {
return {}
}
},
isShare: {
type: Boolean,
default () {
return false
}
},
setCurrent: {
type: Number,
default () {
return 0
}
}
},
watch: {
'detail.images'(newValue, oldValue) {
this.current = 0
}
},
data() {
return {
statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
videoContexts: {},
playVideo: false,
current: 0
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
created() {
this.videoContexts = uni.createVideoContext(`video_id`, this)
},
methods: {
handerSwiperChange(e) {
let {
current,
} = e.detail
this.current = current;
this.videoContexts.pause()
this.playVideo = false;
},
swiperTransition(e) {
// // #ifdef H5
// if(this.playVideo && this.current == 0 && e.detail.dx < 30){
// this.current = 1
// this.videoContexts.pause()
// this.playVideo = false;
// console.log(e,"======swiperTransition")
// }
// // #endif
},
playCurrent() {
this.videoContexts.play()
this.playVideo = true
},
onPlay(e) {},
onPause(e) {},
onEnded(e) {},
onError(e) {},
onTimeUpdate(e) {},
onWaiting(e) {},
onProgress(e) {},
onLoadedMetaData(e) {},
handerBannerClick(index) {
let {
image_url: url,
video_url = ''
} = this.detail
if (index == 0 && video_url) {
this.playVideo = true;
return
}
if (!url) return
this.$util.goUrl({
openType: 'web', //this.configInfo.methodObj[jump_type],
url
})
},
goBack() {
uni.navigateBack({
delta: 1
})
},
}
}
</script>
<style>
.home-return-btn {
margin-top: 10rpx;
margin-left: 24rpx;
width: 60rpx;
height: 60rpx;
border: none;
background-color: rgba(0, 0, 0, 0.3);
}
.video-box {
position: relative;
width: 100%;
height: 500rpx;
}
.my-video {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 80%;
align-items: center;
margin-top: 120rpx;
}
.banner {
position: relative;
}
.banner-swiper {
width: 750rpx;
height: 564rpx;
}
.banner-img {
width: 100%;
height: 100%;
}
.banner-taglist {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
bottom: 32rpx;
width: 100%;
}
.banner-tagitem {
display: flex;
align-items: center;
justify-content: center;
width: 90rpx;
height: 42rpx;
border-radius: 21rpx;
background: rgba(255, 255, 255, 0.8);
color: #2b2b2b;
font-size: 22rpx;
margin-left: 32rpx;
}
.banner-tagitem:nth-child(1) {
margin-left: 0;
}
.banner-tagitem_count {
background: rgba(0, 0, 0, 0.5);
color: #fff;
position: absolute;
bottom: 32rpx;
right: 32rpx;
z-index: 10;
}
.banner-tagitem_active {
background: #19c865;
color: #fff;
}
</style>

210
components/tab.vue Normal file
View File

@ -0,0 +1,210 @@
<template>
<view>
<scroll-view scroll-x class='tab-list' :scroll-into-view="'tab'+(activeIndex-1)" :scroll-with-animation="true"
:style="{background: bgColor,fontSize: fontSize}">
<view class='tab-item rel' v-for="(item,index) in list" :key="index" @tap='handerTabChange(index)'
data-index="index" :id="'tab'+index"
:style='{width,height,lineHeight:height,color:index==activeIndex?activeColor:color}'>
<view class="flex-y-baseline flex-x-center rel">
<view class="rel" v-if="item.number">
{{item.title || item}}
<view v-if="numberType == 1" class="item-msg c-base abs"
:style="{width: item.number<10 ? '30rpx' :item.number<100 ? '40rpx' : '50rpx',right:item.number<10 ? '-34rpx' :item.number<100 ? '-44rpx' : '-54rpx' }">
{{item.number < 100 ? item.number : '99+'}}
</view>
<view class="tab-label c-base abs"
:style="{width: item.number<10 ? '24rpx' :item.number<100 ? '36rpx': '48rpx',right:item.number<10 ? '-14rpx' :item.number<100 ? '-26rpx' : '-38rpx'}"
v-if="numberType == 2">
{{item.number < 100 ? item.number : '99+'}}
</view>
</view>
<block v-else>{{item.title || item}}</block>
<block v-if="item.is_sign == 1">
<view class="tab-item-sanjao abs" :style="{right: item.title.length == 3 ? '-20rpx' : ''}">
<view class="up iconfont icon-up-fill rel"
:style="{color:index==activeIndex && item.sign == 1?activeColor:'#ccc',}"></view>
<view class="down iconfont icon-down-fill rel"
:style="{color:index==activeIndex && item.sign == 0?activeColor:'#ccc',}"></view>
</view>
</block>
</view>
<view class="abs line" :class="[lineClass]" :style="{background: activeColor}"
v-if="isLine && index==activeIndex && !item.is_sign">
</view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
name: 'tab',
props: {
list: {
type: Array,
default () {
return []
}
},
activeIndex: {
type: Number,
default () {
return 0
}
},
color: {
type: String,
default () {
return '#333'
}
},
activeColor: {
type: String,
default () {
return '#e73535'
}
},
bgColor: {
type: String,
default () {
return '#fff'
}
},
width: {
type: String,
default () {
return ''
}
},
height: {
type: String,
default () {
return '100rpx'
}
},
isLine: {
type: Boolean,
default () {
return true
}
},
lineClass: {
type: String,
default () {
return ''
}
},
msgRight: {
type: String,
default () {
return '5rpx'
}
},
fontSize: {
type: String,
default () {
return '30rpx'
}
},
numberType: {
type: Number,
default () {
return 1
}
}
},
created() {
},
data() {
return {
}
},
methods: {
handerTabChange(index) {
this.$emit('change', index);
}
},
}
</script>
<style lang="scss">
.tab-list {
white-space: nowrap;
background: #fff;
width: 100%;
.tab-item {
display: inline-block;
text-align: center;
padding: 0 20rpx;
border-color: #fff;
box-sizing: border-box;
.tab-label {
width: 24rpx;
height: 24rpx;
font-size: 18rpx;
text-align: center;
line-height: 24rpx;
border-radius: 24rpx;
background: #F1381F;
top: 12rpx;
right: -14rpx;
}
.item-msg {
width: 30rpx;
height: 30rpx;
font-size: 20rpx;
text-align: center;
line-height: 30rpx;
border-radius: 15rpx 15rpx 15rpx 0;
background: #f12c20;
top: 10rpx;
right: -34rpx;
}
.tab-item-sanjao {
top: 18rpx;
right: -6rpx;
width: 30rpx;
height: 50rpx;
transform: scale(0.6);
.iconfont {
font-size: 28rpx;
}
.up {
top: 0;
left: 0;
}
.down {
bottom: 10rpx;
left: 0;
}
}
.line {
width: 80rpx;
height: 6rpx;
border-radius: 6rpx;
left: 50%;
bottom: 0rpx;
margin-left: -40rpx;
}
.line.sm {
width: 60rpx;
height: 6rpx;
border-radius: 6rpx;
left: 50%;
bottom: 0rpx;
margin-left: -30rpx;
}
}
}
</style>

117
components/tabbar.vue Normal file
View File

@ -0,0 +1,117 @@
<template>
<view class="custom-tabbar fix flex-center fill-base b-1px-t">
<view @tap.stop="changeTab(item)" class="flex-center flex-column mt-sm"
:style="{width: (100/configInfo.tabBar.length) + '%',color:cur == item.id ? primaryColor : '#666'}"
v-for="(item,index) in configInfo.tabBar" :key="index">
<i class="iconfont" :class="cur == item.id ? item.selected_img : item.default_img"></i>
<image class="" :src="cur == item.id ? item.selected_img : item.default_img" style="width:45rpx;height:45rpx;"></image>
<view class="text">{{item.name}}</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
components: {},
props: {
cur: {
type: Number,
default () {
return 0
}
},
},
data() {
return {}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
commonOptions: state => state.user.commonOptions,
userPageType: state => state.user.userPageType,
activeIndex: state => state.order.activeIndex,
}),
async mounted() {
let that = this;
let sysheight = uni.getSystemInfoSync().windowHeight
let {
navBarHeight
} = that.configInfo
setTimeout(() => {
const query = uni.createSelectorQuery().in(that);
query.select('.custom-tabbar').boundingClientRect(data => {
let curSysHeight = sysheight - data.height - navBarHeight
let configInfo = that.$util.deepCopy(this.configInfo)
configInfo.curSysHeight = curSysHeight
configInfo.tabbarHeight = data.height
that.updateConfigItem({
key: 'configInfo',
val: configInfo
})
}).exec();
}, 600)
},
methods: {
...mapActions(['getConfigInfo']),
...mapMutations(['updateConfigItem']),
//
async changeTab(item) {
if (!item.id) {
await this.getConfigInfo()
let arr = this.configInfo.tabBar.filter(aitem => {
return aitem.name == item.name
})
item = arr[0]
}
let {
id: index
} = item
let {
activeIndex,
cur,
userPageType
} = this
let page = {
1: `/pages/service`,
2: `/pages/technician`,
3: `/pages/dynamic`,
4: `/pages/order?tab=${activeIndex}`,
5: `/pages/mine?type=${userPageType}`,
6: `/pages/shopstore`,
7: `/pages/map`
}
if (index == cur) return
this.$util.goUrl({
url: page[index],
openType: `reLaunch`
})
},
},
}
</script>
<style scoped lang="scss">
.custom-tabbar {
height: 98rpx;
bottom: 0;
height: calc(98rpx + env(safe-area-inset-bottom) / 2);
padding-bottom: calc(env(safe-area-inset-bottom) / 2);
.iconfont {
font-size: 40rpx;
}
.text {
font-size: 22rpx;
margin-top: 5rpx;
height: 32rpx;
}
}
</style>

View File

@ -0,0 +1,306 @@
<template>
<view class="technician-list-item">
<view class="list-item flex-center pd-lg fill-base radius-16 rel">
<image mode="aspectFill" class="king-img abs" src="https://lbqny.migugu.com/admin/anmo/mine/king.gif"
v-if="info.coach_type_status==1">
</image>
<view class="flex-center flex-column">
<view class="item-img rel">
<!-- #ifdef H5 -->
<view class="item-img radius">
<view @tap.stop="toPreviewImage('work_img')" class="h5-image item-img radius"
:style="{ backgroundImage : `url('${info.work_img}')`}">
</view>
</view>
<view @tap.stop="info.coach_type_status==1?toPreviewImage('work_img'):''" class="h5-image abs"
:class="[`${imgType[info.coach_type_status]}-img`]"
:style="{ backgroundImage : info.coach_type_status === 3? `url('https://lbqny.migugu.com/admin/anmo/mine/${imgType[info.coach_type_status]}.png')` : `url('https://lbqny.migugu.com/admin/anmo/mine/${imgType[info.coach_type_status]}.gif')`}"
v-if="info.coach_type_status">
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image @tap.stop="toPreviewImage('work_img')" mode="aspectFill" class="item-img radius"
:src="info.work_img">
</image>
<image @tap.stop="info.coach_type_status==1?toPreviewImage('work_img'):''" class="abs"
:class="[`${imgType[info.coach_type_status]}-img`]"
:src=" info.coach_type_status === 3 ? `https://lbqny.migugu.com/admin/anmo/mine/${imgType[info.coach_type_status]}.png` : `https://lbqny.migugu.com/admin/anmo/mine/${imgType[info.coach_type_status]}.gif`"
v-if="info.coach_type_status">
</image>
<!-- #endif -->
</view>
<view class="item-tag flex-center f-icontext c-base radius-20"
:style="{background: info.text_type === 1 ? service_btn_color : info.text_type == 3?primaryColor: info.text_type==4?'#e1493b':'',color:info.text_type === 1 ? service_font_color :[3,4].includes(info.text_type) ? '#fff' : ''}">
{{textType[info.text_type]}}
</view>
</view>
<view class="flex-1 ml-md max-510">
<view class="flex-between">
<view @tap.stop="goInfo">
<view class="flex-y-center f-title c-title">
<view class="text-bold max-200 ellipsis">{{info.coach_name}}</view>
<view @tap.stop="toPreviewImage('self_img')"
class="more-img flex-center ml-sm f-icontext ml-lg"
:style="{color:primaryColor,border:`1rpx solid ${primaryColor}`}">生活照</view>
</view>
<view class="flex-y-center f-icontext mt-sm">
<view class="flex-y-center"><i class="iconfont iconyduixingxingshixin icon-font-color"></i>
<view class="star-text">{{info.star}}</view>
</view>
<view class="order-num">
已服务 {{info.order_num > 9999 ? '9999+' : info.order_num}}</view>
</view>
<view class="flex-y-center f-icontext mt-sm mb-sm">
<view class="flex-center">
<i class="iconfont iconjuli" :style="{color:primaryColor}"></i>
<view class="f-desc c-title ml-sm">{{info.distance}}</view>
</view>
</view>
</view>
<view class="can-service-btn f-caption c-base radius-10"
:style="{border:`2rpx solid ${primaryColor}`}" v-if="info.near_time">
<view class="text"
:style="{background: `linear-gradient(68deg, ${primaryColor}, ${subColor})`}">最早可约</view>
<view class="time" :style="{color:primaryColor}">{{'时间'+info.near_time+''}}</view>
</view>
</view>
<view class="flex-between">
<view class="flex-y-center f-desc c-caption">
<view @tap.stop="goInfo" class="flex-y-center">
<block v-if="from!='collect' && plugAuth.store && info.store && info.store.id">
<i class="iconfont iconshangjia_1 c-caption mr-sm"></i>
商家
</block>
<block v-else-if="from!='collect' && info.admin_id && info.admin_name && merchantAuth">
<i class="iconfont iconshangjia_1 c-caption mr-sm"></i>
{{info.admin_name}}
</block>
<block v-else>
<i class="iconfont iconxiangqing c-caption mr-sm"></i>
详情
</block>
</view>
<view @tap.stop="toEmit('comment')" class="flex-y-center ml-lg"><i
class="iconfont iconpinglun mr-sm"></i>{{info.comment_num > 9999 ? '9999+':info.comment_num}}
</view>
<view @tap.stop="toEmit('collect')" class="flex-y-center ml-lg"><i class="iconfont mr-sm"
:class="[{'iconshoucang1':!info.is_collect},{'iconshoucang2':info.is_collect}]"
:style="{color:info.is_collect ? primaryColor :''}"></i>{{info.collect_num > 9999 ? '9999+':info.collect_num}}
</view>
</view>
<auth @tap.stop.prevent :needAuth="userInfo && (!userInfo.phone || !userInfo.nickName)" :must="true"
:type="!userInfo.phone ? 'phone' : 'userInfo'" @go="toEmit('order')" style="width:130rpx;">
<view class="item-btn flex-center f-desc c-base"
:style="{background: `linear-gradient(68deg, ${primaryColor}, ${subColor})`}">
预约TA
</view>
</auth>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapMutations
} from "vuex"
export default {
components: {},
props: {
from: {
type: String,
default () {
return 'list'
}
},
info: {
type: Object,
default () {
return {}
}
}
},
data() {
return {
imgType: {
1: 'top',
2: 'hot',
3: 'new'
},
textType: {
1: '可服务',
2: '服务中',
3: '可预约',
4: '不可预约'
}
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
plugAuth: state => state.config.configInfo.plugAuth,
merchantAuth: state => state.config.merchantAuth,
service_btn_color: state => state.config.configInfo.service_btn_color,
service_font_color: state => state.config.configInfo.service_font_color,
userInfo: state => state.user.userInfo,
}),
methods: {
toPreviewImage(key) {
let urls = this.info[key]
if (key == 'work_img') {
urls = [urls]
}
this.$util.previewImage({
current: urls[0],
urls
})
},
// -
goInfo() {
let {
id,
store = {},
admin_id = 0,
admin_name
} = this.info
let {
from,
plugAuth = {},
merchantAuth = 0
} = this
this.$util.goUrl({
url: from != 'collect' && plugAuth.store && store && store.id ?
`/shopstore/pages/detail?id=${store.id}` : from != 'collect' && merchantAuth && admin_id &&
admin_name ? `/user/pages/merchant-info?id=${admin_id}` :
`/user/pages/technician-info?id=${id}`
})
},
toEmit(key) {
this.$emit(key)
}
}
}
</script>
<style scoped lang="scss">
.technician-list-item {
.list-item {
.top-tag {
width: 40rpx;
height: 30rpx;
color: #B75E1D;
background: linear-gradient(90deg, #DFB885 0%, #FCE0AD 100%);
border-radius: 8rpx 0 8rpx 0;
top: 0;
left: 0;
}
.item-img {
width: 124rpx;
height: 124rpx;
}
.king-img {
width: 90rpx;
height: 90rpx;
top: 110rpx;
left: -15rpx;
z-index: 1;
}
.top-img {
width: 146rpx;
height: 140rpx;
top: -7rpx;
left: -11rpx;
}
.hot-img {
width: 75rpx;
height: 51rpx;
top: 77rpx;
left: 25rpx;
}
.new-img {
width: 38rpx;
height: 52rpx;
top: 83rpx;
left: 78rpx;
}
.item-tag {
width: 100rpx;
height: 32rpx;
color: #000;
background: rgba(216, 216, 216, 0.3);
margin-top: 19rpx;
margin-bottom: 6rpx;
}
.more-img {
width: 94rpx;
height: 33rpx;
border-radius: 3px;
transform: rotateZ(1turn);
}
.can-service-btn {
height: 80rpx;
margin-bottom: 30rpx;
width: 130rpx;
.bg {
width: 64rpx;
height: 28rpx;
opacity: 0.1;
border-radius: 3rpx;
top: 0;
left: 0;
z-index: 1;
}
.text {
height: 40rpx;
line-height: 40rpx;
text-align: center;
width: 100%;
}
.time {
height: 40rpx;
line-height: 40rpx;
text-align: center;
width: 100%;
}
}
.iconyduixingxingshixin {
font-size: 28rpx;
background-image: -webkit-linear-gradient(270deg, #FAD961 0%, #F76B1C 100%);
}
.star-text {
color: #FF9519;
margin-left: 6rpx;
}
.order-num {
color: #4D4D4D;
margin-left: 16rpx;
}
.item-btn {
width: 130rpx;
height: 52rpx;
border-radius: 100rpx;
}
}
}
</style>

View File

@ -0,0 +1,331 @@
<template>
<view class="technician-list-popup">
<uni-popup ref="technician_item" type="bottom" style="height: 600px;">
<view class="technician-popup fill-base">
<view class="pd-lg rel"
:class="[{'flex-center': from=='technician-info'&&showType == 'message' || showType == 'technician'},{'flex-warp': showType == 'message'}]">
<image mode="aspectFill" class="item-avatar radius" :src="info.work_img"></image>
<i @tap.stop="$refs.technician_item.close()" class="iconfont icon-close abs"></i>
<view class="flex-1 ml-md">
<view class="flex-between">
<view class="flex-y-baseline f-caption c-caption">
<view class="f-title c-title text-bold mr-sm max-350 ellipsis">
{{info.coach_name}}
</view>从业{{info.work_time}}
</view>
</view>
<scroll-view scroll-y @touchmove.stop.prevent class="technician-text f-caption c-caption mt-sm"
v-if="from != 'technician-info' && showType == 'message'">
{{info.text}}
</scroll-view>
</view>
</view>
<view class="space-sm fill-body"></view>
<scroll-view scroll-y @touchmove.stop.prevent class="list-content">
<block v-if="showType == 'technician'">
<view class="list-item flex-center pd-lg fill-base radius-16" :class="[{'b-1px-t':index != 0}]"
v-for="(item,index) in serviceList" :key="index">
<image @tap.stop="goDetail(index)" mode="aspectFill" class="avatar lg radius-16"
:src="item.cover"></image>
<view class="flex-1 ml-md">
<view @tap.stop="goDetail(index)" class="f-sm-title c-title text-bold max-510 ellipsis">
{{item.title}}
</view>
<view class="f-caption c-caption mt-sm mb-sm ellipsis">{{item.total_sale}}人选择</view>
<view class="flex-between">
<view class="flex-y-baseline flex-1">
<view class="flex-y-baseline f-icontext c-orange text-bold mr-sm">
<b class="f-paragraph c-orange"></b>
<view class="f-big-title">
{{item.price}}
</view>
</view>
<view class="text-delete mr-sm c-caption f-desc" v-if="item.init_price">
¥{{item.init_price}}</view>
<view class="flex-y-baseline flex-1 c-caption f-desc">
<i class="iconfont icon-shijian mr-sm" :style="{ color: primaryColor }"></i>
{{item.time_long}}分钟
</view>
</view>
<view class="flex-warp">
<block v-if="item.car_num">
<button @tap.stop="changeNum(-1,index)" class="reduce"
:style="{borderColor:primaryColor,color:primaryColor}"><i
class="iconfont icon-jian-bold"></i></button>
<button class="addreduce clear-btn">{{item.car_num || 0}}</button>
</block>
<button @tap.stop="changeNum(1,index)" class="add"
:style="{background:primaryColor,borderColor:primaryColor}"><i
class="iconfont icon-jia-bold"></i></button>
</view>
</view>
</view>
</view>
</block>
<block v-if="showType == 'message'">
<view class="list-message flex-warp pd-lg" :class="[{'b-1px-t':index!=0}]"
v-for="(item,index) in commentList.data" :key="index">
<image mode="aspectFill" class="item-avatar radius" :src="item.avatarUrl"></image>
<view class="flex-1 ml-md">
<view class="flex-between">
<view class="flex-y-center">
<view class="f-paragraph c-title mr-md">{{item.nickName}}</view>
<view class="flex-warp">
<i class="iconfont iconyduixingxingshixin icon-font-color"
:style="{backgroundImage: aindex< item.star?`-webkit-linear-gradient(270deg, #FAD961 0%, #F76B1C 100%)`:`-webkit-linear-gradient(270deg, #f4f6f8 0%, #ccc 100%)`}"
v-for="(aitem,aindex) in 5" :key="aindex"></i>
</view>
</view>
<view class="f-icontext c-caption">{{item.create_time}}</view>
</view>
<view class="flex-warp mt-sm">
<view class="pt-sm pb-sm pl-md pr-md mt-sm mr-sm radius fill-body f-caption c-desc"
v-for="(item,index) in item.lable_text" :key="index">{{item}}</view>
</view>
<view class="f-caption c-caption mt-md">
<text decode="emsp" style="word-break:break-all;">{{item.text}}</text>
</view>
</view>
</view>
</block>
</scroll-view>
<view style="margin: 0 100rpx;"
v-if="!loading&&((showType == 'technician' && serviceList&&serviceList.length<=0) || (showType == 'message' && commentList && commentList.data &&commentList.data.length<=0))">
<abnor></abnor>
</view>
<block v-if="showType == 'message' && commentList.last_page > 1">
<view class="space-lg b-1px-t"></view>
<view
@tap.stop="$refs.technician_item.close(),$util.goUrl({url:`/user/pages/comment?id=${info.id}`})"
class="more-btn flex-center f-paragraph c-base radius"
style="width:300rpx;height: 80rpx;margin:0 auto" :style="{background:primaryColor}">查看更多
</view>
<view class="space-lg"></view>
</block>
<view class="flex-between pd-lg b-1px-t" v-if="showType == 'technician' && car_info.car_count > 0">
<view class="flex-center">合计<view class="f-title c-warning text-bold ml-sm">¥{{car_info.car_price}}
</view>
</view>
<view @tap.stop="toOrder" class="order-btn flex-center f-desc c-base radius"
:style="{background: `linear-gradient(68deg, ${primaryColor}, ${subColor})`}">提交订单
</view>
</view>
<view class="space-safe"></view>
<view style="height: 98rpx;"></view>
</view>
</uni-popup>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
components: {},
props: {
from: {
type: String,
default () {
return 'list'
}
},
sid: {
type: Number,
default () {
return 0
}
}
},
data() {
return {
info: {},
showType: '',
car_info: {},
serviceList: [],
commentList: [],
loading: true,
lockTap: false,
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
plugAuth: state => state.config.configInfo.plugAuth,
userInfo: state => state.user.userInfo,
}),
methods: {
...mapActions([]),
async toShowPopup(info, key) {
this.info = info
this.showType = key
if (key == 'technician') {
let {
is_work = 0,
} = this.info
if (!is_work) return
this.serviceList = []
await this.getServiceList()
} else {
await this.getCommentList()
}
this.$refs.technician_item.open()
},
async getCommentList() {
let {
id: coach_id
} = this.info
let param = {
coach_id,
page: 1,
}
this.commentList = await this.$api.service.commentList(param)
this.loading = false
},
async getServiceList(flag = false) {
let {
id: coach_id
} = this.info
let {
data,
car_count,
car_price
} = await this.$api.service.coachServiceList({
coach_id
})
if (!flag) {
this.serviceList = data
}
this.car_info = {
car_count,
car_price
}
this.loading = false
},
//
goDetail(index) {
let {
id
} = this.serviceList[index]
let {
sid: store_id = 0
} = this
let url = `/user/pages/detail?id=${id}&store_id=${store_id}`
this.$util.goUrl({
url
})
},
// /
async changeNum(mol, serInd) {
let {
id: coach_id
} = this.info
let {
id: service_id,
car_num = 0,
car_id = 0
} = this.serviceList[serInd]
if (this.lockTap) return;
this.lockTap = true;
let methodModel = mol > 0 ? 'addCar' : 'delCar'
let param = mol > 0 ? {
service_id,
coach_id,
num: 1
} : {
id: car_id,
num: 1
}
if (methodModel == 'delCar' && !param.id) {
this.lockTap = false
return
}
try {
let add_car_id = await this.$api.order[methodModel](param)
this.serviceList[serInd].car_num = car_num + mol
if (add_car_id && mol > 0 && !car_id) {
this.serviceList[serInd].car_id = add_car_id
}
if (this.serviceList[serInd].car_num < 1) {
this.serviceList[serInd].car_id = 0
}
await this.getServiceList(true)
this.lockTap = false
} catch (e) {
this.lockTap = false
}
},
//
toOrder() {
if (this.car_info.car_count < 1) {
this.$util.showToast({
title: `请选择服务`
})
return
}
let {
id
} = this.info
this.$refs.technician_item.close()
this.$util.goUrl({
url: `/user/pages/order?id=${id}`
})
},
toEmit(key) {
this.$emit(key)
}
}
}
</script>
<style scoped lang="scss">
.technician-list-popup {
.technician-popup {
border-radius: 20rpx 20rpx 0 0;
.item-avatar {
width: 88rpx;
height: 88rpx;
background: #f4f6f8;
}
.icon-close {
font-size: 50rpx;
top: 30rpx;
right: 30rpx;
}
.technician-text {
max-height: 150rpx;
}
.list-content {
max-height: 50vh;
.list-message {
.item-avatar {
width: 52rpx;
height: 52rpx;
background: #f4f6f8;
}
.iconyduixingxingshixin {
font-size: 28rpx;
margin-right: 5rpx;
font-size: 28rpx;
}
}
}
.order-btn {
width: 200rpx;
height: 72rpx;
}
}
}
</style>

View File

@ -0,0 +1,87 @@
### 使用组件
```html
<time-picker-popup ref="TimePickerPopupRef" :value="value" @confirm="confirm"></time-picker-popup>
```
### 引入组件
```javascript
import TimePickerPopup from '@/components/time-picker-popup/time-picker-popup.vue';
```
### 注册组件
```javascript
export default {
components: { TimePickerPopup },
data() {
return {
value: ['00', '00', '00', '00']
}
},
onReady() {
this.open();
},
methods: {
confirm(data) {
uni.showToast({
title: `${data[0]}:${data[1]}-${data[2]}:${data[3]}`
})
},
open() {
this.$refs.TimePickerPopupRef.open();
}
}
}
```
### 参数
```javascript
// 当前选中的值
value: {
type: Array,
default: () => (['00', '00', '00', '00'])
},
// 标题
title: {
type: String,
default: '时间'
},
// 取消按钮文字
cancelText: {
type: String,
default: '取消'
},
// 取消按钮颜色
canceColor: {
type: String,
default: '#666666'
},
// 确定按钮文字
confirmText: {
type: String,
default: '确定'
},
// 确定按钮颜色
confirmColor: {
type: String,
default: '#2bb781'
},
// 分割符
segmentation: {
type: String,
default: '-'
},
// 设置选择器中间选中框的类名 注意页面或组件的style中写了scoped时需要在类名前写/deep/
indicatorClass: {
type: String,
default: 'picker-view__indicator'
},
// 设置选择器中间选中框的样式
indicatorStyle: {
type: String,
default: ''
},
```

View File

@ -0,0 +1,163 @@
<template>
<!-- 时间选择器弹窗 -->
<uni-popup ref="popup" type="bottom" :safe-area="false">
<view class="custom-picker">
<view class="custom-picker__header">
<view class="cancel" :style="{ color: canceColor }" @tap="onCancel">{{ cancelText }}</view>
<view class="title">{{ title }}</view>
<view class="confirm" :style="{ color: confirmColor }" @tap="onConfirm">{{ confirmText }}</view>
</view>
<picker-view :indicator-class="indicatorClass" :indicator-style="indicatorStyle" class="picker-view"
:value="value" @change="bindChange" @pickstart="pickstart" @pickend="pickend">
<picker-view-column>
<view class="picker-view__item" v-for="(item,index) in rangeList[0]" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="picker-view__item" v-for="(item,index) in rangeList[1]" :key="index">{{item}}</view>
</picker-view-column>
<!-- <view class="picker-view__segmentation">{{ segmentation }}</view> -->
<picker-view-column>
<view class="picker-view__item" v-for="(item,index) in rangeList[2]" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="picker-view__item" v-for="(item,index) in rangeList[3]" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
</uni-popup>
</template>
<script>
import utils from './utils.js'
export default {
name: 'TimePickerPopup',
props: utils.props,
data() {
return {
rangeList: utils.range,
pickerValue: [0, 0, 0, 0],
isScoll: false, //
}
},
mounted() {
},
methods: {
/**
* 开启弹窗
*/
open() {
// props -> value
if (Array.isArray(this.value) && this.value.length) {
this.pickerValue = this.value.map((item, index) => this.rangeList[index].findIndex(child =>
child == this.value[index]));
console.log(this.pickerValue)
} else {
this.pickerValue = [0, 0, 0, 0];
}
this.$refs.popup.open();
},
/**
* 关闭弹窗
*/
close() {
this.$refs.popup.close();
//
this.pickerValue = [0, 0, 0, 0];
},
/**
* 点击确定
*/
onConfirm() {
if (!this.isScoll) {
let data = this.value || ['00', '00', '00', '00'];
if (this.pickerValue && this.pickerValue.length) {
data = this.pickerValue.map((item, index) => String(this.rangeList[index][Number(item)]));
}
this.$emit('confirm', data);
this.close();
}
},
/**
* 点击取消
*/
onCancel() {
this.close();
},
/**
* 滚动开始
*/
pickstart() {
this.isScoll = true;
},
/**
* 滚动结束
*/
pickend() {
this.isScoll = false;
},
/**
* 选择器改变
* @param {Object} e
*/
bindChange(e) {
this.pickerValue = e.detail.value;
},
}
}
</script>
<style lang="scss" scoped>
.custom-picker {
width: 100%;
height: 620rpx;
background-color: #fff;
padding-bottom: 0;
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
position: relative;
z-index: 999;
&__header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 30rpx 40rpx;
.cancel {
color: #666;
}
.title {
font-size: 32rpx;
color: #333;
}
.confirm {
color: #2bb781;
}
}
}
.picker-view {
width: 100%;
height: 100%;
margin-top: 20rpx;
&__item {
line-height: 100rpx;
text-align: center;
}
/deep/ &__indicator {
height: 100rpx;
color: #2bb781;
}
&__segmentation {
display: flex;
align-items: center;
}
}
</style>

View File

@ -0,0 +1,69 @@
// 组件props
const props = {
// 当前选中的值
value: {
type: Array,
default: () => (['00', '00', '00', '00'])
},
// 标题
title: {
type: String,
default: '时间'
},
// 取消按钮文字
cancelText: {
type: String,
default: '取消'
},
// 取消按钮颜色
canceColor: {
type: String,
default: '#666666'
},
// 确定按钮文字
confirmText: {
type: String,
default: '确定'
},
// 确定按钮颜色
confirmColor: {
type: String,
default: '#2bb781'
},
// 分割符
segmentation: {
type: String,
default: '-'
},
// 设置选择器中间选中框的类名 注意页面或组件的style中写了scoped时需要在类名前写/deep/
indicatorClass: {
type: String,
default: 'picker-view__indicator'
},
// 设置选择器中间选中框的样式
indicatorStyle: {
type: String,
default: ''
},
}
// 滚动数据
let range = [
[],
[],
[],
[]
];
for (let i = 0; i < 24; i++) {
range[0].push(i >= 10 ? String(i) : `0${i}`);
range[2].push(i >= 10 ? String(i) : `0${i}`);
}
for (let i = 0; i < 60; i++) {
range[1].push(i >= 10 ? String(i) : `0${i}`);
range[3].push(i >= 10 ? String(i) : `0${i}`);
}
export default {
props,
range
}

210
components/timeline.vue Normal file
View File

@ -0,0 +1,210 @@
<template>
<view>
<view class='record-box fill-base'>
<view class='record-item rel ml-sm b-1px-l' :style="{padding:index==list.length -1?'0 0 0 30rpx':''}"
v-for="(item,index) in list" :key="index">
<text class="item-tag abs" :class="[{'cur':info.pay_type > item.pay_type -1 && info.pay_type != 8}]"
:style="{border:`2rpx solid ${primaryColor}`,background: info.pay_type > item.pay_type -1 && info.pay_type != 8 ? primaryColor : ''}"></text>
<view class='c-title'>
<view class="item-text f-paragraph flex-y-baseline">
{{item.title}}
<view class="ml-md f-caption c-caption"
:style="{color:item.title == '签字确认' && !info.sign_img ? '' : info.pay_type > item.pay_type -1 && info.pay_type != 8 ? primaryColor : ''}">
{{item.title == '签字确认' && !info.sign_img ? '暂未签字确认' : info.pay_type > item.pay_type -1 && info.pay_type != 8? '' : '状态未开始' }}
</view>
</view>
<view class="c-caption" v-if="info.pay_type > item.pay_type*1-1 && info[item.time]">
{{info[item.time]}}
</view>
</view>
<block v-if="item.pay_type == 4 && !info.is_add && info.pay_type > 3">
<view @tap.stop="toMap('serout')" class="flex-y-center mt-md f-caption c-title"
v-if="info.serout_address">
<i class="iconfont iconjuli mr-sm" :style="{color:primaryColor}"></i>{{info.serout_address}}
</view>
</block>
<block
v-if="item.pay_type == 5 && !info.is_add && info.pay_type > 4 && (info.arrive_img || info.arr_address)">
<block v-if="info.arrive_img">
<!-- #ifdef H5 -->
<view @tap.stop="toPreviewImage('arrive_img')" class="item-img mt-md radius-5"
v-if="info.arrive_img">
<view class="h5-image item-img mt-md radius-5"
:style="{ backgroundImage : `url('${info.arrive_img}')`}">
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image @tap.stop="toPreviewImage('arrive_img')" mode="widthFix" class="item-img mt-md radius-5"
:src="info.arrive_img" v-if="info.arrive_img">
</image>
<!-- #endif -->
</block>
<view @tap.stop="toMap('arr')" class="flex-y-center mt-md f-caption c-title"
v-if="info.arr_address">
<i class="iconfont iconjuli mr-sm" :style="{color:primaryColor}"></i>{{info.arr_address}}
</view>
</block>
<block v-if="item.title== '服务完成' && info.pay_type == 7 && (info.end_img || info.end_address)">
<block v-if="info.end_img">
<!-- #ifdef H5 -->
<view @tap.stop="toPreviewImage('end_img')" class="item-img mt-md radius-5">
<view class="h5-image item-img mt-md radius-5"
:style="{ backgroundImage : `url('${info.end_img}')`}">
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image @tap.stop="toPreviewImage('end_img')" mode="widthFix" class="item-img mt-md radius-5"
:src="info.end_img">
</image>
<!-- #endif -->
</block>
<view @tap.stop="toMap('end')" class="flex-y-center mt-md f-caption c-title"
v-if="info.end_address">
<i class="iconfont iconjuli mr-sm" :style="{color:primaryColor}"></i>{{info.end_address}}
</view>
</block>
<block v-if="item.title== '签字确认' && info.pay_type == 7">
<block v-if="info.sign_img">
<!-- #ifdef H5 -->
<view @tap.stop="toPreviewImage('sign_img')" class="item-img mt-md radius-5">
<view class="h5-image item-img mt-md radius-5"
:style="{ backgroundImage : `url('${info.sign_img}')`}">
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image @tap.stop="toPreviewImage('sign_img')" mode="widthFix" class="item-img mt-md radius-5"
:src="info.sign_img">
</image>
<!-- #endif -->
</block>
<view class="flex-between" v-if="type==1 && !info.is_add && !info.sign_img">
<view @tap.stop="toSign" class="item-btn flex-center mt-md c-base radius"
:style="{background:primaryColor}">
签字确认
</view>
<view></view>
</view>
</block>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from "vuex"
export default {
name: 'timeline',
props: {
list: {
type: Array,
default () {
return {}
}
},
info: {
type: Object,
default () {
return {}
}
},
type: {
type: Number,
default () {
return 0
}
}
},
data() {
return {
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
}),
methods: {
toPreviewImage(key) {
let curent = this.info[key]
this.$util.previewImage({
curent,
urls: [curent]
})
},
//
async toMap(key) {
let {
info
} = this
await this.$util.checkAuth({
type: 'userLocation'
})
await uni.getLocation({
type: 'gcj02',
})
await uni.openLocation({
latitude: info[`${key}_lat`] * 1,
longitude: info[`${key}_lng`] * 1,
name: info[`${key}_address`],
scale: 28
})
},
toSign() {
let {
id
} = this.info
let url = `/user/pages/order/sign?id=${id}`
this.$util.goUrl({
url
})
}
},
}
</script>
<style lang="scss">
.record-item {
padding: 0 0 30rpx 30rpx;
.item-tag {
width: 14px;
height: 14px;
display: block;
background: #fff;
border-radius: 50%;
top: 0;
left: -7px;
transform: rotateZ(360deg);
}
.item-text {
line-height: 34rpx;
}
.item-img {
width: 180rpx;
min-height: 118rpx;
}
.item-btn {
width: 160rpx;
height: 64rpx;
background: #EEEEEE;
}
}
.record-item.b-1px-l::before {
border-left: 2px solid #ccc;
}
.record-item:last-child {
padding-bottom: 0;
}
</style>

247
components/uni-nav-bar.vue Normal file
View File

@ -0,0 +1,247 @@
<template>
<view class="uni-navbar" :class="{'uni-navbar-fixed':isFixed,'uni-navbar-shadow':hasShadow}"
:style="{backgroundColor:backgroundColor,height:navBarHeight+'px'}">
<uni-status-bar v-if="insertStatusBar"></uni-status-bar>
<view class="uni-navbar-header" :style="{color:color}">
<view class="uni-navbar-header-btns left" @tap="onClickLeft">
<slot name="left"></slot>
<view v-if="leftIcon || leftText" class="uni-navbar-btn-text"><i class="iconfont" :class="leftIcon"
v-if="leftIcon"></i>{{leftText || ''}}</view>
</view>
<block v-if="!onlyLeft">
<view class="uni-navbar-container">
<view v-if="title.length" class="uni-navbar-container-title">{{title}}</view>
<!-- 标题插槽 -->
<slot></slot>
</view>
<view class="uni-navbar-header-btns right" @tap="onClickRight">
<view v-if="rightText.length" class="uni-navbar-btn-text">{{rightText}}</view>
<slot name="right"></slot>
</view>
</block>
</view>
</view>
</template>
<script>
import uniStatusBar from '@/components/uni-status-bar.vue';
export default {
components: {
uniStatusBar
},
props: {
/**
* 标题文字
*/
title: {
type: String,
default: ''
},
/**
* 左侧按钮图标
*/
leftIcon: {
type: String,
default: ''
},
/**
* 左侧按钮文本
*/
leftText: {
type: String,
default: ''
},
/**
* 右侧按钮文本
*/
rightText: {
type: String,
default: ''
},
/**
* 是否固定在顶部
*/
fixed: {
type: [Boolean, String],
default: false
},
/**
* 按钮图标和文字颜色
*/
color: {
type: String,
default: '#000000'
},
/**
* 背景颜色
*/
backgroundColor: {
type: String,
default: '#FFFFFF'
},
/**
* 是否仅有左侧
*/
onlyLeft: {
type: [Boolean],
default: false
},
/**
* 是否包含状态栏默认固定在顶部时包含
*/
statusBar: {
type: [Boolean, String],
default: ''
},
/**
* 是否使用阴影默认根据背景色判断
*/
shadow: {
type: Boolean,
default () {
return false
}
},
},
computed: {
isFixed() {
return String(this.fixed) === 'true'
},
insertStatusBar() {
switch (String(this.statusBar)) {
case 'true':
return true
case 'false':
return false
default:
return this.isFixed
}
},
hasShadow() {
var backgroundColor = this.backgroundColor
switch (this.shadow) {
case true:
return true
case false:
return false
default:
return backgroundColor !== 'transparent' && backgroundColor.indexOf('rgba') < 0
}
}
},
data() {
return {
navBarHeight: uni.getSystemInfoSync().statusBarHeight * 1 + 44
}
},
methods: {
/**
* 左侧按钮点击事件
*/
onClickLeft() {
let {
leftIcon = ''
} = this
if (leftIcon == 'icon-left') {
this.$util.goUrl({
url: 1,
openType: `navigateBack`
})
} else if (leftIcon == 'iconshouye11') {
this.$util.goUrl({
url: `/pages/service`,
openType: 'reLaunch'
})
} else {
this.$emit('clickLeft')
this.$emit('click-left')
}
},
/**
* 右侧按钮点击事件
*/
onClickRight() {
this.$emit('clickRight')
this.$emit('click-right')
}
}
}
</script>
<style lang="scss">
.uni-navbar {
display: block;
position: relative;
width: 100%;
/* background-color: #FFFFFF; */
overflow: hidden;
}
.uni-navbar-shadow {
box-shadow: 0 1px 6px #ccc;
}
.uni-navbar.uni-navbar-fixed {
position: fixed;
z-index: 9999;
}
.uni-navbar-header {
display: flex;
flex-direction: row;
width: 100%;
/* #ifdef MP-BAIDU */
height: 38px;
line-height: 38px;
font-size: 15px;
/* #endif */
/* #ifndef MP-BAIDU */
height: 44px;
line-height: 44px;
font-size: 16px;
/* #endif */
}
.uni-navbar-header-btns {
display: inline-flex;
flex-wrap: nowrap;
flex-shrink: 0;
width: 100px;
}
.uni-navbar-header-btns.left {
padding-left: 30rpx;
display: flex;
align-items: center;
.iconfont {
font-size: 36rpx;
}
}
.uni-navbar-header-btns.right {
padding-right: 30rpx;
}
.uni-navbar-container {
width: 100%;
margin: 0 5px;
}
.uni-navbar-container-title {
/* #ifdef MP-BAIDU */
height: 38px;
line-height: 38px;
/* #endif */
/* #ifndef MP-BAIDU */
height: 44px;
line-height: 44px;
/* #endif */
font-size: 15px;
text-align: center;
/* #ifndef H5 */
padding-right: 30px;
/* #endif */
max-width: 360rpx;
}
</style>

209
components/uni-popup.vue Normal file
View File

@ -0,0 +1,209 @@
<template>
<view v-if="showPopup" class="uni-popup" :style="{top: top,zIndex: zIndex}">
<view @click="close(true)" :class="[ani, animation ? 'ani' : '', !custom ? 'uni-custom' : '']"
class="uni-popup__mask" :style="{zIndex: zIndex}" />
<view @click="close(true)" :class="[type, ani, animation ? 'ani' : '', !custom ? 'uni-custom' : '']"
class="uni-popup__wrapper" :style="{zIndex: zIndex+1,left: type!='center'?left:''}">
<view class="uni-popup__wrapper-box" @click.stop="clear">
<slot />
</view>
</view>
</view>
</template>
<script>
export default {
name: 'UniPopup',
props: {
//
animation: {
type: Boolean,
default: true
},
// top: bottomcenter
type: {
type: String,
default: 'center'
},
//
custom: {
type: Boolean,
default: false
},
maskClick: {
type: Boolean,
default: true
},
show: {
type: Boolean,
default: true
},
top: {
type: String,
default: '0rpx'
},
left: {
type: String,
default: '0rpx'
},
zIndex: {
type: [Number],
default () {
return 998
}
},
},
data() {
return {
ani: '',
showPopup: false
}
},
watch: {
show(newValue) {
if (newValue) {
this.open()
} else {
this.close()
}
}
},
created() {},
methods: {
clear() {
this.$emit('clear', {
show: false
})
},
open() {
this.$emit('change', {
show: true
})
this.showPopup = true
this.$nextTick(() => {
setTimeout(() => {
this.ani = 'uni-' + this.type
}, 30)
})
},
close(type) {
if (!this.maskClick && type) return
this.$emit('change', {
show: false
})
this.ani = ''
this.$nextTick(() => {
setTimeout(() => {
this.showPopup = false
}, 300)
})
}
}
}
</script>
<style lang="scss">
@charset "UTF-8";
.uni-popup {
position: fixed;
top: 0;
top: 0;
bottom: 0;
left: 0;
right: 0;
overflow: hidden
}
.uni-popup__mask {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(0, 0, 0, .4);
opacity: 0
}
.uni-popup__mask.ani {
transition: all .3s
}
.uni-popup__mask.uni-bottom,
.uni-popup__mask.uni-center,
.uni-popup__mask.uni-top {
opacity: 1
}
.uni-popup__wrapper {
position: absolute;
box-sizing: border-box
}
.uni-popup__wrapper.ani {
transition: all .3s
}
.uni-popup__wrapper.top {
top: 0;
width: 100%;
transform: translateY(-100%)
}
.uni-popup__wrapper.bottom {
bottom: 0;
width: 100%;
transform: translateY(100%)
}
.uni-popup__wrapper.center {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
transform: scale(1.2);
opacity: 0
}
.uni-popup__wrapper-box {
position: relative;
box-sizing: border-box
}
.uni-popup__wrapper.uni-custom .uni-popup__wrapper-box {
/* padding: 30upx; */
// background: #fff
}
.uni-popup__wrapper.uni-custom.center .uni-popup__wrapper-box {
position: relative;
/* max-width: 80%;
max-height: 80%; */
overflow-y: scroll;
border-radius: 25rpx;
}
.uni-popup__wrapper.uni-custom.bottom .uni-popup__wrapper-box {
width: 100%;
// max-height: 500px;
overflow-y: scroll;
border-radius: 25rpx 25rpx 0 0;
}
.uni-popup__wrapper.uni-custom.top .uni-popup__wrapper-box {
width: 100%;
// max-height: 500px;
overflow-y: scroll;
border-radius: 0 0 25rpx 25rpx;
}
.uni-popup__wrapper.uni-bottom,
.uni-popup__wrapper.uni-top {
transform: translateY(0)
}
.uni-popup__wrapper.uni-center {
transform: scale(1);
opacity: 1
}
</style>

View File

@ -0,0 +1,160 @@
<template>
<view class="segmented-control" :class="styleType" :style="wrapStyle">
<view v-for="(item, index) in values" class="segmented-control-item" :class="styleType" :key="index"
:style="index === currentIndex ? activeStyle : itemStyle" @click="onClick(index)">
{{item.title}}
</view>
</view>
</template>
<script>
export default {
name: 'uni-segmented-control',
props: {
current: {
type: Number,
default: 0
},
values: {
type: Array,
default () {
return [];
}
},
activeColor: {
type: String,
default: '#007aff'
},
styleType: {
type: String,
default: 'button'
},
haveBorder: {
type: Boolean,
default () {
return false
}
},
lockTap: {
type: Boolean,
default () {
return true
}
}
},
data() {
return {
currentIndex: this.current
}
},
watch: {
current(val) {
if (val !== this.currentIndex) {
this.currentIndex = val;
}
}
},
computed: {
wrapStyle() {
let styleString = '';
switch (this.styleType) {
case 'text':
styleString = `border:0;`;
break;
default:
styleString = this.haveBorder ? `border: 1rpx solid ${this.activeColor};` : ``;
break;
}
return styleString;
},
itemStyle() {
let styleString = '';
switch (this.styleType) {
case 'text':
styleString = `color:#000;border-left:0;`;
break;
default:
styleString = `color:#222;background:#eddbba;border-color:#fff;`;
break;
}
return styleString;
},
activeStyle() {
let styleString = '';
switch (this.styleType) {
case 'text':
styleString = `color:${this.activeColor};border-left:0;border-bottom-style:solid;`;
break;
default:
styleString = `color:#fff;border-color:${this.activeColor};background-color:${this.activeColor}`;
break;
}
return styleString;
}
},
methods: {
onClick(index) {
let {
lockTap,
currentIndex
} = this
console.log(lockTap, currentIndex);
if (currentIndex !== index || !lockTap) {
this.currentIndex = index;
this.$emit('clickItem', index);
}
}
},
}
</script>
<style>
.segmented-control {
display: flex;
flex-direction: row;
justify-content: center;
font-size: 26rpx;
border-radius: 5rpx;
box-sizing: border-box;
margin: 0 auto;
overflow: hidden;
}
.segmented-control.button {
border-radius: 54rpx;
box-sizing: border-box;
}
.segmented-control.text {
border: 0;
border-radius: 0rpx;
}
.segmented-control-item {
flex: 1;
text-align: center;
line-height: 54rpx;
box-sizing: border-box;
}
.segmented-control-item.button {
border-left: 1upx solid;
}
.segmented-control-item.button:first-child {
border-radius: 54rpx 0 0 54rpx;
}
.segmented-control-item.button:last-child {
border-radius: 0 54rpx 54rpx 0;
}
.segmented-control-item.text {
border-left: 0;
}
.segmented-control-item:first-child {
border-left-width: 0;
}
</style>

View File

@ -0,0 +1,29 @@
<template>
<view class="uni-status-bar" :style="style">
<slot></slot>
</view>
</template>
<script>
export default {
computed: {
style() {
//#ifdef APP-PLUS
return ''
//#endif
//#ifndef APP-PLUS
return `height:${uni.getSystemInfoSync().statusBarHeight}px`
//#endif
}
}
}
</script>
<style>
.uni-status-bar {
display: block;
width: 100%;
height: 20px;
height: var(--status-bar-height);
}
</style>

353
components/upload.vue Normal file
View File

@ -0,0 +1,353 @@
<template>
<view
:class="[{'flex-warp':!imgclass || imgclass =='mini'},{'flex-center flex-column':imgclass && imgclass != 'mini'}]">
<block v-for="(item,index) in imagelist" :key="index">
<view class="rel item-child radius-16" :class="[imgclass,{'margin': imgsize > 1}]"
:style="{border:imgclass=='apply'?`4rpx solid ${primaryColor}`:''}">
<!-- #ifdef H5 -->
<view class="upload-img radius-16" v-if="filetype == 'picture'">
<view @tap.stop="previewImage(item,imagelist)" class="h5-image upload-img radius-16"
:style="{ backgroundImage : `url('${item.path}')`}">
</view>
</view>
<!-- #endif -->
<!-- #ifndef H5 -->
<image mode="aspectFill" @tap.stop="previewImage(item,imagelist)" class="upload-img radius-16"
:src="item.path" v-if="filetype == 'picture'"></image>
<!-- #endif -->
<video :id="`video_${index}`" class="upload-video rel radius-16" :loop="false" enable-play-gesture
enable-progress-gesture :show-center-play-btn="true" :controls="true" :src="item.path"
:data-id="item.id" objectFit="cover" :data-index="index" @play="onPlay" @pause="onPause"
@ended="onEnded" @timeupdate="onTimeUpdate" @waiting="onWaiting" @progress="onProgress"
@loadedmetadata="onLoadedMetaData" v-if="filetype == 'video'">
<cover-view @tap="toDel(index)" class="item-delete abs flex-center f-icontext c-base"
:style="{background:primaryColor}">
删除
</cover-view>
</video>
<block v-if="filetype == 'picture'">
<view @tap="toDel(index)" class="guanbi abs flex-center" :class="[imgclass]" style="z-index: 1;"
v-if="imgsize>1"><i class="iconfont icon-add rotate-45 c-base"></i></view>
<view @tap="chooseImage" class="flex-center flex-column item-child upload-item radius-16 abs"
:class="[imgclass]" style="top:0;margin-top:0;background:rgba(0,0,0,0.5);" v-else>
<view class="upload-icon flex-center c-title radius-10">
<i class="iconfont icon-camera"></i>
</view>
<view class="f-caption c-base mt-sm">重新上传</view>
</view>
</block>
</view>
</block>
<view @tap="chooseImage" class="radius-16 flex-center flex-column item-child upload-item fill-body radius-16"
:class="[imgclass,{'margin': imgsize > 1}]"
:style="{border:imgclass=='apply'?`4rpx solid ${primaryColor}`:''}" v-if="imagelist.length < imgsize">
<view class="upload-icon flex-center c-title radius-10">
<i class="iconfont icon-camera"></i>
</view>
<view class="f-caption c-caption mt-sm" v-if="text">{{text}}</view>
<view class="cur-imgsize f-caption c-caption" v-if="imgsize>1">{{`${imagelist.length}/${imgsize}`}}</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapActions,
} from 'vuex';
export default {
props: {
//
imagelist: {
type: Array,
default () {
return []
}
},
//
imgtype: {
type: String,
default () {
return ''
}
},
//
imgsize: {
type: Number,
default () {
return 9
}
},
//
filetype: {
type: String,
default () {
return 'picture'
}
},
//
imgclass: {
type: String,
default () {
return ''
}
},
//
text: {
type: String,
default () {
return ''
}
},
//
videoSize: {
type: Number,
default () {
return 50
}
},
//
sourceType: {
type: Number,
default () {
return 1
}
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
userInfo: state => state.user.userInfo
}),
methods: {
previewImage(current, urls) {
let res_urls = [];
urls = this.$util.deepCopy(urls);
urls.forEach((item, index) => {
res_urls.push(item.path)
})
uni.previewImage({
current: current.path,
urls: res_urls,
})
},
async toDel(index) {
let fileName = this.filetype == 'picture' ? '图片' : '视频'
let [res_del, {
confirm
}] = await uni.showModal({
content: `请确认是否要删除${fileName}`,
})
if (!confirm) return;
this.imagelist.splice(index, 1);
this.$emit('del', {
imgtype: this.imgtype,
imagelist: this.imagelist
});
},
async chooseImage() {
let {
imgtype,
imgsize,
filetype,
videoSize,
sourceType
} = this;
let imagelist = this.$util.deepCopy(this.imagelist)
let is_upload_img = filetype == 'picture'
let chooseModel = is_upload_img ? 'chooseImage' : 'chooseVideo'
let count = 1
// #ifndef H5
count = imgsize == 1 ? 1 : imgsize - imagelist.length * 1
// #endif
let param = {
count
}
if (is_upload_img) {
param.sizeType = ['compressed']
}
param.sourceType = sourceType == 1 ? ['camera', 'album'] : ['camera']
let [res_upload, res_info] = await uni[chooseModel](param)
if (res_upload) return
let {
size = 0,
tempFiles,
tempFilePath = ''
} = res_info
console.log(is_upload_img, size, size / 1024 / 1024, "=====size")
if (filetype == 'video' && size / 1024 / 1024 > videoSize) {
this.$util.showToast({
title: `上传视频大小超过限制${videoSize}M`
})
return
}
let filePath = [];
//
this.$util.showLoading({
title: "上传中"
});
console.log("======tempFiles==tempFilePath", tempFiles, tempFilePath)
if (is_upload_img) {
for (let i = 0; i < tempFiles.length; i++) {
let {
attachment_path: path
} = await this.$api.base.uploadFile({
filePath: tempFiles[i].path,
formData: {
type: this.filetype
}
})
if (imgsize > 1) {
imagelist.push({
path
})
} else {
imagelist = [{
path
}]
}
}
} else {
let {
attachment_path: path
} = await this.$api.base.uploadFile({
filePath: tempFilePath,
formData: {
type: this.filetype
}
})
imagelist.push({
path
})
}
this.$util.hideAll()
this.$emit('upload', {
imgtype,
imagelist
});
},
onPlay(e) {},
onPause(e) {},
onEnded(e) {},
onTimeUpdate(e) {},
onWaiting(e) {},
onProgress(e) {},
onLoadedMetaData(e) {},
}
}
</script>
<style lang="scss">
.item-child {
width: 216rpx;
height: 216rpx;
background: #F7F7F7;
margin-bottom: 20rpx;
}
.margin {
margin: 0 21rpx 21rpx 0;
}
.item-child:nth-child(3n) {
margin-right: 0rpx;
}
.item-child.sm {
width: 140rpx;
height: 140rpx;
}
.item-child.apply {
width: 176rpx;
height: 176rpx;
}
.item-child.mini {
width: 196rpx;
height: 196rpx;
}
.item-child.md {
width: 335rpx;
height: 210rpx;
margin: 0rpx;
}
.item-child.lg {
width: 686rpx;
height: 400rpx;
}
.upload-img,
.upload-video {
width: 100%;
height: 100%;
}
.upload-video {
.item-delete {
width: 60rpx;
height: 32rpx;
top: 0;
right: 0;
z-index: 2;
}
}
.upload-item {
.upload-icon {
width: 80rpx;
height: 76rpx;
background: #FFFFFF;
.iconfont {
font-size: 40rpx;
display: block;
}
}
.cur-imgsize {
line-height: 1.1;
}
}
.upload-item.margin {
margin-bottom: 0;
}
.item-child.apply {
.upload-item {
width: 168rpx;
height: 168rpx;
}
}
.guanbi {
width: 32rpx;
height: 32rpx;
background: rgba(0, 0, 0, 0.2);
border-radius: 0 15rpx 0 0;
top: 0rpx;
right: 0rpx;
z-index: 1;
.iconfont {
font-size: 28rpx;
}
}
.guanbi.lg {
width: 50rpx;
height: 50rpx;
.iconfont {
font-size: 38rpx;
}
}
</style>

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,742 @@
<template>
<view class="w-picker-view">
<picker-view v-if="fields=='year'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
<picker-view v-if="fields=='month'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
<picker-view v-if="fields=='day'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
<picker-view v-if="fields=='hour'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
<picker-view v-if="fields=='minute'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
<picker-view v-if="fields=='second'" class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.seconds" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
export default {
data() {
return {
pickVal:[],
range:{
years:[],
months:[],
days:[],
hours:[],
minutes:[],
seconds:[]
},
checkObj:{}
};
},
props:{
itemHeight:{
type:String,
default:"44px"
},
startYear:{
type:[String,Number],
default:""
},
endYear:{
type:[String,Number],
default:""
},
value:{
type:[String,Array,Number],
default:""
},
current:{//
type:Boolean,
default:false
},
disabledAfter:{//
type:Boolean,
default:false
},
fields:{
type:String,
default:"day"
}
},
watch:{
fields(val){
this.initData();
},
value(val){
this.initData();
}
},
created() {
this.initData();
},
methods:{
formatNum(n){
return (Number(n)<10?'0'+Number(n):Number(n)+'');
},
checkValue(value){
let strReg,example
switch(this.fields){
case "year":
strReg=/^\d{4}$/;
example="2019";
break;
case "month":
strReg=/^\d{4}-\d{2}$/;
example="2019-02";
break;
case "day":
strReg=/^\d{4}-\d{2}-\d{2}$/;
example="2019-02-01";
break;
case "hour":
strReg=/^\d{4}-\d{2}-\d{2} \d{2}(:\d{2}){1,2}?$/;
example="2019-02-01 18:00:00或2019-02-01 18";
break;
case "minute":
strReg=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2}){0,1}?$/;
example="2019-02-01 18:06:00或2019-02-01 18:06";
break;
case "second":
strReg=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/;
example="2019-02-01 18:06:01";
break;
}
if(!strReg.test(value)){
console.log(new Error("请传入与mode、fields匹配的value值例value="+example+""))
}
return strReg.test(value);
},
resetData(year,month,day,hour,minute){
let curDate=this.getCurrenDate();
let curFlag=this.current;
let curYear=curDate.curYear;
let curMonth=curDate.curMonth;
let curDay=curDate.curDay;
let curHour=curDate.curHour;
let curMinute=curDate.curMinute;
let curSecond=curDate.curSecond;
let months=[],days=[],hours=[],minutes=[],seconds=[];
let disabledAfter=this.disabledAfter;
let monthsLen=disabledAfter?(year*1<curYear?12:curMonth):12;
let totalDays=new Date(year,month,0).getDate();//;
let daysLen=disabledAfter?((year*1<curYear||month*1<curMonth)?totalDays:curDay):totalDays;
let hoursLen=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay)?24:curHour+1):24;
let minutesLen=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay||hour*1<curHour)?60:curMinute+1):60;
let secondsLen=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay||hour*1<curHour||minute*1<curMinute)?60:curSecond+1):60;
for(let month=1;month<=monthsLen;month++){
months.push(this.formatNum(month));
};
for(let day=1;day<=daysLen;day++){
days.push(this.formatNum(day));
}
for(let hour=0;hour<hoursLen;hour++){
hours.push(this.formatNum(hour));
}
for(let minute=0;minute<minutesLen;minute++){
minutes.push(this.formatNum(minute));
}
for(let second=0;second<secondsLen;second++){
seconds.push(this.formatNum(second));
}
return{
months,
days,
hours,
minutes,
seconds
}
},
isLeapYear (Year) {
if (((Year % 4)==0) && ((Year % 100)!=0) || ((Year % 400)==0)) {
return true;
} else {
return false;
}
},
getData(dVal){
//
let curFlag=this.current;
let disabledAfter=this.disabledAfter;
let fields=this.fields;
let curDate=this.getCurrenDate();
let curYear=curDate.curYear;
let curMonthdays=curDate.curMonthdays;
let curMonth=curDate.curMonth;
let curDay=curDate.curDay;
let curHour=curDate.curHour;
let curMinute=curDate.curMinute;
let curSecond=curDate.curSecond;
let defaultDate=this.getDefaultDate();
let startYear=this.getStartDate().getFullYear();
let endYear=this.getEndDate().getFullYear();
//year,month,day,hour;,
let years=[],months=[],days=[],hours=[],minutes=[],seconds=[];
let year=dVal[0]*1;
let month=dVal[1]*1;
let day=dVal[2]*1;
let hour=dVal[3]*1;
let minute=dVal[4]*1;
let monthsLen=disabledAfter?(year<curYear?12:curDate.curMonth):12;
let daysLen=disabledAfter?((year<curYear||month<curMonth)?defaultDate.defaultDays:curDay):(curFlag?curMonthdays:defaultDate.defaultDays);
let hoursLen=disabledAfter?((year<curYear||month<curMonth||day<curDay)?24:curHour+1):24;
let minutesLen=disabledAfter?((year<curYear||month<curMonth||day<curDay||hour<curHour)?60:curMinute+1):60;
let secondsLen=disabledAfter?((year<curYear||month<curMonth||day<curDay||hour<curHour||minute<curMinute)?60:curSecond+1):60;
for(let year=startYear;year<=(disabledAfter?curYear:endYear);year++){
years.push(year.toString())
}
for(let month=1;month<=monthsLen;month++){
months.push(this.formatNum(month));
}
for(let day=1;day<=daysLen;day++){
days.push(this.formatNum(day));
}
for(let hour=0;hour<hoursLen;hour++){
hours.push(this.formatNum(hour));
}
for(let minute=0;minute<minutesLen;minute++){
minutes.push(this.formatNum(minute));
}
// for(let second=0;second<(disabledAfter?curDate.curSecond+1:60);second++){
// seconds.push(this.formatNum(second));
// }
for(let second=0;second<60;second++){
seconds.push(this.formatNum(second));
}
return {
years,
months,
days,
hours,
minutes,
seconds
}
},
getCurrenDate(){
let curDate=new Date();
let curYear=curDate.getFullYear();
let curMonth=curDate.getMonth()+1;
let curMonthdays=new Date(curYear,curMonth,0).getDate();
let curDay=curDate.getDate();
let curHour=curDate.getHours();
let curMinute=curDate.getMinutes();
let curSecond=curDate.getSeconds();
return{
curDate,
curYear,
curMonth,
curMonthdays,
curDay,
curHour,
curMinute,
curSecond
}
},
getDefaultDate(){
let value=this.value;
let reg=/-/g;
let defaultDate=value?new Date(value.replace(reg,"/")):new Date();
let defaultYear=defaultDate.getFullYear();
let defaultMonth=defaultDate.getMonth()+1;
let defaultDay=defaultDate.getDate();
let defaultDays=new Date(defaultYear,defaultMonth,0).getDate()*1;
return{
defaultDate,
defaultYear,
defaultMonth,
defaultDay,
defaultDays
}
},
getStartDate(){
let start=this.startYear;
let startDate="";
let reg=/-/g;
if(start){
startDate=new Date(start+"/01/01");
}else{
startDate=new Date("1970/01/01");
}
return startDate;
},
getEndDate(){
let end=this.endYear;
let reg=/-/g;
let endDate="";
if(end){
endDate=new Date(end+"/12/01");
}else{
endDate=new Date();
}
return endDate;
},
getDval(){
let value=this.value;
let fields=this.fields;
let dVal=null;
let aDate=new Date();
let year=this.formatNum(aDate.getFullYear());
let month=this.formatNum(aDate.getMonth()+1);
let day=this.formatNum(aDate.getDate());
let hour=this.formatNum(aDate.getHours());
let minute=this.formatNum(aDate.getMinutes());
let second=this.formatNum(aDate.getSeconds());
if(value){
let flag=this.checkValue(value);
if(!flag){
dVal=[year,month,day,hour,minute,second]
}else{
switch(this.fields){
case "year":
dVal=value?[value]:[];
break;
case "month":
dVal=value?value.split("-"):[];
break;
case "day":
dVal=value?value.split("-"):[];
break;
case "hour":
dVal=[...value.split(" ")[0].split("-"),...value.split(" ")[1].split(":")];
break;
case "minute":
dVal=value?[...value.split(" ")[0].split("-"),...value.split(" ")[1].split(":")]:[];
break;
case "second":
dVal=[...value.split(" ")[0].split("-"),...value.split(" ")[1].split(":")];
break;
}
}
}else{
dVal=[year,month,day,hour,minute,second]
}
return dVal;
},
initData(){
let startDate,endDate,startYear,endYear,startMonth,endMonth,startDay,endDay;
let years=[],months=[],days=[],hours=[],minutes=[],seconds=[];
let dVal=[],pickVal=[];
let value=this.value;
let reg=/-/g;
let range={};
let result="",full="",year,month,day,hour,minute,second,obj={};
let defaultDate=this.getDefaultDate();
let defaultYear=defaultDate.defaultYear;
let defaultMonth=defaultDate.defaultMonth;
let defaultDay=defaultDate.defaultDay;
let defaultDays=defaultDate.defaultDays;
let curFlag=this.current;
let disabledAfter=this.disabledAfter;
let curDate=this.getCurrenDate();
let curYear=curDate.curYear;
let curMonth=curDate.curMonth;
let curMonthdays=curDate.curMonthdays;
let curDay=curDate.curDay;
let curHour=curDate.curHour;
let curMinute=curDate.curMinute;
let curSecond=curDate.curSecond;
let dateData=[];
dVal=this.getDval();
startDate=this.getStartDate();
endDate=this.getEndDate();
startYear=startDate.getFullYear();
startMonth=startDate.getMonth();
startDay=startDate.getDate();
endYear=endDate.getFullYear();
endMonth=endDate.getMonth();
endDay=endDate.getDate();
dateData=this.getData(dVal);
years=dateData.years;
months=dateData.months;
days=dateData.days;
hours=dateData.hours;
minutes=dateData.minutes;
seconds=dateData.seconds;
switch(this.fields){
case "year":
pickVal=disabledAfter?[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0
]:(curFlag?[
years.indexOf(curYear+'')
]:[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0
]);
range={years};
year=dVal[0]?dVal[0]:years[0];
result=full=`${year}`;
obj={
year
}
break;
case "month":
pickVal=disabledAfter?[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0
]:(curFlag?[
years.indexOf(curYear+''),
months.indexOf(this.formatNum(curMonth))
]:[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0
]);
range={years,months};
year=dVal[0]?dVal[0]:years[0];
month=dVal[1]?dVal[1]:months[0];
result=full=`${year+'-'+month}`;
obj={
year,
month
}
break;
case "day":
pickVal=disabledAfter?[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0
]:(curFlag?[
years.indexOf(curYear+''),
months.indexOf(this.formatNum(curMonth)),
days.indexOf(this.formatNum(curDay)),
]:[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0
]);
range={years,months,days};
year=dVal[0]?dVal[0]:years[0];
month=dVal[1]?dVal[1]:months[0];
day=dVal[2]?dVal[2]:days[0];
result=full=`${year+'-'+month+'-'+day}`;
obj={
year,
month,
day
}
break;
case "hour":
pickVal=disabledAfter?[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0
]:(curFlag?[
years.indexOf(curYear+''),
months.indexOf(this.formatNum(curMonth)),
days.indexOf(this.formatNum(curDay)),
hours.indexOf(this.formatNum(curHour)),
]:[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0
]);
range={years,months,days,hours};
year=dVal[0]?dVal[0]:years[0];
month=dVal[1]?dVal[1]:months[0];
day=dVal[2]?dVal[2]:days[0];
hour=dVal[3]?dVal[3]:hours[0];
result=`${year+'-'+month+'-'+day+' '+hour}`;
full=`${year+'-'+month+'-'+day+' '+hour+':00:00'}`;
obj={
year,
month,
day,
hour
}
break;
case "minute":
pickVal=disabledAfter?[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0
]:(curFlag?[
years.indexOf(curYear+''),
months.indexOf(this.formatNum(curMonth)),
days.indexOf(this.formatNum(curDay)),
hours.indexOf(this.formatNum(curHour)),
minutes.indexOf(this.formatNum(curMinute)),
]:[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0
]);
range={years,months,days,hours,minutes};
year=dVal[0]?dVal[0]:years[0];
month=dVal[1]?dVal[1]:months[0];
day=dVal[2]?dVal[2]:days[0];
hour=dVal[3]?dVal[3]:hours[0];
minute=dVal[4]?dVal[4]:minutes[0];
full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':00'}`;
result=`${year+'-'+month+'-'+day+' '+hour+':'+minute}`;
obj={
year,
month,
day,
hour,
minute
}
break;
case "second":
pickVal=disabledAfter?[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0,
dVal[5]&&seconds.indexOf(dVal[5])!=-1?seconds.indexOf(dVal[5]):0
]:(curFlag?[
years.indexOf(curYear+''),
months.indexOf(this.formatNum(curMonth)),
days.indexOf(this.formatNum(curDay)),
hours.indexOf(this.formatNum(curHour)),
minutes.indexOf(this.formatNum(curMinute)),
seconds.indexOf(this.formatNum(curSecond)),
]:[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
dVal[3]&&hours.indexOf(dVal[3])!=-1?hours.indexOf(dVal[3]):0,
dVal[4]&&minutes.indexOf(dVal[4])!=-1?minutes.indexOf(dVal[4]):0,
dVal[5]&&seconds.indexOf(dVal[5])!=-1?seconds.indexOf(dVal[5]):0
]);
range={years,months,days,hours,minutes,seconds};
year=dVal[0]?dVal[0]:years[0];
month=dVal[1]?dVal[1]:months[0];
day=dVal[2]?dVal[2]:days[0];
hour=dVal[3]?dVal[3]:hours[0];
minute=dVal[4]?dVal[4]:minutes[0];
second=dVal[5]?dVal[5]:seconds[0];
result=full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':'+second}`;
obj={
year,
month,
day,
hour,
minute,
second
}
break;
default:
range={years,months,days};
break;
}
this.range=range;
this.checkObj=obj;
this.$emit("change",{
result:result,
value:full,
obj:obj
});
this.$nextTick(()=>{
this.pickVal=pickVal;
})
},
handlerChange(e){
let arr=[...e.detail.value];
let data=this.range;
let year="",month="",day="",hour="",minute="",second="";
let result="",full="",obj={};
let months=null,days=null,hours=null,minutes=null,seconds=null;
let disabledAfter=this.disabledAfter;
let leapYear=false,resetData={};
year=(arr[0]||arr[0]==0)?data.years[arr[0]]||data.years[data.years.length-1]:"";
month=(arr[1]||arr[1]==0)?data.months[arr[1]]||data.months[data.months.length-1]:"";
day=(arr[2]||arr[2]==0)?data.days[arr[2]]||data.days[data.days.length-1]:"";
hour=(arr[3]||arr[3]==0)?data.hours[arr[3]]||data.hours[data.hours.length-1]:"";
minute=(arr[4]||arr[4]==0)?data.minutes[arr[4]]||data.minutes[data.minutes.length-1]:"";
second=(arr[5]||arr[5]==0)?data.seconds[arr[5]]||data.seconds[data.seconds.length-1]:"";
resetData=this.resetData(year,month,day,hour,minute);//;
leapYear=this.isLeapYear(year);//;
switch(this.fields){
case "year":
result=full=`${year}`;
obj={
year
};
break;
case "month":
result=full=`${year+'-'+month}`;
if(this.disabledAfter)months=resetData.months;
if(months)this.range.months=months;
obj={
year,
month
}
break;
case "day":
result=full=`${year+'-'+month+'-'+day}`;
if(this.disabledAfter){
months=resetData.months;
days=resetData.days;
}else{
if(leapYear||(month!=this.checkObj.month)||month==2){
days=resetData.days;
}
}
if(months)this.range.months=months;
if(days)this.range.days=days;
obj={
year,
month,
day
}
break;
case "hour":
result=`${year+'-'+month+'-'+day+' '+hour}`;
full=`${year+'-'+month+'-'+day+' '+hour+':00:00'}`;
if(this.disabledAfter){
months=resetData.months;
days=resetData.days;
hours=resetData.hours;
}else{
if(leapYear||(month!=this.checkObj.month)||month==2){
days=resetData.days;
}
}
if(months)this.range.months=months;
if(days)this.range.days=days;
if(hours)this.range.hours=hours;
obj={
year,
month,
day,
hour
}
break;
case "minute":
full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':00'}`;
result=`${year+'-'+month+'-'+day+' '+hour+':'+minute}`;
if(this.disabledAfter){
months=resetData.months;
days=resetData.days;
hours=resetData.hours;
minutes=resetData.minutes;
}else{
if(leapYear||(month!=this.checkObj.month)||month==2){
days=resetData.days;
}
}
if(months)this.range.months=months;
if(days)this.range.days=days;
if(hours)this.range.hours=hours;
if(minutes)this.range.minutes=minutes;
obj={
year,
month,
day,
hour,
minute
};
break;
case "second":
result=full=`${year+'-'+month+'-'+day+' '+hour+':'+minute+':'+second}`;
if(this.disabledAfter){
months=resetData.months;
days=resetData.days;
hours=resetData.hours;
minutes=resetData.minutes;
//seconds=resetData.seconds;
}else{
if(leapYear||(month!=this.checkObj.month)||month==2){
days=resetData.days;
}
}
if(months)this.range.months=months;
if(days)this.range.days=days;
if(hours)this.range.hours=hours;
if(minutes)this.range.minutes=minutes;
//if(seconds)this.range.seconds=seconds;
obj={
year,
month,
day,
hour,
minute,
second
}
break;
}
this.checkObj=obj;
this.$emit("change",{
result:result,
value:full,
obj:obj
})
}
}
}
</script>
<style lang="scss">
@import "./w-picker.css";
</style>

View File

@ -0,0 +1,345 @@
<template>
<view class="w-picker-view">
<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.years" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.months" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.days" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.sections" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
export default {
data() {
return {
pickVal:[],
range:{},
checkObj:{}
};
},
props:{
itemHeight:{
type:String,
default:"44px"
},
startYear:{
type:String,
default:""
},
endYear:{
type:String,
default:""
},
value:{
type:[String,Array,Number],
default:""
},
current:{//
type:Boolean,
default:false
},
disabledAfter:{//
type:Boolean,
default:false
}
},
watch:{
value(val){
this.initData();
}
},
created() {
this.initData();
},
methods:{
formatNum(n){
return (Number(n)<10?'0'+Number(n):Number(n)+'');
},
checkValue(value){
let strReg=/^\d{4}-\d{2}-\d{2} [\u4e00-\u9fa5]{2}$/,example;
if(!strReg.test(value)){
console.log(new Error("请传入与mode、fields匹配的value值例value="+example+""))
}
return strReg.test(value);
},
resetData(year,month,day){
let curDate=this.getCurrenDate();
let curFlag=this.current;
let curYear=curDate.curYear;
let curMonth=curDate.curMonth;
let curDay=curDate.curDay;
let curHour=curDate.curHour;
let months=[],days=[],sections=[];
let disabledAfter=this.disabledAfter;
let monthsLen=disabledAfter?(year*1<curYear?12:curMonth):12;
let totalDays=new Date(year,month,0).getDate();//;
let daysLen=disabledAfter?((year*1<curYear||month*1<curMonth)?totalDays:curDay):totalDays;
let sectionFlag=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay)==true?false:true):(curHour>12==true?true:false);
sections=["上午","下午"];
for(let month=1;month<=monthsLen;month++){
months.push(this.formatNum(month));
};
for(let day=1;day<=daysLen;day++){
days.push(this.formatNum(day));
}
if(sectionFlag){
sections=["上午"];
}
return{
months,
days,
sections
}
},
getData(dVal){
//
let curFlag=this.current;
let disabledAfter=this.disabledAfter;
let curDate=this.getCurrenDate();
let curYear=curDate.curYear;
let curMonthdays=curDate.curMonthdays;
let curMonth=curDate.curMonth;
let curDay=curDate.curDay;
let curHour=curDate.curHour;
let defaultDate=this.getDefaultDate();
let startYear=this.getStartDate().getFullYear();
let endYear=this.getEndDate().getFullYear();
let years=[],months=[],days=[],sections=[];
let year=dVal[0]*1;
let month=dVal[1]*1;
let day=dVal[2]*1;
let monthsLen=disabledAfter?(year<curYear?12:curDate.curMonth):12;
let daysLen=disabledAfter?((year<curYear||month<curMonth)?defaultDate.defaultDays:curDay):(curFlag?curMonthdays:defaultDate.defaultDays);
let sectionFlag=disabledAfter?((year*1<curYear||month*1<curMonth||day*1<curDay)==true?false:true):(curHour>12==true?true:false);
for(let year=startYear;year<=(disabledAfter?curYear:endYear);year++){
years.push(year.toString())
}
for(let month=1;month<=monthsLen;month++){
months.push(this.formatNum(month));
}
for(let day=1;day<=daysLen;day++){
days.push(this.formatNum(day));
}
if(sectionFlag){
sections=["下午"];
}else{
sections=["上午","下午"];
}
return {
years,
months,
days,
sections
}
},
getCurrenDate(){
let curDate=new Date();
let curYear=curDate.getFullYear();
let curMonth=curDate.getMonth()+1;
let curMonthdays=new Date(curYear,curMonth,0).getDate();
let curDay=curDate.getDate();
let curHour=curDate.getHours();
let curSection="上午";
if(curHour>=12){
curSection="下午";
}
return{
curDate,
curYear,
curMonth,
curMonthdays,
curDay,
curHour,
curSection
}
},
getDefaultDate(){
let value=this.value;
let reg=/-/g;
let defaultDate=value?new Date(value.split(" ")[0].replace(reg,"/")):new Date();
let defaultYear=defaultDate.getFullYear();
let defaultMonth=defaultDate.getMonth()+1;
let defaultDay=defaultDate.getDate();
let defaultDays=new Date(defaultYear,defaultMonth,0).getDate()*1;
return{
defaultDate,
defaultYear,
defaultMonth,
defaultDay,
defaultDays
}
},
getStartDate(){
let start=this.startYear;
let startDate="";
let reg=/-/g;
if(start){
startDate=new Date(start+"/01/01");
}else{
startDate=new Date("1970/01/01");
}
return startDate;
},
getEndDate(){
let end=this.endYear;
let reg=/-/g;
let endDate="";
if(end){
endDate=new Date(end+"/12/31");
}else{
endDate=new Date();
}
return endDate;
},
getDval(){
let value=this.value;
let dVal=null;
let aDate=new Date();
let year=this.formatNum(aDate.getFullYear());
let month=this.formatNum(aDate.getMonth()+1);
let day=this.formatNum(aDate.getDate());
let hour=aDate.getHours();
let section="上午";
if(hour>=12)section="下午";
if(value){
let flag=this.checkValue(value);
if(!flag){
dVal=[year,month,day,section]
}else{
let v=value.split(" ");
dVal=[...v[0].split("-"),v[1]];
}
}else{
dVal=[year,month,day,section]
}
return dVal;
},
initData(){
let startDate,endDate,startYear,endYear,startMonth,endMonth,startDay,endDay;
let years=[],months=[],days=[],sections=[];
let dVal=[],pickVal=[];
let value=this.value;
let reg=/-/g;
let range={};
let result="",full="",year,month,day,section,obj={};
let defaultDate=this.getDefaultDate();
let defaultYear=defaultDate.defaultYear;
let defaultMonth=defaultDate.defaultMonth;
let defaultDay=defaultDate.defaultDay;
let defaultDays=defaultDate.defaultDays;
let curFlag=this.current;
let disabledAfter=this.disabledAfter;
let curDate=this.getCurrenDate();
let curYear=curDate.curYear;
let curMonth=curDate.curMonth;
let curMonthdays=curDate.curMonthdays;
let curDay=curDate.curDay;
let curSection=curDate.curSection;
let dateData=[];
dVal=this.getDval();
startDate=this.getStartDate();
endDate=this.getEndDate();
startYear=startDate.getFullYear();
startMonth=startDate.getMonth();
startDay=startDate.getDate();
endYear=endDate.getFullYear();
endMonth=endDate.getMonth();
endDay=endDate.getDate();
dateData=this.getData(dVal);
years=dateData.years;
months=dateData.months;
days=dateData.days;
sections=dateData.sections;
pickVal=disabledAfter?[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
dVal[3]&&sections.indexOf(dVal[3])!=-1?sections.indexOf(dVal[3]):0
]:(curFlag?[
years.indexOf(curYear+''),
months.indexOf(this.formatNum(curMonth)),
days.indexOf(this.formatNum(curDay)),
sections.indexOf(curSection),
]:[
dVal[0]&&years.indexOf(dVal[0])!=-1?years.indexOf(dVal[0]):0,
dVal[1]&&months.indexOf(dVal[1])!=-1?months.indexOf(dVal[1]):0,
dVal[2]&&days.indexOf(dVal[2])!=-1?days.indexOf(dVal[2]):0,
dVal[3]&&sections.indexOf(dVal[3])!=-1?sections.indexOf(dVal[3]):0
]);
range={years,months,days,sections};
year=dVal[0]?dVal[0]:years[0];
month=dVal[1]?dVal[1]:months[0];
day=dVal[2]?dVal[2]:days[0];
section=dVal[3]?dVal[3]:sections[0];
result=full=`${year+'-'+month+'-'+day+' '+section}`;
obj={
year,
month,
day,
section
}
this.range=range;
this.checkObj=obj;
this.$nextTick(()=>{
this.pickVal=pickVal;
});
this.$emit("change",{
result:result,
value:full,
obj:obj
})
},
handlerChange(e){
let arr=[...e.detail.value];
let data=this.range;
let year="",month="",day="",section="";
let result="",full="",obj={};
let months=null,days=null,sections=null;
let disabledAfter=this.disabledAfter;
year=(arr[0]||arr[0]==0)?data.years[arr[0]]||data.years[data.years.length-1]:"";
month=(arr[1]||arr[1]==0)?data.months[arr[1]]||data.months[data.months.length-1]:"";
day=(arr[2]||arr[2]==0)?data.days[arr[2]]||data.days[data.days.length-1]:"";
section=(arr[3]||arr[3]==0)?data.sections[arr[3]]||data.sections[data.sections.length-1]:"";
result=full=`${year+'-'+month+'-'+day+' '+section}`;
let resetData=this.resetData(year,month,day);
if(this.disabledAfter){
months=resetData.months;
days=resetData.days;
sections=resetData.sections;
}else{
if(year%4==0||(month!=this.checkObj.month)){
days=resetData.days;
}
}
if(months)this.range.months=months;
if(days)this.range.days=days;
if(sections)this.range.sections=sections;
obj={
year,
month,
day,
section
}
this.checkObj=obj;
this.$emit("change",{
result:result,
value:full,
obj:obj
})
}
}
}
</script>
<style lang="scss">
@import "./w-picker.css";
</style>

View File

@ -0,0 +1,274 @@
<template>
<view class="w-picker-view">
<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column v-for="(group,gIndex) in range" :key="gIndex">
<view class="w-picker-item" v-for="(item,index) in group" :key="index">{{item[nodeKey]}}</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
export default {
data() {
return {
pickVal:[],
range:[],
checkObj:{}
};
},
props:{
itemHeight:{
type:String,
default:"44px"
},
value:{
type:[Array,String],
default:""
},
defaultType:{
type:String,
default:"label"
},
options:{
type:Array,
default(){
return []
}
},
defaultProps:{
type:Object,
default(){
return{
lable:"label",
value:"value",
children:"children"
}
}
},
level:{
//
type:[Number,String],
default:2
}
},
computed:{
nodeKey(){
return this.defaultProps.label;
},
nodeVal(){
return this.defaultProps.value;
},
nodeChild(){
return this.defaultProps.children;
}
},
watch:{
value(val){
if(this.options.length!=0){
this.initData();
}
},
options(val){
this.initData();
}
},
created() {
if(this.options.length!=0){
this.initData();
}
},
methods:{
getData(){
//
let options=this.options;
let col1={},col2={},col3={},col4={};
let arr1=options,arr2=[],arr3=[],arr4=[];
let col1Index=0,col2Index=0,col3Index=0,col4Index=0;
let a1="",a2="",a3="",a4="";
let dVal=[],obj={};
let value=this.value;
let data=[];
a1=value[0];
a2=value[1];
if(this.level>2){
a3=value[2];
}
if(this.level>3){
a4=value[3];
};
/*第1列*/
col1Index=arr1.findIndex((v)=>{
return v[this.defaultType]==a1
});
col1Index=value?(col1Index!=-1?col1Index:0):0;
col1=arr1[col1Index];
/*第2列*/
arr2=arr1[col1Index][this.nodeChild];
col2Index=arr2.findIndex((v)=>{
return v[this.defaultType]==a2
});
col2Index=value?(col2Index!=-1?col2Index:0):0;
col2=arr2[col2Index];
/*第3列*/
if(this.level>2){
arr3=arr2[col2Index][this.nodeChild];
col3Index=arr3.findIndex((v)=>{
return v[this.defaultType]==a3;
});
col3Index=value?(col3Index!=-1?col3Index:0):0;
col3=arr3[col3Index];
};
/*第4列*/
if(this.level>3){
arr4=arr3[col4Index][this.nodeChild];
col4Index=arr4.findIndex((v)=>{
return v[this.defaultType]==a4;
});
col4Index=value?(col4Index!=-1?col4Index:0):0;
col4=arr4[col4Index];
};
switch(this.level*1){
case 2:
dVal=[col1Index,col2Index];
obj={
col1,
col2
}
data=[arr1,arr2];
break;
case 3:
dVal=[col1Index,col2Index,col3Index];
obj={
col1,
col2,
col3
}
data=[arr1,arr2,arr3];
break;
case 4:
dVal=[col1Index,col2Index,col3Index,col4Index];
obj={
col1,
col2,
col3,
col4
}
data=[arr1,arr2,arr3,arr4];
break
}
return {
data,
dVal,
obj
}
},
initData(){
let dataData=this.getData();
let data=dataData.data;
let arr1=data[0];
let arr2=data[1];
let arr3=data[2]||[];
let arr4=data[3]||[];
let obj=dataData.obj;
let col1=obj.col1,col2=obj.col2,col3=obj.col3||{},col4=obj.col4||{};
let result="",value=[];
let range=[];
switch(this.level){
case 2:
value=[col1[this.nodeVal],col2[this.nodeVal]];
result=`${col1[this.nodeKey]+col2[this.nodeKey]}`;
range=[arr1,arr2];
break;
case 3:
value=[col1[this.nodeVal],col2[this.nodeVal],col3[this.nodeVal]];
result=`${col1[this.nodeKey]+col2[this.nodeKey]+col3[this.nodeKey]}`;
range=[arr1,arr2,arr3];
break;
case 4:
value=[col1[this.nodeVal],col2[this.nodeVal],col3[this.nodeVal],col4[this.nodeVal]];
result=`${col1[this.nodeKey]+col2[this.nodeKey]+col3[this.nodeKey]+col4[this.nodeKey]}`;
range=[arr1,arr2,arr3,arr4];
break;
}
this.range=range;
this.checkObj=obj;
this.$nextTick(()=>{
this.pickVal=dataData.dVal;
});
this.$emit("change",{
result:result,
value:value,
obj:obj
})
},
handlerChange(e){
let arr=[...e.detail.value];
let col1Index=arr[0],col2Index=arr[1],col3Index=arr[2]||0,col4Index=arr[3]||0;
let arr1=[],arr2=[],arr3=[],arr4=[];
let col1,col2,col3,col4,obj={};
let result="",value=[];
arr1=this.options;
arr2=(arr1[col1Index]&&arr1[col1Index][this.nodeChild])||arr1[arr1.length-1][this.nodeChild]||[];
col1=arr1[col1Index]||arr1[arr1.length-1]||{};
col2=arr2[col2Index]||arr2[arr2.length-1]||{};
if(this.level>2){
arr3=(arr2[col2Index]&&arr2[col2Index][this.nodeChild])||arr2[arr2.length-1][this.nodeChild];
col3=arr3[col3Index]||arr3[arr3.length-1]||{};
}
if(this.level>3){
arr4=(arr3[col3Index]&&arr3[col3Index][this.nodeChild])||arr3[arr3.length-1][this.nodeChild]||[];
col4=arr4[col4Index]||arr4[arr4.length-1]||{};
}
switch(this.level){
case 2:
obj={
col1,
col2
}
this.range=[arr1,arr2];
result=`${(col1[this.nodeKey]||'')+(col2[this.nodeKey]||'')}`;
value=[col1[this.nodeVal]||'',col2[this.nodeVal]||''];
break;
case 3:
obj={
col1,
col2,
col3
}
this.range=[arr1,arr2,arr3];
result=`${(col1[this.nodeKey]||'')+(col2[this.nodeKey]||'')+(col3[this.nodeKey]||'')}`;
value=[col1[this.nodeVal]||'',col2[this.nodeVal]||'',col3[this.nodeVal]||''];
break;
case 4:
obj={
col1,
col2,
col3,
col4
}
this.range=[arr1,arr2,arr3,arr4];
result=`${(col1[this.nodeKey]||'')+(col2[this.nodeKey]||'')+(col3[this.nodeKey]||'')+(col4[this.nodeKey]||'')}`;
value=[col1[this.nodeVal]||'',col2[this.nodeVal]||'',col3[this.nodeVal]||'',col4[this.nodeVal]||''];
break;
}
this.checkObj=obj;
this.pickVal=arr;
this.$emit("change",{
result:result,
value:value,
obj:obj
})
}
}
}
</script>
<style lang="scss">
@import "./w-picker.css";
</style>

View File

@ -0,0 +1,344 @@
<template>
<view class="w-picker-view">
<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column class="w-picker-flex2">
<view class="w-picker-item" v-for="(item,index) in range.fyears" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column class="w-picker-flex2">
<view class="w-picker-item" v-for="(item,index) in range.fmonths" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column class="w-picker-flex2">
<view class="w-picker-item" v-for="(item,index) in range.fdays" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column class="w-picker-flex1">
<view class="w-picker-item">-</view>
</picker-view-column>
<picker-view-column class="w-picker-flex2">
<view class="w-picker-item" v-for="(item,index) in range.tyears" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column class="w-picker-flex2">
<view class="w-picker-item" v-for="(item,index) in range.tmonths" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column class="w-picker-flex2">
<view class="w-picker-item" v-for="(item,index) in range.tdays" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
export default {
data() {
return {
pickVal:[],
range:{},
checkObj:{}
};
},
props:{
itemHeight:{
type:String,
default:"44px"
},
value:{
type:[String,Array],
default(){
return []
}
},
current:{//
type:Boolean,
default:false
},
startYear:{
type:[String,Number],
default:1970
},
endYear:{
type:[String,Number],
default:new Date().getFullYear()
}
},
watch:{
value(val){
this.initData();
}
},
created() {
this.initData();
},
methods:{
formatNum(n){
return (Number(n)<10?'0'+Number(n):Number(n)+'');
},
checkValue(value){
let strReg=/^\d{4}-\d{2}-\d{2}$/,example="2020-04-03";
if(!strReg.test(value[0])||!strReg.test(value[1])){
console.log(new Error("请传入与mode匹配的value值例["+example+","+example+"]"))
}
return strReg.test(value[0])&&strReg.test(value[1]);
},
resetToData(fmonth,fday,tyear,tmonth){
let range=this.range;
let tmonths=[],tdays=[];
let yearFlag=tyear!=range.tyears[0];
let monthFlag=tyear!=range.tyears[0]||tmonth!=range.tmonths[0];
let ttotal=new Date(tyear,tmonth,0).getDate();
for(let i=yearFlag?1:fmonth*1;i<=12;i++){
tmonths.push(this.formatNum(i))
}
for(let i=monthFlag?1:fday*1;i<=ttotal;i++){
tdays.push(this.formatNum(i))
}
return{
tmonths,
tdays
}
},
resetData(fyear,fmonth,fday,tyear,tmonth){
let fyears=[],fmonths=[],fdays=[],tyears=[],tmonths=[],tdays=[];
let startYear=this.startYear;
let endYear=this.endYear;
let ftotal=new Date(fyear,fmonth,0).getDate();
let ttotal=new Date(tyear,tmonth,0).getDate();
for(let i=startYear*1;i<=endYear;i++){
fyears.push(this.formatNum(i))
}
for(let i=1;i<=12;i++){
fmonths.push(this.formatNum(i))
}
for(let i=1;i<=ftotal;i++){
fdays.push(this.formatNum(i))
}
for(let i=fyear*1;i<=endYear;i++){
tyears.push(this.formatNum(i))
}
for(let i=fmonth*1;i<=12;i++){
tmonths.push(this.formatNum(i))
}
for(let i=fday*1;i<=ttotal;i++){
tdays.push(this.formatNum(i))
}
return {
fyears,
fmonths,
fdays,
tyears,
tmonths,
tdays
}
},
getData(dVal){
let start=this.startYear*1;
let end=this.endYear*1;
let value=dVal;
let flag=this.current;
let aToday=new Date();
let tYear,tMonth,tDay,tHours,tMinutes,tSeconds,pickVal=[];
let initstartDate=new Date(start.toString());
let endDate=new Date(end.toString());
if(start>end){
initstartDate=new Date(end.toString());
endDate=new Date(start.toString());
};
let startYear=initstartDate.getFullYear();
let startMonth=initstartDate.getMonth()+1;
let endYear=endDate.getFullYear();
let fyears=[],fmonths=[],fdays=[],tyears=[],tmonths=[],tdays=[],returnArr=[],startDVal=[],endDVal=[];
let curMonth=flag?value[1]*1:(startDVal[1]*1+1);
let curMonth1=flag?value[5][1]*1:(value[5]*1+1);
let totalDays=new Date(value[0],value[1],0).getDate();
let totalDays1=new Date(value[4],value[5],0).getDate();
for(let s=startYear;s<=endYear;s++){
fyears.push(this.formatNum(s));
};
for(let m=1;m<=12;m++){
fmonths.push(this.formatNum(m));
};
for(let d=1;d<=totalDays;d++){
fdays.push(this.formatNum(d));
};
for(let s=value[0]*1;s<=endYear;s++){
tyears.push(this.formatNum(s));
};
if(value[4]*1>value[0]*1){
for(let m=1;m<=12;m++){
tmonths.push(this.formatNum(m));
};
for(let d=1;d<=totalDays1;d++){
tdays.push(this.formatNum(d));
};
}else{
for(let m=value[1]*1;m<=12;m++){
tmonths.push(this.formatNum(m));
};
for(let d=value[2]*1;d<=totalDays1;d++){
tdays.push(this.formatNum(d));
};
};
pickVal=[
fyears.indexOf(value[0])==-1?0:fyears.indexOf(value[0]),
fmonths.indexOf(value[1])==-1?0:fmonths.indexOf(value[1]),
fdays.indexOf(value[2])==-1?0:fdays.indexOf(value[2]),
0,
tyears.indexOf(value[4])==-1?0:tyears.indexOf(value[4]),
tmonths.indexOf(value[5])==-1?0:tmonths.indexOf(value[5]),
tdays.indexOf(value[6])==-1?0:tdays.indexOf(value[6])
];
return {
fyears,
fmonths,
fdays,
tyears,
tmonths,
tdays,
pickVal
}
},
getDval(){
let value=this.value;
let fields=this.fields;
let dVal=null;
let aDate=new Date();
let fyear=this.formatNum(aDate.getFullYear());
let fmonth=this.formatNum(aDate.getMonth()+1);
let fday=this.formatNum(aDate.getDate());
let tyear=this.formatNum(aDate.getFullYear());
let tmonth=this.formatNum(aDate.getMonth()+1);
let tday=this.formatNum(aDate.getDate());
if(value&&value.length>0){
let flag=this.checkValue(value);
if(!flag){
dVal=[fyear,fmonth,fday,"-",tyear,tmonth,tday]
}else{
dVal=[...value[0].split("-"),"-",...value[1].split("-")];
}
}else{
dVal=[fyear,fmonth,fday,"-",tyear,tmonth,tday]
}
return dVal;
},
initData(){
let range=[],pickVal=[];
let result="",full="",obj={};
let dVal=this.getDval();
let dateData=this.getData(dVal);
let fyears=[],fmonths=[],fdays=[],tyears=[],tmonths=[],tdays=[];
let fyear,fmonth,fday,tyear,tmonth,tday;
pickVal=dateData.pickVal;
fyears=dateData.fyears;
fmonths=dateData.fmonths;
fdays=dateData.fdays;
tyears=dateData.tyears;
tmonths=dateData.tmonths;
tdays=dateData.tdays;
range={
fyears,
fmonths,
fdays,
tyears,
tmonths,
tdays,
}
fyear=range.fyears[pickVal[0]];
fmonth=range.fmonths[pickVal[1]];
fday=range.fdays[pickVal[2]];
tyear=range.tyears[pickVal[4]];
tmonth=range.tmonths[pickVal[5]];
tday=range.tdays[pickVal[6]];
obj={
fyear,
fmonth,
fday,
tyear,
tmonth,
tday
}
result=`${fyear+'-'+fmonth+'-'+fday+'至'+tyear+'-'+tmonth+'-'+tday}`;
this.range=range;
this.checkObj=obj;
this.$nextTick(()=>{
this.pickVal=pickVal;
});
this.$emit("change",{
result:result,
value:result.split("至"),
obj:obj
})
},
handlerChange(e){
let arr=[...e.detail.value];
let result="",full="",obj={};
let year="",month="",day="",hour="",minute="",second="",note=[],province,city,area;
let checkObj=this.checkObj;
let days=[],months=[],endYears=[],endMonths=[],endDays=[],startDays=[];
let mode=this.mode;
let col1,col2,col3,d,a,h,m;
let xDate=new Date().getTime();
let range=this.range;
let fyear=range.fyears[arr[0]]||range.fyears[range.fyears.length-1];
let fmonth=range.fmonths[arr[1]]||range.fmonths[range.fmonths.length-1];
let fday=range.fdays[arr[2]]||range.fdays[range.fdays.length-1];
let tyear=range.tyears[arr[4]]||range.tyears[range.tyears.length-1];
let tmonth=range.tmonths[arr[5]]||range.tmonths[range.tmonths.length-1];
let tday=range.tdays[arr[6]]||range.tdays[range.tdays.length-1];
let resetData=this.resetData(fyear,fmonth,fday,tyear,tmonth);
if(fyear!=checkObj.fyear||fmonth!=checkObj.fmonth||fday!=checkObj.fday){
arr[4]=0;
arr[5]=0;
arr[6]=0;
range.tyears=resetData.tyears;
range.tmonths=resetData.tmonths;
range.tdays=resetData.tdays;
tyear=range.tyears[0];
checkObj.tyears=range.tyears[0];
tmonth=range.tmonths[0];
checkObj.tmonths=range.tmonths[0];
tday=range.tdays[0];
checkObj.tdays=range.tdays[0];
}
if(fyear!=checkObj.fyear||fmonth!=checkObj.fmonth){
range.fdays=resetData.fdays;
};
if(tyear!=checkObj.tyear){
arr[5]=0;
arr[6]=0;
let toData=this.resetToData(fmonth,fday,tyear,tmonth);
range.tmonths=toData.tmonths;
range.tdays=toData.tdays;
tmonth=range.tmonths[0];
checkObj.tmonths=range.tmonths[0];
tday=range.tdays[0];
checkObj.tdays=range.tdays[0];
};
if(tmonth!=checkObj.tmonth){
arr[6]=0;
let toData=this.resetToData(fmonth,fday,tyear,tmonth);
range.tdays=toData.tdays;
tday=range.tdays[0];
checkObj.tdays=range.tdays[0];
};
result=`${fyear+'-'+fmonth+'-'+fday+'至'+tyear+'-'+tmonth+'-'+tday}`;
obj={
fyear,fmonth,fday,tyear,tmonth,tday
}
this.checkObj=obj;
this.$nextTick(()=>{
this.pickVal=arr;
})
this.$emit("change",{
result:result,
value:result.split("至"),
obj:obj
})
}
}
}
</script>
<style lang="scss">
@import "./w-picker.css";
</style>

View File

@ -0,0 +1,183 @@
<template>
<view class="w-picker-view">
<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.provinces" :key="index">{{item.label}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.citys" :key="index">{{item.label}}</view>
</picker-view-column>
<picker-view-column v-if="!hideArea">
<view class="w-picker-item" v-for="(item,index) in range.areas" :key="index">{{item.label}}</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
import areaData from "./areadata/areadata.js"
export default {
data() {
return {
pickVal:[],
range:{
provinces:[],
citys:[],
areas:[]
},
checkObj:{}
};
},
props:{
itemHeight:{
type:String,
default:"44px"
},
value:{
type:[Array,String],
default:""
},
defaultType:{
type:String,
default:"label"
},
hideArea:{
type:Boolean,
default:false
}
},
watch:{
value(val){
this.initData();
}
},
created() {
this.initData();
},
methods:{
getData(){
//
let provinces=areaData;
let dVal=[];
let value=this.value;
let a1=value[0];//
let a2=value[1];//
let a3=value[2];//
let province,city,area;
let provinceIndex=provinces.findIndex((v)=>{
return v[this.defaultType]==a1
});
provinceIndex=value?(provinceIndex!=-1?provinceIndex:0):0;
let citys=provinces[provinceIndex].children;
let cityIndex=citys.findIndex((v)=>{
return v[this.defaultType]==a2
});
cityIndex=value?(cityIndex!=-1?cityIndex:0):0;
let areas=citys[cityIndex].children;
let areaIndex=areas.findIndex((v)=>{
return v[this.defaultType]==a3;
});
areaIndex=value?(areaIndex!=-1?areaIndex:0):0;
dVal=this.hideArea?[provinceIndex,cityIndex]:[provinceIndex,cityIndex,areaIndex];
province=provinces[provinceIndex];
city=citys[cityIndex];
area=areas[areaIndex];
let obj=this.hideArea?{
province,
city
}:{
province,
city,
area
}
return this.hideArea?{
provinces,
citys,
dVal,
obj
}:{
provinces,
citys,
areas,
dVal,
obj
}
},
initData(){
let dataData=this.getData();
let provinces=dataData.provinces;
let citys=dataData.citys;
let areas=this.hideArea?[]:dataData.areas;
let obj=dataData.obj;
let province=obj.province,city=obj.city,area=this.hideArea?{}:obj.area;
let value=this.hideArea?[province.value,city.value]:[province.value,city.value,area.value];
let result=this.hideArea?`${province.label+city.label}`:`${province.label+city.label+area.label}`;
this.range=this.hideArea?{
provinces,
citys,
}:{
provinces,
citys,
areas
};
this.checkObj=obj;
this.$nextTick(()=>{
this.pickVal=dataData.dVal;
});
this.$emit("change",{
result:result,
value:value,
obj:obj
})
},
handlerChange(e){
let arr=[...e.detail.value];
let provinceIndex=arr[0],cityIndex=arr[1],areaIndex=this.hideArea?0:arr[2];
let provinces=areaData;
let citys=(provinces[provinceIndex]&&provinces[provinceIndex].children)||provinces[provinces.length-1].children||[];
let areas=this.hideArea?[]:((citys[cityIndex]&&citys[cityIndex].children)||citys[citys.length-1].children||[]);
let province=provinces[provinceIndex]||provinces[provinces.length-1],
city=citys[cityIndex]||[citys.length-1],
area=this.hideArea?{}:(areas[areaIndex]||[areas.length-1]);
let obj=this.hideArea?{
province,
city
}:{
province,
city,
area
}
if(this.checkObj.province.label!=province.label){
//;
this.range.citys=citys;
if(!this.hideArea){
this.range.areas=areas;
}
}
if(this.checkObj.city.label!=city.label){
//;
if(!this.hideArea){
this.range.areas=areas;
}
}
this.checkObj=obj;
this.$nextTick(()=>{
this.pickVal=arr;
})
let result=this.hideArea?`${province.label+city.label}`:`${province.label+city.label+area.label}`;
let value=this.hideArea?[province.value,city.value]:[province.value,city.value,area.value];
this.$emit("change",{
result:result,
value:value,
obj:obj
})
}
}
}
</script>
<style lang="scss">
@import "./w-picker.css";
</style>

View File

@ -0,0 +1,129 @@
<template>
<view class="w-picker-view">
<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range" :key="index">{{item[nodeKey]}}</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
export default {
props:{
itemHeight:{
type:String,
default:"44px"
},
options:{
type:[Array,Object],
default(){
return []
}
},
value:{
type:String,
default:""
},
defaultType:{
type:String,
default:"label"
},
defaultProps:{
type:Object,
default(){
return{
label:"label",
value:"value"
}
}
}
},
data() {
return {
pickVal:[]
};
},
computed:{
nodeKey(){
return this.defaultProps.label;
},
nodeValue(){
return this.defaultProps.value;
},
range(){
return this.options
}
},
watch:{
value(val){
if(this.options.length!=0){
this.initData();
}
},
options(val){
this.initData();
}
},
created() {
if(this.options.length!=0){
this.initData();
}
},
methods:{
initData(){
let dVal=this.value||"";
let data=this.range;
let pickVal=[0];
let cur=null;
let label="";
let value,idx;
if(this.defaultType==this.nodeValue){
value=data.find((v)=>v[this.nodeValue]==dVal);
idx=data.findIndex((v)=>v[this.nodeValue]==dVal);
}else{
value=data.find((v)=>v[this.nodeKey]==dVal);
idx=data.findIndex((v)=>v[this.nodeKey]==dVal);
}
pickVal=[idx!=-1?idx:0];
this.$nextTick(()=>{
this.pickVal=pickVal;
});
if(this.defaultType==this.nodeValue){
this.$emit("change",{
result:value?value[this.nodeKey]:data[0][this.nodeKey],
value:dVal||data[0][this.nodeKey],
obj:value?value:data[0]
})
}else{
this.$emit("change",{
result:dVal||data[0][this.nodeKey],
value:value?value[this.nodeValue]:data[0][this.nodeValue],
obj:value?value:data[0]
})
}
},
handlerChange(e){
let arr=[...e.detail.value];
let pickVal=[arr[0]||0];
let data=this.range;
let cur=data[arr[0]];
let label="";
let value="";
this.$nextTick(()=>{
this.pickVal=pickVal;
});
this.$emit("change",{
result:cur[this.nodeKey],
value:cur[this.nodeValue],
obj:cur
})
}
}
}
</script>
<style lang="scss">
@import "./w-picker.css";
</style>

View File

@ -0,0 +1,250 @@
<template>
<view class="w-picker-view">
<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.dates" :key="index">{{item.label}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item.label}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item.label}}</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
export default {
data() {
return {
pickVal:[],
range:{},
checkObj:{}
};
},
props:{
itemHeight:{
type:String,
default:"44px"
},
value:{
type:[String,Array,Number],
default:""
},
current:{//
type:Boolean,
default:false
},
expand:{
type:[Number,String],
default:30
}
},
watch:{
value(val){
this.initData();
}
},
created() {
this.initData();
},
methods:{
formatNum(n){
return (Number(n)<10?'0'+Number(n):Number(n)+'');
},
checkValue(value){
let strReg=/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2})?$/,example="2019-12-12 18:05:00或者2019-12-12 18:05";
if(!strReg.test(value)){
console.log(new Error("请传入与mode、fields匹配的value值例value="+example+""))
}
return strReg.test(value);
},
resetData(year,month,day){
let curDate=this.getCurrenDate();
let curFlag=this.current;
let curYear=curDate.curYear;
let curMonth=curDate.curMonth;
let curDay=curDate.curDay;
let curHour=curDate.curHour;
let months=[],days=[],sections=[];
let disabledAfter=this.disabledAfter;
let monthsLen=disabledAfter?(year*1<curYear?12:curMonth):12;
let totalDays=new Date(year,month,0).getDate();//;
for(let month=1;month<=monthsLen;month++){
months.push(this.formatNum(month));
};
for(let day=1;day<=daysLen;day++){
days.push(this.formatNum(day));
}
return{
months,
days,
sections
}
},
getData(dVal){
//
let curFlag=this.current;
let disabledAfter=this.disabledAfter;
let dates=[],hours=[],minutes=[];
let curDate=new Date();
let curYear=curDate.getFullYear();
let curMonth=curDate.getMonth();
let curDay=curDate.getDate();
let aDate=new Date(curYear,curMonth,curDay);
for(let i=0;i<this.expand*1;i++){
aDate=new Date(curYear,curMonth,curDay+i);
let year=aDate.getFullYear();
let month=aDate.getMonth()+1;
let day=aDate.getDate();
let label=year+"-"+this.formatNum(month)+"-"+this.formatNum(day);
switch(i){
case 0:
label="今天";
break;
case 1:
label="明天";
break;
case 2:
label="后天";
break
}
dates.push({
label:label,
value:year+"-"+this.formatNum(month)+"-"+this.formatNum(day)
})
};
for(let i=0;i<24;i++){
hours.push({
label:this.formatNum(i),
value:this.formatNum(i)
})
}
for(let i=0;i<60;i++){
minutes.push({
label:this.formatNum(i),
value:this.formatNum(i)
})
}
return {
dates,
hours,
minutes
}
},
getDefaultDate(){
let value=this.value;
let reg=/-/g;
let defaultDate=value?new Date(value.replace(reg,"/")):new Date();
let defaultYear=defaultDate.getFullYear();
let defaultMonth=defaultDate.getMonth()+1;
let defaultDay=defaultDate.getDate();
let defaultDays=new Date(defaultYear,defaultMonth,0).getDate()*1;
return{
defaultDate,
defaultYear,
defaultMonth,
defaultDay,
defaultDays
}
},
getDval(){
let value=this.value;
let dVal=null;
let aDate=new Date();
let year=this.formatNum(aDate.getFullYear());
let month=this.formatNum(aDate.getMonth()+1);
let day=this.formatNum(aDate.getDate());
let date=this.formatNum(year)+"-"+this.formatNum(month)+"-"+this.formatNum(day);
let hour=aDate.getHours();
let minute=aDate.getMinutes();
if(value){
let flag=this.checkValue(value);
if(!flag){
dVal=[date,hour,minute]
}else{
let v=value.split(" ");
dVal=[v[0],...v[1].split(":")];
}
}else{
dVal=[date,hour,minute]
}
return dVal;
},
initData(){
let startDate,endDate,startYear,endYear,startMonth,endMonth,startDay,endDay;
let dates=[],hours=[],minutes=[];
let dVal=[],pickVal=[];
let value=this.value;
let reg=/-/g;
let range={};
let result="",full="",date,hour,minute,obj={};
let defaultDate=this.getDefaultDate();
let defaultYear=defaultDate.defaultYear;
let defaultMonth=defaultDate.defaultMonth;
let defaultDay=defaultDate.defaultDay;
let defaultDays=defaultDate.defaultDays;
let curFlag=this.current;
let disabledAfter=this.disabledAfter;
let dateData=[];
dVal=this.getDval();
dateData=this.getData(dVal);
dates=dateData.dates;
hours=dateData.hours;
minutes=dateData.minutes;
pickVal=[
dates.findIndex(n => n.value == dVal[0])!=-1?dates.findIndex(n => n.value == dVal[0]):0,
hours.findIndex(n => n.value == dVal[1])!=-1?hours.findIndex(n => n.value == dVal[1]):0,
minutes.findIndex(n => n.value == dVal[2])!=-1?minutes.findIndex(n => n.value == dVal[2]):0,
];
range={dates,hours,minutes};
date=dVal[0]?dVal[0]:dates[0].label;
hour=dVal[1]?dVal[1]:hours[0].label;
minute=dVal[2]?dVal[2]:minutes[0].label;
result=full=`${date+' '+hour+':'+minute}`;
obj={
date,
hour,
minute
}
this.range=range;
this.checkObj=obj;
this.$nextTick(()=>{
this.pickVal=pickVal;
});
this.$emit("change",{
result:result,
value:full,
obj:obj
})
},
handlerChange(e){
let arr=[...e.detail.value];
let data=this.range;
let date="",hour="",minute="";
let result="",full="",obj={};
let disabledAfter=this.disabledAfter;
date=(arr[0]||arr[0]==0)?data.dates[arr[0]]||data.dates[data.dates.length-1]:"";
hour=(arr[1]||arr[1]==0)?data.hours[arr[1]]||data.hours[data.hours.length-1]:"";
minute=(arr[2]||arr[2]==0)?data.minutes[arr[2]]||data.minutes[data.minutes.length-1]:"";
result=full=`${date.label+' '+hour.label+':'+minute.label+':00'}`;
obj={
date,
hour,
minute
}
this.checkObj=obj;
this.$emit("change",{
result:result,
value:full,
obj:obj
})
}
}
}
</script>
<style lang="scss">
@import "./w-picker.css";
</style>

View File

@ -0,0 +1,218 @@
<template>
<view class="w-picker-view">
<picker-view class="d-picker-view" :indicator-style="itemHeight" :value="pickVal" @change="handlerChange">
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.hours" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column>
<view class="w-picker-item" v-for="(item,index) in range.minutes" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column v-if="second">
<view class="w-picker-item" v-for="(item,index) in range.seconds" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
export default {
data() {
return {
pickVal:[],
range:{},
checkObj:{}
};
},
props:{
itemHeight:{
type:String,
default:"44px"
},
value:{
type:[String,Array,Number],
default:""
},
current:{//
type:Boolean,
default:false
},
second:{
type:Boolean,
default:true
}
},
watch:{
value(val){
this.initData();
}
},
created() {
this.initData();
},
methods:{
formatNum(n){
return (Number(n)<10?'0'+Number(n):Number(n)+'');
},
checkValue(value){
let strReg=/^\d{2}:\d{2}:\d{2}$/,example="18:00:05";
if(!strReg.test(value)){
console.log(new Error("请传入与mode、fields匹配的value值例value="+example+""))
}
return strReg.test(value);
},
resetData(year,month,day,hour,minute){
let curDate=this.getCurrenDate();
let curFlag=this.current;
let curHour=curDate.curHour;
let curMinute=curDate.curMinute;
let curSecond=curDate.curSecond;
for(let hour=0;hour<24;hour++){
hours.push(this.formatNum(hour));
}
for(let minute=0;minute<60;minute++){
minutes.push(this.formatNum(minute));
}
for(let second=0;second<60;second++){
seconds.push(this.formatNum(second));
}
return{
hours,
minutes,
seconds
}
},
getData(curDate){
//
let hours=[],minutes=[],seconds=[];
let curFlag=this.current;
let disabledAfter=this.disabledAfter;
let fields=this.fields;
let curHour=curDate.curHour;
let curMinute=curDate.curMinute;
let curSecond=curDate.curSecond;
for(let hour=0;hour<24;hour++){
hours.push(this.formatNum(hour));
}
for(let minute=0;minute<60;minute++){
minutes.push(this.formatNum(minute));
}
for(let second=0;second<60;second++){
seconds.push(this.formatNum(second));
}
return this.second?{
hours,
minutes,
seconds
}:{
hours,
minutes
}
},
getCurrenDate(){
let curDate=new Date();
let curHour=curDate.getHours();
let curMinute=curDate.getMinutes();
let curSecond=curDate.getSeconds();
return this.second?{
curHour,
curMinute,
curSecond
}:{
curHour,
curMinute,
}
},
getDval(){
let value=this.value;
let fields=this.fields;
let dVal=null;
let aDate=new Date();
let hour=this.formatNum(aDate.getHours());
let minute=this.formatNum(aDate.getMinutes());
let second=this.formatNum(aDate.getSeconds());
if(value){
let flag=this.checkValue(value);
if(!flag){
dVal=[hour,minute,second]
}else{
dVal=value?value.split(":"):[];
}
}else{
dVal=this.second?[hour,minute,second]:[hour,minute]
}
return dVal;
},
initData(){
let curDate=this.getCurrenDate();
let dateData=this.getData(curDate);
let pickVal=[],obj={},full="",result="",hour="",minute="",second="";
let dVal=this.getDval();
let curFlag=this.current;
let disabledAfter=this.disabledAfter;
let hours=dateData.hours;
let minutes=dateData.minutes;
let seconds=dateData.seconds;
let defaultArr=this.second?[
dVal[0]&&hours.indexOf(dVal[0])!=-1?hours.indexOf(dVal[0]):0,
dVal[1]&&minutes.indexOf(dVal[1])!=-1?minutes.indexOf(dVal[1]):0,
dVal[2]&&seconds.indexOf(dVal[2])!=-1?seconds.indexOf(dVal[2]):0
]:[
dVal[0]&&hours.indexOf(dVal[0])!=-1?hours.indexOf(dVal[0]):0,
dVal[1]&&minutes.indexOf(dVal[1])!=-1?minutes.indexOf(dVal[1]):0
];
pickVal=disabledAfter?defaultArr:(curFlag?(this.second?[
hours.indexOf(this.formatNum(curDate.curHour)),
minutes.indexOf(this.formatNum(curDate.curMinute)),
seconds.indexOf(this.formatNum(curDate.curSecond)),
]:[
hours.indexOf(this.formatNum(curDate.curHour)),
minutes.indexOf(this.formatNum(curDate.curMinute))
]):defaultArr);
this.range=dateData;
this.checkObj=obj;
hour=dVal[0]?dVal[0]:hours[0];
minute=dVal[1]?dVal[1]:minutes[0];
if(this.second)second=dVal[2]?dVal[0]:seconds[0];
result=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute}`;
full=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute+':00'}`;
this.$nextTick(()=>{
this.pickVal=pickVal;
});
this.$emit("change",{
result:result,
value:full,
obj:obj
})
},
handlerChange(e){
let arr=[...e.detail.value];
let data=this.range;
let hour="",minute="",second="",result="",full="",obj={};
hour=(arr[0]||arr[0]==0)?data.hours[arr[0]]||data.hours[data.hours.length-1]:"";
minute=(arr[1]||arr[1]==0)?data.minutes[arr[1]]||data.minutes[data.minutes.length-1]:"";
if(this.second)second=(arr[2]||arr[2]==0)?data.seconds[arr[2]]||data.seconds[data.seconds.length-1]:"";
obj=this.second?{
hour,
minute,
second
}:{
hour,
minute
};
this.checkObj=obj;
result=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute}`;
full=this.second?`${hour+':'+minute+':'+second}`:`${hour+':'+minute+':00'}`;
this.$emit("change",{
result:result,
value:full,
obj:obj
})
}
}
}
</script>
<style lang="scss">
@import "./w-picker.css";
</style>

View File

@ -0,0 +1,26 @@
.w-picker-flex2{
flex:2;
}
.w-picker-flex1{
flex:1;
}
.w-picker-view {
width: 100%;
height: 476upx;
overflow: hidden;
background-color: rgba(255, 255, 255, 1);
z-index: 666;
}
.d-picker-view{
height: 100%;
}
.w-picker-item {
text-align: center;
width: 100%;
height: 88upx;
line-height: 88upx;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 30upx;
}

View File

@ -0,0 +1,340 @@
<template name="w-picker">
<view class="w-picker" :key="createKey" :data-key="createKey">
<view class="mask" :class="{'visible':visible}" @tap="onCancel" @touchmove.stop.prevent catchtouchmove="true"></view>
<view class="w-picker-cnt" :class="{'visible':visible}">
<view class="w-picker-header" @touchmove.stop.prevent catchtouchmove="true">
<text @tap.stop.prevent="onCancel">取消</text>
<slot></slot>
<text :style="{'color':themeColor}" @tap.stop.prevent="pickerConfirm">确定</text>
</view>
<date-picker
v-if="mode=='date'"
class="w-picker-wrapper"
:startYear="startYear"
:endYear="endYear"
:value="value"
:fields="fields"
:item-height="itemHeight"
:current="current"
:disabled-after="disabledAfter"
@change="handlerChange"
@touchstart="touchStart"
@touchend="touchEnd">
</date-picker>
<range-picker
v-if="mode=='range'"
class="w-picker-wrapper"
:startYear="startYear"
:endYear="endYear"
:value="value"
:item-height="itemHeight"
:current="current"
@change="handlerChange"
@touchstart="touchStart"
@touchend="touchEnd">
</range-picker>
<half-picker
v-if="mode=='half'"
class="w-picker-wrapper"
:startYear="startYear"
:endYear="endYear"
:value="value"
:item-height="itemHeight"
:current="current"
:disabled-after="disabledAfter"
@change="handlerChange"
@touchstart="touchStart"
@touchend="touchEnd">
</half-picker>
<shortterm-picker
v-if="mode=='shortTerm'"
class="w-picker-wrapper"
:startYear="startYear"
:endYear="endYear"
:value="value"
:item-height="itemHeight"
:current="current"
expand="60"
:disabled-after="disabledAfter"
@change="handlerChange"
@touchstart="touchStart"
@touchend="touchEnd">
</shortterm-picker>
<time-picker
v-if="mode=='time'"
class="w-picker-wrapper"
:value="value"
:item-height="itemHeight"
:current="current"
:disabled-after="disabledAfter"
:second="second"
@change="handlerChange"
@touchstart="touchStart"
@touchend="touchEnd">
</time-picker>
<selector-picker
v-if="mode=='selector'"
class="w-picker-wrapper"
:value="value"
:item-height="itemHeight"
:options="options"
:default-type="defaultType"
:default-props="defaultProps"
@change="handlerChange"
@touchstart="touchStart"
@touchend="touchEnd">
</selector-picker>
<region-picker
v-if="mode=='region'"
class="w-picker-wrapper"
:value="value"
:hide-area="hideArea"
:default-type="defaultType"
:item-height="itemHeight"
@change="handlerChange"
@touchstart="touchStart"
@touchend="touchEnd">
</region-picker>
<linkage-picker
v-if="mode=='linkage'"
class="w-picker-wrapper"
:value="value"
:options="options"
:level="level"
:default-type="defaultType"
:default-props="defaultProps"
:item-height="itemHeight"
@change="handlerChange"
@touchstart="touchStart"
@touchend="touchEnd">
</linkage-picker>
</view>
</view>
</template>
<script>
import datePicker from "./date-picker.vue"
import rangePicker from "./range-picker.vue"
import halfPicker from "./half-picker.vue"
import shorttermPicker from "./shortterm-picker.vue"
import timePicker from "./time-picker.vue"
import selectorPicker from "./selector-picker.vue"
import regionPicker from "./region-picker.vue"
import linkagePicker from "./linkage-picker.vue"
export default {
name:"w-picker",
components:{
datePicker,
rangePicker,
halfPicker,
timePicker,
selectorPicker,
shorttermPicker,
regionPicker,
linkagePicker
},
props:{
mode:{
type:String,
default:"date"
},
value:{//
type:[String,Array,Number],
default:""
},
current:{//
type:Boolean,
default:false
},
themeColor:{//
type:String,
default:"#f5a200"
},
fields:{//:yearmonthdayhourminutesecond
type:String,
default:"date"
},
disabledAfter:{//
type:Boolean,
default:false
},
second:{//time-picker
type:Boolean,
default:true
},
options:{//selector,region
type:[Array,Object],
default(){
return []
}
},
defaultProps:{//selector,linkagle
type:Object,
default(){
return{
label:"label",
value:"value",
children:"children"
}
}
},
defaultType:{
type:String,
default:"label"
},
hideArea:{//mode=region
type:Boolean,
default:false
},
level:{
//,2-4;
type:[Number,String],
default:2
},
timeout:{//,
type:Boolean,
default:false
},
expand:{//mode=shortterm
type:[Number,String],
default:30
},
startYear:{
type:[String,Number],
default:1970
},
endYear:{
type:[String,Number],
default:new Date().getFullYear()
},
visible:{
type:Boolean,
default:false
}
},
created() {
this.createKey=Math.random()*1000;
},
data() {
return {
itemHeight:`height: ${uni.upx2px(88)}px;`,
result:{},
confirmFlag:true
};
},
methods:{
touchStart(){
if(this.timeout){
this.confirmFlag=false;
}
},
touchEnd(){
if(this.timeout){
setTimeout(()=>{
this.confirmFlag=true;
},500)
}
},
handlerChange(res){
let _this=this;
this.result={...res};
},
show(){
this.$emit("update:visible",true);
},
hide(){
this.$emit("update:visible",false);
},
onCancel(res){
this.$emit("update:visible",false);
this.$emit("cancel");
},
pickerConfirm(){
if(!this.confirmFlag){
return;
};
this.$emit("confirm",this.result);
this.$emit("update:visible",false);
}
}
}
</script>
<style lang="scss">
.w-picker-item {
text-align: center;
width: 100%;
height: 88upx;
line-height: 88upx;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 30upx;
}
.w-picker{
z-index: 888;
.mask {
position: fixed;
z-index: 1000;
top: 0;
right: 0;
left: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
visibility: hidden;
opacity: 0;
transition: all 0.3s ease;
}
.mask.visible{
visibility: visible;
opacity: 1;
}
.w-picker-cnt {
position: fixed;
bottom: 0;
left: 0;
width: 100%;
transition: all 0.3s ease;
transform: translateY(100%);
z-index: 3000;
background-color: #fff;
}
.w-picker-cnt.visible {
transform: translateY(0);
}
.w-picker-header{
display: flex;
align-items: center;
padding: 0 30upx;
height: 88upx;
background-color: #fff;
position: relative;
text-align: center;
font-size: 32upx;
justify-content: space-between;
border-bottom: solid 1px #eee;
.w-picker-btn{
font-size: 30upx;
}
}
.w-picker-hd:after {
content: ' ';
position: absolute;
left: 0;
bottom: 0;
right: 0;
height: 1px;
border-bottom: 1px solid #e5e5e5;
color: #e5e5e5;
transform-origin: 0 100%;
transform: scaleY(0.5);
}
}
</style>

183
components/wfalls-flow.vue Normal file
View File

@ -0,0 +1,183 @@
<template>
<view class="wf-list-container">
<view id="wf-list" class="wf-list" v-for="(list,listIndex) of viewList" :key="listIndex">
<view @tap="goDetail(listIndex,index)" :data-id="item.id" class="wf-item-child rel"
v-for="(item,index) of list.list" :key="index">
<view class="examin-btn flex-center f-icontext c-base radius abs"
:style="{background:item.status==1?'#FC8218':'#FF6262'}" v-if="path==2&& item.status!=2">
{{item.status==1?'审核中':'已驳回'}}
</view>
<image mode="widthFix" class="cover" :id="'id'+item.id" @load="handleViewRender"
@error="handleViewRender" :src="item.cover"></image>
<view class="play-video-info flex-center c-base abs" v-if="item.type == 2">
<view class="play-video flex-center c-base radius">
<i class="iconfont icon-play-video"></i>
</view>
</view>
<view class="wf-item">
<view class="f-desc c-black text-bold">{{item.title}}</view>
<view class="flex-between mt-sm">
<view class="flex-center">
<image mode="aspectFill" class="avatar" :src="item.work_img"></image>
<view class="coach f-caption c-desc ellipsis">{{item.coach_name}}</view>
</view>
<view class="flex-y-baseline f-caption c-desc"> <i class="iconfont iconjuli"
:style="{color:primaryColor}"></i>
{{item.distance}}
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import {
mapState,
mapActions
} from 'vuex';
export default {
props: {
list: {
type: Array, //
},
path: {
type: String || Number, // 12-
}
},
data() {
return {
viewList: [{
list: []
}, {
list: []
}],
//
everyNum: 2
}
},
mounted() {
if (this.list.length) {
this.init()
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
have: state => state.config.configInfo.subColor,
}),
methods: {
...mapActions(),
init() {
this.viewList = [{
list: []
}, {
list: []
}];
setTimeout(() => {
this.handleViewRender(0, 0)
}, 600)
},
handleViewRender(x, y) {
const index = this.viewList.reduce((total, current) => total + current.list.length, 0)
if (index > this.list.length - 1) {
//
this.$emit('finishLoad', index)
return
};
const query = uni.createSelectorQuery().in(this);
let listFlag = 0;
query.selectAll('#wf-list').boundingClientRect(data => {
listFlag = data[0].bottom - data[1].bottom <= 0 ? 0 : 1;
this.viewList[listFlag].list.push(this.list[index])
}).exec()
},
async goDetail(a, b) {
let {
id
} = this.viewList[a].list[b]
let {
path
} = this
let page_url = path == 1 ? `` : `technician/`
let url = `/dynamic/pages/${page_url}detail?id=${id}`
this.$util.goUrl({
url,
})
}
}
}
</script>
<style lang="scss">
.wf-list-container {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 0 20rpx;
}
.wf-list {
width: calc(50% - 10rpx);
display: flex;
flex-direction: column;
}
.wf-item-child {
background: white;
margin-bottom: 20rpx;
border-radius: 16rpx;
overflow: hidden;
.examin-btn {
top: 20rpx;
left: 15rpx;
width: 89rpx;
height: 37rpx;
z-index: 1;
}
.play-video-info {
top: 0rpx;
width: 100%;
height: calc(100% - 128rpx);
z-index: 9;
.play-video {
width: 66rpx;
height: 66rpx;
background: rgba(2, 2, 2, 0.5);
.iconfont {
font-size: 28rpx;
}
}
}
}
.cover {
width: 100%;
height: 100rpx;
}
.wf-item {
padding: 20rpx;
.avatar {
width: 40rpx;
height: 40rpx;
border-radius: 40rpx;
margin-right: 6rpx;
}
.coach {
max-width: 100rpx;
}
.iconfont {
font-size: 24rpx;
}
}
</style>

View File

@ -0,0 +1,280 @@
<template>
<view class="xt__verify-code">
<!-- 输入框 -->
<input id="xt__input" :value="code" class="xt__input" :focus="isFocus" :password="isPassword" :type="inputType"
:maxlength="itemSize" @input="input" @focus="inputFocus" @blur="inputBlur" />
<!-- 光标 -->
<view id="xt__cursor" v-if="cursorVisible && type !== 'middle'" class="xt__cursor"
:style="{ left: codeCursorLeft[code.length] + 'px', height: cursorHeight + 'px', backgroundColor: cursorColor }">
</view>
<!-- 输入框 - -->
<view id="xt__input-ground" class="xt__input-ground">
<template v-for="(item, index) in itemSize">
<view :key="index"
:style="{ borderColor: code.length === index && cursorVisible ? boxActiveColor : boxNormalColor }"
:class="['xt__box', `xt__box-${type + ''}`, `xt__box::after`]">
<view :style="{ borderColor: boxActiveColor }" class="xt__middle-line"
v-if="type === 'middle' && !code[index]"></view>
<text class="xt__code-text">{{ code[index] | codeFormat(isPassword) }}</text>
</view>
</template>
</view>
</view>
</template>
<script>
/**
* @description 输入验证码组件
* @property {string} type = [box|middle|bottom] - 显示类型 默认box -eg:bottom
* @property {string} inputType = [text|number] - 输入框类型 默认number -eg:number
* @property {number} size = [1|2|3|4|5|6] - 支持的验证码数量 默认6 -eg:6
* @property {boolean} isFocus - 是否立即聚焦 默认true
* @property {boolean} isPassword - 是否以密码形式显示 默认false -eg:false
* @property {string} cursorColor - 光标颜色 默认#cccccc
* @property {string} boxNormalColor - 光标未聚焦到的框的颜色 默认#f7f7f7
* @property {string} boxActiveColor - 光标聚焦到的框的颜色 默认#009254
* @event {Function(data)} confirm - 输入完成
*/
export default {
name: 'xt-verify-code',
props: {
value: {
type: String,
default: () => ''
},
type: {
type: String,
default: () => 'box'
},
inputType: {
type: String,
default: () => 'number'
},
size: {
type: Number,
default: () => 6
},
isFocus: {
type: Boolean,
default: () => true
},
isPassword: {
type: Boolean,
default: () => false
},
cursorColor: {
type: String,
default: () => '#cccccc'
},
boxNormalColor: {
type: String,
default: () => '#f7f7f7'
},
boxActiveColor: {
type: String,
default: () => '#009254'
}
},
model: {
prop: 'value',
event: 'input'
},
data() {
return {
cursorVisible: false,
cursorHeight: 35,
code: '', //
codeCursorLeft: [], // ,
itemSize: 6
};
},
created() {
this.cursorVisible = this.isFocus;
this.validatorSize();
},
mounted() {
this.init();
},
methods: {
/**
*
*/
validatorSize() {
if (this.size <= 6 && this.size > 0) {
this.itemSize = Math.floor(this.size);
} else {
this.itemSize = 6;
}
},
/**
* @description 初始化
*/
init() {
this.getCodeCursorLeft();
this.setCursorHeight();
},
/**
* @description 获取元素节点
* @param {string} elm - 节点的idclass 相当于 document.querySelect的参数 -eg: #id
* @param {string} type = [single|array] - 单个元素获取多个元素 默认是单个元素
* @param {Function} callback - 回调函数
*/
getElement(elm, type = 'single', callback) {
uni
.createSelectorQuery()
.in(this)[type === 'array' ? 'selectAll' : 'select'](elm)
.boundingClientRect()
.exec(data => {
callback(data[0]);
});
},
/**
* @description 计算光标的高度
*/
setCursorHeight() {
this.getElement('.xt__box', 'single', boxElm => {
this.cursorHeight = boxElm.height * 0.6;
});
},
/**
* @description 获取光标在每一个box的left位置
*/
getCodeCursorLeft() {
//
this.getElement('#xt__input-ground', 'single', parentElm => {
const parentLeft = parentElm.left;
// box
this.getElement('.xt__box', 'array', elms => {
this.codeCursorLeft = [];
elms.forEach(elm => {
this.codeCursorLeft.push(elm.left - parentLeft + elm.width / 2);
});
});
});
},
//
input(e) {
const value = e.detail.value;
this.cursorVisible = value.length !== this.itemSize;
this.$emit('input', value);
this.inputSuccess(value);
},
//
inputSuccess(value) {
if (value.length === this.itemSize) {
this.$emit('confirm', value);
}
},
//
inputFocus() {
this.cursorVisible = this.code.length !== this.itemSize;
},
//
inputBlur() {
this.cursorVisible = false;
}
},
watch: {
value(val) {
this.code = val;
}
},
filters: {
codeFormat(val, isPassword) {
let value = '';
if (val) {
value = isPassword ? '*' : val;
}
return value;
}
}
};
</script>
<style lang="scss" scoped>
.xt__verify-code {
position: relative;
width: 100%;
box-sizing: border-box;
.xt__input {
height: 100%;
width: 200%;
position: absolute;
left: -100%;
z-index: 1;
}
.xt__cursor {
position: absolute;
top: 50%;
transform: translateY(-50%);
display: inline-block;
width: 2px;
animation-name: cursor;
animation-duration: 0.8s;
animation-iteration-count: infinite;
}
.xt__input-ground {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
box-sizing: border-box;
.xt__box {
position: relative;
display: inline-block;
width: 88rpx;
height: 100rpx;
background: #F7F7F7;
border-radius: 12rpx;
margin-left: 20rpx;
&-bottom {
border-bottom: 1rpx solid #009254;
}
&-box {
border: 1rpx solid #009254;
}
&-middle {
border: none;
}
.xt__middle-line {
position: absolute;
top: 50%;
left: 50%;
width: 50%;
transform: translate(-50%, -50%);
border-bottom-width: 2px;
border-bottom-style: solid;
}
.xt__code-text {
position: absolute;
top: 50%;
left: 50%;
font-size: 58rpx;
transform: translate(-50%, -50%);
}
}
.xt__box:nth-child(1){
margin-left: 0;
}
}
}
@keyframes cursor {
0% {
opacity: 1;
}
100% {
opacity: 0;
}
}
</style>

1384
dynamic/pages/detail.vue Normal file

File diff suppressed because it is too large Load Diff

206
dynamic/pages/follow.vue Normal file
View File

@ -0,0 +1,206 @@
<template>
<view class="dynamic-follow">
<view @tap.stop="goDetail(index)"
class="list-item flex-center fill-base mt-md ml-md mr-md pd-lg radius-16 box-shadow"
v-for="(item,index) in list.data" :key="index">
<image mode="aspectFill" class="avatar radius" :src="item.work_img"></image>
<view class="flex-1 ml-md">
<view class="flex-between">
<view class="f-title c-black text-bold ellipsis" style="max-width: 150rpx;">{{item.coach_name}}
</view>
<view class="flex-y-baseline f-desc c-black"> <i class="iconfont iconjuli"
:style="{color:primaryColor}"></i>
{{item.distance}}
</view>
</view>
<view class="flex-y-baseline">
<view class="text flex-y-center f-desc">已接单<view class="ml-sm" :style="{color:primaryColor}">
{{item.order_num}}
</view>
</view>
<view class="flex-y-center f-caption c-caption ml-md"> 粉丝数 <view class="c-title ml-sm">
{{item.fans_num}}
</view>
</view>
</view>
</view>
</view>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading" v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
<view class="space-footer"></view>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
data() {
return {
loading: true,
param: {
page: 1
},
list: {
data: []
}
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
userInfo: state => state.user.userInfo,
location: state => state.user.location,
}),
onLoad() {
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
this.$util.showLoading()
this.initIndex()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
this.getList();
},
methods: {
...mapMutations(['updateUserItem']),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
this.getList();
},
handerTabChange(index) {
this.activeIndex = index
this.param.status = index
this.getList();
},
initRefresh() {
this.param.page = 1
this.initIndex(true)
},
async getList() {
let {
list: oldList,
param,
location
} = this
if (!location.lat) {
// #ifdef H5
if (this.$jweixin.isWechat()) {
this.$util.showLoading()
// await this.$jweixin.initJssdk();
await this.$jweixin.wxReady2();
let {
latitude: lat = 0,
longitude: lng = 0
} = await this.$jweixin.getWxLocation()
location = {
lng,
lat,
address: '定位失败',
province: '',
city: '',
district: ''
}
if (lat && lng) {
let key = `${lat},${lng}`
let data = await this.$api.base.getMapInfo({
location: key
})
let {
status,
result
} = JSON.parse(data)
if (status == 0) {
let {
address,
address_component
} = result
let {
province,
city,
district
} = address_component
location = {
lng,
lat,
address,
province,
city,
district
}
}
}
}
// #endif
// #ifndef H5
location = await this.$util.getBmapLocation()
// #endif
this.updateUserItem({
key: 'location',
val: location
})
}
let {
lat = 0, lng = 0
} = location
param.lat = lat
param.lng = lng
let newList = await this.$api.dynamic.followCoachList(param)
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
this.loading = false
this.$util.hideAll()
},
goDetail(index) {
let {
coach_id
} = this.list.data[index]
this.$util.goUrl({
url: `/user/pages/technician-info?id=${coach_id}`
})
}
}
}
</script>
<style lang="scss">
.dynamic-follow {
.list-item {
.avatar {
width: 124rpx;
height: 124rpx;
}
.text {
color: #4D4D4D;
margin-top: 6rpx;
}
}
}
</style>

View File

@ -0,0 +1,194 @@
<template>
<view class="dynamic-technician-comment">
<view @longpress="toDel(index)" class="list-item flex-warp ml-lg mr-lg pt-lg pb-lg"
:class="[{'b-1px-t':index!==0}]" v-for="(item,index) in list.data" :key="index">
<image mode="aspectFill" class="avatar radius" :src="item.avatarUrl"></image>
<view class="flex-1 ml-md">
<view class="flex-between">
<view>
<view class="flex-y-center f-paragraph c-title text-bold">
<view class="max-400 ellipsis">{{item.nickName}}</view>
<view class="examine-btn flex-center f-icontext ml-md radius" v-if="item.status==1">审核中
</view>
</view>
<view class="text flex-y-center f-caption">评论了你的动态<view class="ml-md">{{item.friend_time}}
</view>
</view>
</view>
<image mode="aspectFill" class="cover radius-16" :src="item.cover"></image>
</view>
<view class="comment f-paragraph mt-md"> {{item.text}} </view>
</view>
</view>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading" v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
<view class="space-footer"></view>
<common-popup @confirm="confirmDel" ref="del_item" type="DELETE_ORDER" title="删除评论" desc="请确认是否删除评论,删除后将无法恢复"
:info="popupInfo"></common-popup>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
data() {
return {
loading: true,
param: {
page: 1
},
list: {
data: []
},
popupInfo: {},
lockTap: false
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
onLoad() {
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
this.initIndex()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
this.getList();
},
methods: {
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
this.getList();
},
handerTabChange(index) {
this.activeIndex = index
this.param.status = index
this.getList();
},
initRefresh() {
this.param.page = 1
this.initIndex(true)
},
async getList() {
this.$util.showLoading()
let {
list: oldList,
param
} = this
let newList = await this.$api.dynamic.coachCommentList(param)
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
this.loading = false
this.$util.hideAll()
},
toDel(index) {
let {
id,
cover: image
} = this.list.data[index]
this.popupInfo = {
id,
name: ``,
image,
index,
}
this.$refs.del_item.open()
},
//
async confirmDel() {
let {
id,
index,
} = this.popupInfo
if (this.lockTap) return
this.lockTap = true
this.$util.showLoading()
try {
await this.$api.dynamic.commentDel({
id
})
this.lockTap = false
this.$util.hideAll()
this.list.data.splice(index, 1)
this.$util.showToast({
title: `删除成功`
})
this.$refs.del_item.close()
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
}
},
}
}
</script>
<style lang="scss">
page {
background: #fff;
}
.dynamic-technician-comment {
.list-item {
.avatar {
width: 72rpx;
height: 72rpx;
margin-top: 16rpx;
}
.cover {
width: 107rpx;
height: 107rpx;
}
.text {
color: #ADADAD;
margin-top: 6rpx;
}
.comment {
color: #3A3A3A;
line-height: 1.4;
}
.examine-btn {
width: 82rpx;
height: 34rpx;
color: #F96246;
background: rgba(249, 98, 70, 0.1);
}
}
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,273 @@
<template>
<view class="dynamic-technician-add">
<view class="pd-lg">
<view class="pb-md" :class="[{'flex-warp': subForm.type == 2 }]">
<view class="mr-md" v-if="subForm.type == 2">
<upload @upload="imgUpload" @del="imgUpload" :imagelist="subForm.cover" filetype="picture"
imgtype="cover" text="上传封面" :imgsize="1">
</upload>
</view>
<upload @upload="imgUpload" @del="imgUpload" :imagelist="subForm.imgs"
:filetype="subForm.type == 1 ? 'picture' : 'video'" imgtype="imgs"
:text="subForm.type == 1 ? '上传图片' : '上传视频'" :imgsize="subForm.type == 1 ? 9 : 1">
</upload>
</view>
<view class="space-md"></view>
<input v-model="subForm.title" class="f-mini-title c-title" type="text" placeholder="填写标题有更多赞哟~"
placeholder-class="f-mini-title c-caption" maxlength="10" />
<view class="space-lg b-1px-b"></view>
<view class="space-lg"></view>
<textarea v-model="subForm.text" class="textarea f-desc c-title" maxlength="400"
placeholder="还有更多的想法也一起写到正文吧~" placeholder-class="f-desc c-caption"></textarea>
</view>
<view @tap.stop="toChooseLocation" class="pt-lg pl-lg pr-md flex-center f-desc">
<i class="iconfont icon-dingwei"></i>
<view class="flex-1 ml-sm mr-lg c-title ellipsis-2">
{{subForm.lat ? subForm.address : '添加地点'}}
</view>
<i class="iconfont icongengduo"></i>
</view>
<view class="space-max-footer"></view>
<fix-bottom-button @confirm="submit" :text="[{text:subForm.id?'编辑动态':'发布动态',type:'confirm'}]" bgColor="#fff">
</fix-bottom-button>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
data() {
return {
options: {},
subForm: {
id: 0,
cover: [],
imgs: [],
type: 1,
title: '',
text: '',
lat: '',
lng: '',
address: ''
},
rule: [{
name: "cover",
checkType: "isNotNull",
errorMsg: "请上传封面图"
}, {
name: "imgs",
checkType: "isNotNull",
errorMsg: ""
}, {
name: "title",
checkType: "isNotNull",
errorMsg: "请输入标题",
regType: 2
},
{
name: "text",
checkType: "isNotNull",
errorMsg: "请输入正文内容",
regType: 2
},
{
name: "address",
checkType: "isNotNull",
errorMsg: "请选择定位地点"
}
],
lockTap: false
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
location: state => state.user.location,
haveShieldOper: state => state.user.haveShieldOper,
}),
onLoad(options) {
let {
type
} = options
type = type * 1
this.options = options
this.subForm.type = type
this.rule[1].errorMsg = type == 1 ? '请上传图片' : '请上传视频'
this.initIndex()
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
},
methods: {
...mapMutations(['updateUserItem']),
async initIndex() {
let {
id = 0
} = this.options
if (!id) return
let data = await this.$api.dynamic.coachDynamicInfo({
id
})
data.cover = [{
path: data.cover
}]
data.imgs = data.imgs.map(item => {
return {
path: item
}
})
for (let key in this.subForm) {
this.subForm[key] = data[key]
}
},
imgUpload(e) {
let {
imagelist,
imgtype
} = e;
this.subForm[imgtype] = imagelist;
},
//
async toChooseLocation(e) {
await this.$util.checkAuth({
type: 'userLocation'
})
let {
lat: locaLat = '',
lng: locaLng = ''
} = this.location
let {
id = 0,
lat: addrLat,
lng: addrLng
} = this.subForm
if (id) {
locaLat = addrLat
locaLng = addrLng
}
let param = {}
if (!locaLat && !locaLng) {
// #ifdef H5
if (this.$jweixin.isWechat()) {
this.$util.showLoading()
await this.$jweixin.wxReady2();
let {
latitude,
longitude
} = await this.$jweixin.getWxLocation()
locaLat = latitude
locaLng = longitude
}
// #endif
// #ifdef APP-PLUS
let location = await this.$util.getBmapLocation()
locaLat = location.lat
locaLng = location.lng
// #endif
}
// #ifndef MP-WEIXIN
param = {
latitude: locaLat,
longitude: locaLng
}
// #endif
let [, {
address = '',
longitude: lng,
latitude: lat,
}] = await uni.chooseLocation(param);
if (!lng) return
this.subForm.address = address
this.subForm.lat = lat
this.subForm.lng = lng
},
//
validate(param) {
let validate = new this.$util.Validate();
this.rule.map(item => {
let {
name,
} = item
validate.add(param[name], item);
})
let message = validate.start();
return message;
},
async submit() {
let subForm = this.$util.deepCopy(this.subForm)
subForm.imgs = subForm.imgs && subForm.imgs.length > 0 ? subForm.imgs.map(item => {
return item.path
}) : []
let url = subForm.type == 1 ? subForm.imgs.length > 0 ? subForm.imgs[0] : '' : subForm.cover && subForm
.cover.length > 0 ? subForm.cover[0].path : ''
subForm.cover = url
let msg = this.validate(subForm);
if (msg) {
this.$util.showToast({
title: msg
});
return;
}
if (this.lockTap) return
this.lockTap = true
this.$util.showLoading()
try {
let methodModel = subForm.id ? 'dynamicUpdate' : 'dynamicAdd'
let {
status = 1
} = await this.$api.dynamic[methodModel](subForm)
this.$util.hideAll()
this.$util.showToast({
title: status == 1 ? '正在审核中' : subForm.id ? `编辑成功` : `发布成功`
})
this.updateUserItem({
key: 'haveShieldOper',
val: 1
})
setTimeout(() => {
this.$util.back()
this.$util.goUrl({
url: 1,
openType: `navigateBack`
})
}, 2000)
} catch (e) {
setTimeout(() => {
this.lockTap = false
this.$util.hideAll()
}, 2000)
}
}
}
}
</script>
<style lang="scss">
page {
background: #fff;
}
.dynamic-technician-add {
.icon-dingwei {
font-size: 28rpx;
}
.icongengduo {
font-size: 26rpx;
color: #9B9B9B;
}
.textarea {
width: 690rpx;
}
}
</style>

View File

@ -0,0 +1,130 @@
<template>
<view class="dynamic-technician-follow">
<view class="list-item flex-center ml-lg mr-lg pt-lg pb-lg" :class="[{'b-1px-t':index!==0}]"
v-for="(item,index) in list.data" :key="index">
<image mode="aspectFill" class="avatar radius" :src="item.avatarUrl"></image>
<view class="flex-1 flex-between ml-md">
<view>
<view class="f-paragraph c-title text-bold max-470 ellipsis">{{item.nickName}}</view>
<view class="text f-caption">开始关注了你</view>
</view>
<view class="time f-icontext">{{item.friend_time}}</view>
</view>
</view>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading" v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
<view class="space-footer"></view>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
data() {
return {
loading: true,
param: {
page: 1
},
list: {
data: []
}
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
onLoad() {
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
this.initIndex()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
this.getList();
},
methods: {
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
this.getList();
},
handerTabChange(index) {
this.activeIndex = index
this.param.status = index
this.getList();
},
initRefresh() {
this.param.page = 1
this.initIndex(true)
},
async getList() {
this.$util.showLoading()
let {
list: oldList,
param
} = this
let newList = await this.$api.dynamic.followList(param)
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
this.loading = false
this.$util.hideAll()
},
}
}
</script>
<style lang="scss">
page {
background: #fff;
}
.dynamic-technician-follow {
.list-item {
.avatar {
width: 72rpx;
height: 72rpx;
}
.text {
color: #ADADAD;
margin-top: 6rpx;
}
.time {
color: #9B9B9B;
}
}
}
</style>

View File

@ -0,0 +1,341 @@
<template>
<view class="dynamic-technician-list" v-if="isLoad">
<fixed>
<view class="fill-body">
<view class="count-list flex-center fill-base">
<view @tap.stop="$util.goUrl({url:`/dynamic/pages/technician/thumbs`})"
class="count-item flex-center flex-column f-caption c-title">
<view class="tag thumbs flex-center rel">
<i class="iconfont icon-shoucang-fill"></i>
<view class="count-tag flex-center f-icontext c-base abs"
:style="{width: count.thumbs_num < 10 ? '26rpx':'50rpx',right:count.thumbs_num < 10 ? '-13rpx':'-38rpx'}"
v-if="count.thumbs_num">
{{count.thumbs_num < 100 ? count.thumbs_num : '99+'}}
</view>
</view>
<view class="mt-md">收获的赞</view>
</view>
<view @tap.stop="$util.goUrl({url:`/dynamic/pages/technician/follow`})"
class="count-item flex-center flex-column f-caption c-title">
<view class="tag follow flex-center rel">
<i class="iconfont iconxinzengguanzhu"></i>
<view class="count-tag flex-center f-icontext c-base abs"
:style="{width: count.follow_num < 10 ? '26rpx':'50rpx',right:count.follow_num < 10 ? '-13rpx':'-38rpx'}"
v-if="count.follow_num">
{{count.follow_num < 100 ? count.follow_num : '99+'}}
</view>
</view>
<view class="mt-md">新增关注</view>
</view>
<view @tap.stop="$util.goUrl({url:`/dynamic/pages/technician/comment`})"
class="count-item flex-center flex-column f-caption c-title">
<view class="tag comment flex-center rel">
<i class="iconfont iconshouhuodepinglun"></i>
<view class="count-tag flex-center f-icontext c-base abs"
:style="{width: count.comment_num < 10 ? '26rpx':'50rpx',right:count.comment_num < 10 ? '-13rpx':'-38rpx'}"
v-if="count.comment_num">
{{count.comment_num < 100 ? count.comment_num : '99+'}}
</view>
</view>
<view class="mt-md">收获的评论</view>
</view>
</view>
<view class="rel">
<view class="flex-between pd-lg">
<view class="f-title text-bold">我的发布</view>
<view @tap.stop="toChangeItem(-1)" class="flex-y-center f-desc">
{{statusList[statusInd].title}}<i class="iconfont c-desc ml-sm"
:class="[{'iconshaixuanxia-1':showSort},{'iconshaixuanshang-1':!showSort}]"
style="font-size: 20rpx"></i>
</view>
</view>
<view class="dynamic-sort pt-md pb-md f-desc abs" v-if="showSort">
<view @tap.stop="toChangeItem(index)" class="sort-item flex-center"
:style="{color:statusInd == index ? primaryColor : ''}" v-for="(item,index) in statusList"
:key="index">
{{item.title}}
</view>
</view>
</view>
</view>
</fixed>
<wfalls-flow :list="list.data" :path="2" ref="wfalls" v-if="list.data.length > 0"></wfalls-flow>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading" v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
<view class="space-footer"></view>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
import wfallsFlow from "@/components/wfalls-flow.vue"
export default {
components: {
wfallsFlow
},
data() {
return {
isLoad: false,
statusList: [{
id: 0,
title: '全部'
}, {
id: 1,
title: '审核中'
}, {
id: 2,
title: '审核通过'
}, {
id: 3,
title: '已驳回'
}],
statusInd: 0,
showSort: false,
loading: true,
param: {
page: 1,
limit: 10,
status: 0
},
list: {
data: []
},
count: {}
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
location: state => state.user.location,
haveShieldOper: state => state.user.haveShieldOper,
}),
onLoad() {
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
this.$util.showLoading()
this.initIndex()
},
async onShow() {
if (this.haveShieldOper == 1) {
await this.initRefresh()
this.updateUserItem({
key: 'haveShieldOper',
val: 0
})
} else {
this.getDynamicData()
}
},
async onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
await this.initRefresh();
uni.stopPullDownRefresh()
},
async onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
await this.getList();
setTimeout(() => {
this.$refs.wfalls.handleViewRender();
}, 0)
},
methods: {
...mapMutations(['updateUserItem']),
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
if (refresh) {
await Promise.all([this.getDynamicData(), this.getList(1)])
} else {
await this.getList(1)
}
},
async initRefresh() {
await this.initIndex(true)
},
async getDynamicData() {
this.count = await this.$api.dynamic.dynamicData()
},
async getList(flag) {
if (flag) {
this.showSort = false
this.param.page = 1
this.list.data = []
}
let {
location
} = this
if (!location.lat) {
// #ifdef H5
if (this.$jweixin.isWechat()) {
this.$util.showLoading()
// await this.$jweixin.initJssdk();
await this.$jweixin.wxReady2();
let {
latitude: lat = 0,
longitude: lng = 0
} = await this.$jweixin.getWxLocation()
location = {
lng,
lat,
address: '定位失败',
province: '',
city: '',
district: ''
}
if (lat && lng) {
let key = `${lat},${lng}`
let data = await this.$api.base.getMapInfo({
location: key
})
let {
status,
result
} = JSON.parse(data)
if (status == 0) {
let {
address,
address_component
} = result
let {
province,
city,
district
} = address_component
location = {
lng,
lat,
address,
province,
city,
district
}
}
}
}
// #endif
// #ifndef H5
location = await this.$util.getBmapLocation()
// #endif
this.updateUserItem({
key: 'location',
val: location
})
}
let {
lat = 0, lng = 0
} = location
let {
list: oldList,
param,
statusList,
statusInd
} = this
param.lat = lat
param.lng = lng
param.status = statusList[statusInd].id
let newList = await this.$api.dynamic.coachDynamicList(param)
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
this.isLoad = true
this.loading = false
this.$util.hideAll()
},
toChangeItem(index) {
if (index == -1) {
this.showSort = !this.showSort
} else {
this.statusInd = index
this.showSort = false
this.initRefresh()
}
},
}
}
</script>
<style lang="scss">
.dynamic-technician-list {
.count-list {
width: 750rpx;
height: 204rpx;
.count-item {
width: 33.33%;
.tag {
width: 85rpx;
height: 85rpx;
border-radius: 29rpx;
.iconfont {
font-size: 44rpx;
}
.count-tag {
top: 0;
right: -13rpx;
width: 26rpx;
height: 26rpx;
background: #E82F21;
border-radius: 26rpx;
}
}
.thumbs {
color: #ff6262;
background: #FFEFEF;
}
.follow {
color: #FC8218;
background: #FEF6E7;
}
.comment {
color: #44A860;
background: #ECF6EF;
}
}
}
.dynamic-sort {
top: 80rpx;
right: 32rpx;
width: 145rpx;
background: #FEFFFE;
box-shadow: 0 14rpx 20rpx 0 rgba(132, 132, 132, 0.08);
border-radius: 4rpx;
border: 1rpx solid #EFEFEF;
transform: rotateZ(360deg);
z-index: 1;
.sort-item {
padding: 15rpx 0;
}
}
}
</style>

View File

@ -0,0 +1,126 @@
<template>
<view class="dynamic-technician-thumbs">
<view class="list-item flex-center ml-lg mr-lg pt-lg pb-lg" :class="[{'b-1px-t':index!==0}]"
v-for="(item,index) in list.data" :key="index">
<image mode="aspectFill" class="avatar radius" :src="item.avatarUrl"></image>
<view class="flex-1 flex-between ml-md">
<view>
<view class="f-paragraph c-title text-bold max-470 ellipsis">{{item.nickName}}</view>
<view class="text flex-y-center f-caption">赞了你的动态<view class="ml-md">{{item.friend_time}}</view>
</view>
</view>
<image mode="aspectFill" class="cover radius-16" :src="item.cover"></image>
</view>
</view>
<load-more :noMore="list.current_page>=list.last_page&&list.data.length>0" :loading="loading" v-if="loading">
</load-more>
<abnor v-if="!loading&&list.data.length<=0&&list.current_page==1"></abnor>
<view class="space-footer"></view>
</view>
</template>
<script>
import {
mapState,
mapActions,
mapMutations
} from "vuex"
export default {
data() {
return {
loading: true,
param: {
page: 1
},
list: {
data: []
}
}
},
computed: mapState({
primaryColor: state => state.config.configInfo.primaryColor,
subColor: state => state.config.configInfo.subColor,
configInfo: state => state.config.configInfo,
userInfo: state => state.user.userInfo,
}),
onLoad() {
this.$util.setNavigationBarColor({
bg: this.primaryColor
})
this.initIndex()
},
onPullDownRefresh() {
// #ifndef APP-PLUS
uni.showNavigationBarLoading()
// #endif
this.initRefresh();
uni.stopPullDownRefresh()
},
onReachBottom() {
if (this.list.current_page >= this.list.last_page || this.loading) return;
this.param.page = this.param.page + 1;
this.loading = true;
this.getList();
},
methods: {
async initIndex(refresh = false) {
// #ifdef H5
if (!refresh && this.$jweixin.isWechat()) {
await this.$jweixin.initJssdk();
this.$jweixin.wxReady(() => {
this.$jweixin.hideOptionMenu()
})
}
// #endif
this.getList();
},
initRefresh() {
this.param.page = 1
this.initIndex(true)
},
async getList() {
this.$util.showLoading()
let {
list: oldList,
param
} = this
let newList = await this.$api.dynamic.thumbsList(param)
if (this.param.page == 1) {
this.list = newList
} else {
newList.data = oldList.data.concat(newList.data)
this.list = newList
}
this.loading = false
this.$util.hideAll()
},
}
}
</script>
<style lang="scss">
page {
background: #fff;
}
.dynamic-technician-thumbs {
.list-item {
.avatar {
width: 72rpx;
height: 72rpx;
}
.cover {
width: 107rpx;
height: 107rpx;
}
.text {
color: #ADADAD;
margin-top: 6rpx;
}
}
}
</style>

30
jweixin-module/README.md Normal file
View File

@ -0,0 +1,30 @@
# jweixin-module
微信JS-SDK
## 安装
### NPM
```shell
npm install jweixin-module --save
```
### UMD
```http
https://unpkg.com/jweixin-module/out/index.js
```
## 使用
```js
var jweixin = require('jweixin-module')
jweixin.ready(function(){
// TODO
});
```
## 完整API
>[微信JS-SDK说明文档](https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1421141115)

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,53 @@
{
"_from": "jweixin-module",
"_id": "jweixin-module@1.6.0",
"_inBundle": false,
"_integrity": "sha512-dGk9cf+ipipHmtzYmKZs5B2toX+p4hLyllGLF6xuC8t+B05oYxd8fYoaRz0T30U2n3RUv8a4iwvjhA+OcYz52w==",
"_location": "/jweixin-module",
"_phantomChildren": {},
"_requested": {
"type": "tag",
"registry": true,
"raw": "jweixin-module",
"name": "jweixin-module",
"escapedName": "jweixin-module",
"rawSpec": "",
"saveSpec": null,
"fetchSpec": "latest"
},
"_requiredBy": [
"#USER"
],
"_resolved": "https://registry.npmjs.org/jweixin-module/-/jweixin-module-1.6.0.tgz",
"_shasum": "4a7ea614083e3c9c3f49e2fdc2bb882cfa58dfcd",
"_spec": "jweixin-module",
"_where": "D:\\node-v12.12.0",
"author": {
"name": "Shengqiang Guo"
},
"bugs": {
"url": "https://github.com/zhetengbiji/jweixin-module/issues"
},
"bundleDependencies": false,
"deprecated": false,
"description": "微信JS-SDK",
"devDependencies": {},
"homepage": "https://github.com/zhetengbiji/jweixin-module#readme",
"keywords": [
"wxjssdk",
"weixin",
"jweixin",
"wechat",
"jssdk",
"wx"
],
"license": "ISC",
"main": "lib/index.js",
"name": "jweixin-module",
"repository": {
"type": "git",
"url": "git+https://github.com/zhetengbiji/jweixin-module.git"
},
"scripts": {},
"version": "1.6.0"
}

13
locale/index.js Normal file
View File

@ -0,0 +1,13 @@
import zhCnLocale from './lang/zh-CN';
import enUsLocale from './lang/en-US';
import VueI18n from 'vue-i18n';
import Vue from 'vue';
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: 'zh',
messages: {
'en': enUsLocale,
'zh': zhCnLocale
}
})
export default i18n

5
locale/lang/en-US.js Normal file
View File

@ -0,0 +1,5 @@
export default {
action: {
attendantName: 'technician'
}
}

12
locale/lang/zh-CN.js Normal file
View File

@ -0,0 +1,12 @@
export default {
action: {
attendantName: '技师',
agreeRefund: '立即退款',
transferOrder: '转单',
orderTaking: '技师接单',
setOut: '技师出发',
arrive: '技师到达',
startService: '开始服务',
serviceCompletion: '服务完成',
}
}

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