合立app移动端项目初始化

ccongli-dev-app-hl-0920
LI-CCONG\李聪聪 1 year ago
parent 63b1b48d73
commit c85b50b5ef

@ -1,5 +1,5 @@
{
"name" : "芋道移动端",
"name" : "合立机械",
"appid" : "__UNI__25A9D80",
"description" : "",
"versionName" : "1.0.0",

@ -66,19 +66,19 @@
} else if (this.loginForm.password === "") {
this.$modal.msgError("请输入您的密码")
} else {
//
if (this.captchaEnabled) {
this.$refs.verify.show()
} else { //
await this.pwdLogin({})
}
//
if (this.captchaEnabled) {
this.$refs.verify.show()
} else { //
await this.pwdLogin({})
}
}
},
//
async pwdLogin(captchaParams) {
this.$modal.loading("登录中,请耐心等待...")
//
this.loginForm.captchaVerification = captchaParams.captchaVerification
this.$modal.loading("登录中,请耐心等待...")
//
this.loginForm.captchaVerification = captchaParams.captchaVerification
this.$store.dispatch('Login', this.loginForm).then(() => {
this.$modal.closeLoading()
this.loginSuccess()
@ -165,4 +165,4 @@
.login-code-img {
height: 45px;
}
</style>
</style>

@ -0,0 +1,22 @@
# 告诉EditorConfig插件这是根文件不用继续往上查找
root = true
# 匹配全部文件
[*]
# 设置字符集
charset = utf-8
# 缩进风格可选space、tab
indent_style = space
# 缩进的空格数
indent_size = 2
# 结尾换行符可选lf、cr、crlf
end_of_line = lf
# 在文件结尾插入新行
insert_final_newline = true
# 删除一行中的前后空格
trim_trailing_whitespace = true
# 匹配md结尾的文件
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

@ -0,0 +1,9 @@
/unpackage/dist/*
/unpackage/cache/*
/unpackage/release/*
/node_modules/*
/.idea/*
/.hbuilderx/
/.vscode/
/.hbuilderx
yarn.lock

@ -0,0 +1,34 @@
<script>
export default {
onLaunch: function () {
console.log('App Launch')
// #ifdef H5
//sessionStorage
if (sessionStorage.getItem('store')) {
this.$store.replaceState(Object.assign({}, this.$store.state, JSON.parse(sessionStorage.getItem('store'))))
}
//vuexsessionStorage
window.addEventListener('beforeunload', () => {
sessionStorage.setItem('store', JSON.stringify(this.$store.state))
})
// #endif
},
onShow: function () {
console.log('App Show')
},
onHide: function () {
console.log('App Hide')
}
}
</script>
<style lang="scss">
/** 引入全局基本样式 */
@import 'styles/base.scss';
/* 引入 uView 基础样式 */
@import '@/uni_modules/uview-ui/index.scss';
/*每个页面公共scss */
@import 'app.scss';
</style>

@ -0,0 +1,15 @@
//请求工具参考https://ext.dcloud.net.cn/plugin?id=392
const { http } = uni.$u
//获得用户收件地址列表
export const getAddressList = params => http.get('/member/address/list', params)
//创建用户收件地址
export const createAddress = data => http.post('/member/address/create', data)
//通过ID获得用户收件地址
export const getAddressById = params => http.get('/member/address/get', { params })
//获得默认的用户收件地址
export const getDefaultUserAddress = params => http.get('/member/address/get-default', { params })
//更新用户收件地址
export const updateAddress = params => http.put('/member/address/update', params)
//删除用户收件地址
export const deleteAddress = params => http.delete('/member/address/delete', {}, { params })

@ -0,0 +1,15 @@
//请求工具参考https://ext.dcloud.net.cn/plugin?id=392
const { http } = uni.$u
//使用手机 + 密码登录
export const passwordLogin = data => http.post('/member/auth/login', data)
//发送手机验证码
export const sendSmsCode = data => http.post('/member/auth/send-sms-code', data)
//使用手机 + 验证码登录
export const smsLogin = data => http.post('/member/auth/sms-login', data)
//微信小程序的一键登录
export const weixinMiniAppLogin = data => http.post('/member/auth/weixin-mini-app-login', data)
//刷新令牌
export const refreshToken = data => http.post('/member/auth/refresh-token', {data})
//退出登录
export const logout = data => http.post('/member/auth/logout', data)

@ -0,0 +1,5 @@
const { http } = uni.$u
//获取购物车数据
export const getCartProductDetail = () => http.get('/trade/cart/get-detail')

@ -0,0 +1,5 @@
//请求工具参考https://ext.dcloud.net.cn/plugin?id=392
const { http } = uni.$u
// 查询分类列表
export const categoryListData = params => http.get('product/category/list', { params })

@ -0,0 +1,7 @@
//请求工具参考https://ext.dcloud.net.cn/plugin?id=392
const { http } = uni.$u
// 获取滚动图数据
export const getBannerData = params => http.get('/index', { params })
// 获取滚动通知数据
export const getNoticeData = params => http.get('/notice', { params })

@ -0,0 +1,6 @@
const { http } = uni.$u
// 获得订单交易分页
export const getOrderPage = params => http.get('/trade/order/page', { params })
// 获得交易订单详情
export const getOrderDetail = params => http.get('/trade/order/get-detail', { params })

@ -0,0 +1,8 @@
//请求工具参考https://ext.dcloud.net.cn/plugin?id=392
const { http } = uni.$u
// 查询商品spu列表
export const productSpuPage = params => http.get('product/spu/page', { params })
// 查询商品
export const getSpuDetail = id => http.get('product/spu/get-detail?id=' + id, { })

@ -0,0 +1,14 @@
//请求工具参考https://ext.dcloud.net.cn/plugin?id=392
const { http } = uni.$u
//获取用户信息
export const getUserInfo = params => http.get('/member/user/get', params)
//修改用户头像
export const updateAvatar = filePath =>
http.upload('/member/user/update-avatar', {
name: 'avatarFile',
fileType: 'image',
filePath: filePath
})
//修改用户昵称
export const updateNickname = params => http.put('/member/user/update-nickname', {}, { params })

@ -0,0 +1,8 @@
/* 页面公共scss */
.container {
background-color: $custom-bg-color;
margin: 0;
padding: 0;
box-sizing: border-box;
height: 100%;
}

@ -0,0 +1,14 @@
module.exports = {
//后端接口地址
// baseUrl: 'http://127.0.0.1:48080/app-api',
baseUrl: 'http://127.0.0.1:8091/app-api',
// baseUrl: 'http://api-dashboard.yunxi.iocoder.cn/app-api',
// 超时
timeout: 30000,
// 禁用 Cookie 等信息
withCredentials: false,
header: {
//租户ID
'tenant-id': 1
}
}

@ -0,0 +1,5 @@
export default {
data() {
return {}
}
}

@ -0,0 +1,7 @@
export default {
'0': { name: '待付款', icon: 'red-packet'},
'10': { name: '待发货', icon: 'car'},
'20': { name: '待收货', icon: 'order'},
'30': { name: '已完成', icon: 'integral'},
'40': { name: '已取消', icon: 'close-circle'}
}

@ -0,0 +1,3 @@
/* uView组件全局属性 */
uni.$u.props.gap.bgColor = '#ffffff'
uni.$u.props.gap.height = '10'

File diff suppressed because one or more lines are too long

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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>

@ -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[2]: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>

@ -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;
}

@ -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>

@ -0,0 +1,106 @@
<template>
<view class="address-box">
<view v-if="!address.area" class="address-empty">
<view class="address-tips">
<u--text :lines="1" size="14px" color="#333" bold="true" text="请选择收货地址"></u--text>
<u-icon class="icon-arrow-right" name="arrow-right"></u-icon>
</view>
</view>
<view v-else class="address-selected">
<view class="address-area">
<view class="default-tag">默认</view>
<u--text :lines="1" size="12px" color="#666" :text="address.area"></u--text>
</view>
<view class="address-detail">
<u--text :lines="2" size="14px" color="#333" :bold="true" :text="address.detail"></u--text>
<u-icon class="icon-arrow-right" name="arrow-right"></u-icon>
</view>
<view class="address-user">
<view class="user-text">{{ address.name }}</view>
<view class="user-text">{{ address.mobile }}</view>
</view>
</view>
<image class="address-line" src="/static/images/address-line.png"></image>
</view>
</template>
<script>
/**
* 地址选择
*/
export default {
name: 'yd-address-select',
props: {
address: {
type: Object,
default: () => []
}
},
data() {
return {}
},
methods: {}
}
</script>
<style lang="scss" scoped>
.address-box {
padding: 0 10rpx;
background-color: #fff;
border-radius: 0 0 30rpx 30rpx;
.address-empty {
padding: 20rpx 20rpx 0;
.address-tips {
@include flex-space-between;
padding: 10rpx 0;
.icon-arrow-right {
margin-left: 50rpx;
}
}
}
.address-selected {
padding: 20rpx 20rpx 0;
.address-area {
@include flex-left;
.default-tag {
font-size: 22rpx;
color: $u-primary;
border: 1rpx solid $u-primary;
padding: 0 6rpx;
margin-right: 10rpx;
border-radius: 5rpx;
}
}
.address-detail {
@include flex-space-between;
padding: 10rpx 0;
.icon-arrow-right {
margin-left: 50rpx;
}
}
.address-user {
@include flex-left;
.user-text {
color: #666;
font-size: 24rpx;
margin-right: 10rpx;
}
}
}
.address-line {
width: 100%;
margin: 0 auto;
height: 5rpx;
}
}
</style>

@ -0,0 +1,58 @@
<template>
<u-swiper :list="bannerList" :keyName="keyName" previousMargin="20" nextMargin="20" circular height="150" @change="e => (current = e.current)" :autoplay="true" @click="handleSwiperClick">
<view slot="indicator" class="indicator">
<view class="indicator__dot" v-for="(item, index) in bannerList" :key="index" :class="[index === current && 'indicator__dot--active']"></view>
</view>
</u-swiper>
</template>
<script>
/**
* 广告滚动图
*/
export default {
name: 'yd-banner',
components: {},
props: {
keyName: {
type: String,
default: 'url'
},
bannerList: {
type: Array,
default: () => []
}
},
data() {
return {
current: 0,
currentNum: 0
}
},
methods: {
handleSwiperClick(index) {
console.log('点击了图片索引值:', index)
}
}
}
</script>
<style lang="scss" scoped>
.indicator {
@include flex(row);
justify-content: center;
&__dot {
height: 15rpx;
width: 15rpx;
border-radius: 100rpx;
background-color: rgba(255, 255, 255, 0.35);
margin: 0 10rpx;
transition: background-color 0.3s;
&--active {
background-color: $custom-bg-color;
}
}
}
</style>

@ -0,0 +1,116 @@
<template>
<view>
<view class="product-item" v-for="(item, index) in productList" :key="item.productId" @click="handleProductItemClick(item.productId)">
<view class="product-check" @click.stop="handleCheckProduct(item.productId, item.checked)">
<u-icon v-if="item.checked" name="checkmark-circle-fill" color="#3c9cff" size="22"></u-icon>
<view v-else class="un-check-box"></view>
</view>
<image class="product-image" :src="item.coverUrl"></image>
<view class="item-info">
<view class="info-text">
<u--text :lines="1" size="15px" color="#333333" :text="item.productTitle"></u--text>
<u-gap height="2px"></u-gap>
<u--text class="info-tips" :lines="1" size="12px" color="#939393" :text="item.tips"></u--text>
</view>
<view class="price-number-box">
<view class="price-box">
<yd-text-price color="red" size="13" intSize="20" :price="item.sellPrice"></yd-text-price>
<yd-text-price v-if="item.strikePrice" style="margin-left: 5px" decoration="line-through" color="#999" size="12" :price="item.sellPrice"></yd-text-price>
</view>
<view class="number-box" @click.stop>
<u-number-box min="1" max="999" bgColor="#fff" integer :disableMinus="false" :disabledInput="true" :name="item.productId" :value="item.productCount" @change="handleProductCountChange"></u-number-box>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 购物车商品列表
*/
export default {
name: 'yd-cart-product',
props: {
productList: {
type: Array,
default: () => []
}
},
data() {
return {
//status: 'nomore',
loadingText: '加载中...',
loadmoreText: '上拉加载更多',
nomoreText: '没有更多了'
}
},
methods: {
handleProductItemClick(productId) {
uni.$u.route('/pages/product/product', {
id: productId
})
},
handleItemCartClick(productId) {
this.$store.dispatch('CartProductCountChange', { productIds: [productId], productCount: 1, addition: true }).then(res => {
uni.$u.toast('已添加到购物车')
})
},
handleCheckProduct(productId, checked) {
this.$emit('productCheckedChange', productId, !checked)
},
handleProductCountChange({ name, value }) {
this.$emit('productCartProductCountChange', name, value)
}
}
}
</script>
<style lang="scss" scoped>
.product-item {
background: #ffffff;
@include flex-space-between;
border-bottom: $custom-border-style;
padding: 10rpx 0 0 5rpx;
.product-check {
padding: 20rpx;
.un-check-box {
width: 20px;
height: 20px;
border: 1px solid #939393;
border-radius: 50%;
}
}
.product-image {
width: 180rpx;
height: 180rpx;
border-radius: 10rpx;
}
.item-info {
flex: 1;
padding: 20rpx 20rpx 0;
.info-text {
height: 70rpx;
padding-bottom: 10rpx;
}
.price-number-box {
@include flex-space-between;
.price-box {
@include flex-left();
}
.number-box {
height: 100rpx;
@include flex-center;
}
}
}
}
</style>

@ -0,0 +1,100 @@
<template>
<view>
<view class="product-item" v-for="(item, index) in productList" :key="item.productId">
<image class="product-image" :src="item.coverUrl"></image>
<view class="item-info">
<view class="info-text">
<u--text :lines="1" size="15px" color="#333333" :text="item.productTitle"></u--text>
<u-gap height="10"></u-gap>
<yd-text-price class="product-price" size="13" intSize="16" :price="item.sellPrice"></yd-text-price>
</view>
<view class="price-number-box">
<view class="number-box">
<view class="product-number"> {{ item.productCount }} </view> 小计
</view>
<view class="number-box" @click.stop>
<yd-text-price size="13" intSize="18" :price="item.totalPrice"></yd-text-price>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 订单商品列表
*/
export default {
name: 'yd-order-product',
props: {
productList: {
type: Array,
default: () => []
}
},
data() {
return {}
},
methods: {
handleProductItemClick(productId) {
uni.$u.route('/pages/product/product', {
id: productId
})
}
}
}
</script>
<style lang="scss" scoped>
.product-item {
background: #ffffff;
@include flex-space-between;
border-bottom: $custom-border-style;
padding: 10rpx 0 0 5rpx;
.product-check {
padding: 20rpx;
.un-check-box {
width: 20px;
height: 20px;
border: 1px solid #939393;
border-radius: 50%;
}
}
.product-image {
width: 180rpx;
height: 180rpx;
border-radius: 10rpx;
}
.item-info {
flex: 1;
padding: 0 20rpx;
.info-text {
padding-bottom: 10rpx;
.product-price {
margin-top: 15rpx;
}
}
.price-number-box {
@include flex-space-between;
.number-box {
font-size: 24rpx;
.product-number {
width: 200rpx;
}
}
.number-box {
height: 60rpx;
@include flex-center;
}
}
}
}
</style>

@ -0,0 +1,173 @@
<template>
<view>
<view v-if="showType === 'normal'">
<u-gap height="180" bgColor="#398ade"></u-gap>
<view class="prod-block">
<view class="bloc-header">
<text class="bloc-title">{{title}}</text>
<text class="see-more">查看更多</text>
</view>
<view class="prod-grid">
<view class="prod-item" v-for="(item, index) in productList" :key="item.id" @click="handleProdItemClick(item.id)">
<image class="prod-image" :src="item.image"></image>
<view class="item-info">
<view class="info-text">
<u--text :lines="2" size="14px" color="#333333" :text="item.title"></u--text>
</view>
<view class="price-and-cart">
<yd-text-price color="red" size="12" intSize="18" :price="item.price"></yd-text-price>
<u-icon name="shopping-cart" color="#2979ff" size="28"></u-icon>
</view>
</view>
</view>
</view>
</view>
</view>
<view v-if="showType === 'half'">
<view class="prod-block half">
<view class="bloc-header">
<text class="bloc-title">{{title}}</text>
<text class="more">更多 &gt;</text>
</view>
<view class="prod-grid half">
<view class="prod-item" v-for="(item, index) in productList" :key="item.id" @click="handleProdItemClick(item.id)">
<image class="prod-image" :src="item.image"></image>
<view class="item-info">
<view class="info-text">
<u--text :lines="1" size="14px" color="#333333" :text="item.title"></u--text>
<u--text :lines="1" size="12px" color="#939393" :text="item.desc"></u--text>
</view>
<view class="price-and-cart">
<yd-text-price color="red" size="12" intSize="18" :price="item.price"></yd-text-price>
<u-icon name="shopping-cart" color="#2979ff" size="28"></u-icon>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 商品列表
*/
export default {
name: 'yd-product-box',
components: {},
props: {
showType: {
type: String,
default: 'normal'
},
title: {
type: String,
default: '商品推荐'
},
productList: {
type: Array,
default: () => []
}
},
computed: {},
methods: {
handleProdItemClick(productId) {
uni.$u.route('/pages/product/product', {
id: productId
})
}
}
}
</script>
<style lang="scss" scoped>
.prod-block {
margin-top: -160px;
.bloc-header {
@include flex-space-between;
padding: 10rpx 20rpx;
.bloc-title {
color: $custom-bg-color;
font-size: 34rpx;
}
.see-more {
color: $custom-bg-color;
background: $u-primary;
padding: 0 30rpx;
height: 50rpx;
line-height: 50rpx;
border-radius: 50rpx;
font-size: 24rpx;
}
}
&.half {
margin-top: 0;
.bloc-header {
margin-top: 50rpx;
margin-bottom: 20rpx;
.bloc-title {
color: #333333;
}
.more {
font-size: 24rpx;
}
}
}
.prod-grid {
width: 730rpx;
margin: 0 auto;
@include flex;
flex-wrap: wrap;
justify-content: left;
&.half {
.prod-item {
width: 345rpx;
margin: 10rpx;
.prod-image {
width: 345rpx;
height: 345rpx;
}
}
}
.prod-item {
width: 223rpx;
margin: 10rpx;
background: #ffffff;
border-radius: 10rpx;
box-shadow: 0rpx 6rpx 8rpx rgba(58, 134, 185, 0.2);
.prod-image {
width: 223rpx;
height: 223rpx;
border-radius: 10rpx 10rpx 0 0;
}
.item-info {
padding: 15rpx;
.info-text {
height: 70rpx;
padding-bottom: 10rpx;
}
.price-and-cart {
@include flex-space-between;
}
}
}
}
}
</style>

@ -0,0 +1,114 @@
<template>
<view>
<view class="prod-block list">
<view class="bloc-header">
<text class="bloc-title">更多宝贝</text>
<text></text>
</view>
<view class="prod-list" v-for="(item, index) in productList" :key="item.id">
<view class="prod-item" @click="handleProdItemClick(item.id)">
<image class="prod-image" :src="item.image"></image>
<view class="item-info">
<view class="info-text">
<u--text :lines="1" size="14px" color="#333333" :text="item.title"></u--text>
<u-gap height="2px"></u-gap>
<u--text class="info-desc" :lines="2" size="12px" color="#939393" :text="item.desc"></u--text>
</view>
<view class="price-and-cart">
<yd-text-price color="red" size="12" intSize="18" :price="item.price"></yd-text-price>
<u-icon name="shopping-cart" color="#2979ff" size="28"></u-icon>
</view>
</view>
</view>
</view>
</view>
<!--加载更多-->
<u-loadmore fontSize="28rpx" :line="true" :status="moreStatus" :loading-text="loadingText" :loadmore-text="loadmoreText" :nomore-text="nomoreText" />
</view>
</template>
<script>
/**
* 商品列表(加载更多)
*/
export default {
name: 'yd-product-more',
components: {},
props: {
title: {
type: String,
default: '商品推荐'
},
productList: {
type: Array,
default: () => []
},
moreStatus: {
type: String,
default: 'nomore'
}
},
data() {
return {
//status: 'nomore',
loadingText: '加载中...',
loadmoreText: '上拉加载更多',
nomoreText: '已经到底了'
}
},
methods: {
handleProdItemClick(productId) {
uni.$u.route('/pages/product/product', {
id: productId
})
}
}
}
</script>
<style lang="scss" scoped>
.prod-block {
margin-top: 0;
.bloc-header {
margin-top: 50rpx;
margin-bottom: 20rpx;
.bloc-title {
margin-left: 20rpx;
color: #333333;
}
}
.prod-list {
.prod-item {
background: #ffffff;
@include flex-space-between;
border-bottom: $custom-border-style;
padding: 20rpx;
.prod-image {
width: 200rpx;
height: 200rpx;
border-radius: 10rpx;
}
.item-info {
flex: 1;
padding: 20rpx 20rpx 0;
.info-text {
height: 100rpx;
padding-bottom: 10rpx;
}
.price-and-cart {
@include flex-space-between;
}
}
}
}
}
</style>

@ -0,0 +1,73 @@
<template>
<view>
<text v-for="(item, index) in textArray" :key="index" :style="{ fontSize: (index === 1 ? integerSize : size) + 'px', color, textDecoration }">
{{ item }}
</text>
</view>
</template>
<script>
/**
* 此组件简单的显示驼峰式的价格
*/
export default {
name: 'yd-text-price',
components: {},
props: {
//
symbol: {
type: String,
default: '¥'
},
price: {
type: [String, Number],
default: ''
},
color: {
type: String,
default: '#333333'
},
//
size: {
type: [String, Number],
default: 15
},
//
intSize: {
type: [String, Number],
default: ''
},
//线线
decoration: {
type: String,
default: 'none'
}
},
data() {
return {
textDecoration: this.decoration
}
},
computed: {
textArray() {
let array = []
if (this.price === '' || this.price === undefined) {
return []
}
array.push(this.symbol)
if (!/^\d+(\.\d+)?$/.test(this.price)) {
console.error('组件<yd-text-price :text="???" 此处参数应为金额数字')
} else {
let arr = parseFloat(this.price).toFixed(2).split('.')
array.push(arr[0])
array.push('.' + arr[1])
}
return array
},
integerSize() {
return this.intSize ? this.intSize : this.size
}
}
}
</script>
<style scoped></style>

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<script>
let coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
CSS.supports('top: constant(a)'))
document.write(
'<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
(coverSupport ? ', viewport-fit=cover' : '') + '" />')
</script>
<title></title>
<!--preload-links-->
<!--app-context-->
</head>
<body>
<div id="app"><!--app-html--></div>
<script type="module" src="/main.js"></script>
</body>
</html>

@ -0,0 +1,26 @@
import Vue from 'vue'
import App from './App'
// 引入全局uView
import uView from '@/uni_modules/uview-ui'
// vuex
import store from './store'
Vue.config.productionTip = false
Vue.prototype.$store = store
App.mpType = 'app'
Vue.use(uView)
const app = new Vue({
store,
...App
})
// 引入请求封装
require('./utils/request/index')(app)
app.$mount()

@ -0,0 +1,72 @@
{
"name" : "yunxi-ui-app",
"appid" : "__UNI__CA099E3",
"description" : "yunxi-ui-app for ruoyi-vue-pro",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/* */
"modules" : {},
/* */
"distribute" : {
/* android */
"android" : {
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
}
},
/* */
"quickapp" : {},
/* */
"mp-weixin" : {
"appid" : "wxd4d71484fd08f94b",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "2"
}

@ -0,0 +1,156 @@
{
"pages": [ //pageshttps://uniapp.dcloud.io/collocation/pages
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/category/category",
"style": {
"navigationBarTitleText": "分类"
}
},
{
"path": "pages/category/product-list",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/cart/cart",
"style": {
"navigationBarTitleText": "购物车"
}
},
{
"path": "pages/user/user",
"style": {
"navigationBarTitleText": "我的"
}
},
{
"path": "pages/login/social",
"style": {
"navigationBarTitleText": "授权登录"
}
},
{
"path": "pages/login/mobile",
"style": {
"navigationBarTitleText": "手机登录"
}
},
{
"path": "pages/forgot/forgot",
"style": {
"navigationBarTitleText": "忘记密码"
}
},
{
"path": "pages/profile/profile",
"style": {
"navigationBarTitleText": "个人资料"
}
},
{
"path": "pages/setting/setting",
"style": {
"navigationBarTitleText": "账户设置"
}
},
{
"path": "pages/product/product",
"style": {
"navigationBarTitleText": "商品详情"
}
},
{
"path": "pages/checkout/checkout",
"style": {
"navigationBarTitleText": "填写订单"
}
},
{
"path": "pages/order/list",
"style": {
"navigationBarTitleText": "我的订单"
}
},
{
"path": "pages/order/detail",
"style": {
"navigationBarTitleText": "订单详情"
}
},
{
"path": "pages/order/confirm",
"style": {
"navigationBarTitleText": "确认订单"
}
},
{
"path": "pages/address/list",
"style": {
"navigationBarTitleText": "收货地址"
}
},
{
"path": "pages/search/search",
"style": {
"navigationBarTitleText": "搜索"
}
},
{
"path": "pages/address/create",
"style": {
"navigationBarTitleText": "新增地址"
}
},
{
"path": "pages/address/update",
"style": {
"navigationBarTitleText": "修改地址"
}
}
],
"tabBar": {
"selectedColor": "#333333",
"color": "#bfbfbf",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "/static/images/tabbar/index.png",
"selectedIconPath": "/static/images/tabbar/index-active.png"
},
{
"pagePath": "pages/category/category",
"text": "分类",
"iconPath": "/static/images/tabbar/category.png",
"selectedIconPath": "/static/images/tabbar/category-active.png"
},
{
"pagePath": "pages/cart/cart",
"text": "购物车",
"iconPath": "/static/images/tabbar/cart.png",
"selectedIconPath": "/static/images/tabbar/cart-active.png"
},
{
"pagePath": "pages/user/user",
"text": "我的",
"iconPath": "/static/images/tabbar/user.png",
"selectedIconPath": "/static/images/tabbar/user-active.png"
}
]
},
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "yunxi-ui-app",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#ffffff"
}
}

@ -0,0 +1,148 @@
<template>
<view class="container">
<view class="address-box">
<u--form labelPosition="left" :model="formData" :rules="rules" ref="form">
<u-form-item label="收件人名称" prop="name" labelWidth="90" borderBottom ref="item-name">
<u-input type="text" maxlength="11" v-model="formData.name" clearable placeholder="请填写收件人名称" border="none"></u-input>
</u-form-item>
<u-form-item label="手机号" prop="mobile" labelWidth="90" borderBottom ref="item-mobile">
<u-input type="number" maxlength="11" v-model="formData.mobile" clearable placeholder="请填写手机号" border="none"></u-input>
</u-form-item>
<u-form-item label="省市地区" prop="areaText" labelWidth="90" borderBottom @click=" regionVisible = true; hideKeyboard()" ref="item-areaText">
<u--input v-model="formData.areaText" disabled disabledColor="#ffffff" placeholder="请选择省市地区" border="none"></u--input>
<u-icon slot="right" name="arrow-right"></u-icon>
<w-picker :visible.sync="regionVisible" mode="region" :value="defaultRegion" default-type="value" :hide-area="false" @confirm="onConfirm($event, 'region')" @cancel="onCancel" ref="region"></w-picker>
</u-form-item>
<u-form-item label="详细地址" prop="detail" labelWidth="90" borderBottom ref="item-detail">
<u--textarea placeholder="请输入街道门牌号不低于6个字" v-model="formData.detail" count></u--textarea>
</u-form-item>
<u-form-item label="默认地址" prop="type" labelWidth="90" borderBottom ref="item-type">
<u-radio-group v-model="formData.type">
<u-radio :customStyle="{ marginRight: '16px' }" v-for="(item, index) in typeList" :key="index" :label="item.name" :name="item.value"></u-radio>
</u-radio-group>
</u-form-item>
<view class="btn-group">
<u-button type="primary" text="保存地址" customStyle="margin-top: 50px" @click="handleSubmit"></u-button>
</view>
</u--form>
</view>
</view>
</template>
<script>
import { createAddress } from '../../api/address'
export default {
data() {
return {
regionVisible: false,
defaultRegion: ['110000', '110100', '110101'],
typeList: [
{
name: '是',
value: 1
},
{
name: '否',
value: 2
}
],
formData: {
name: '',
mobile: '',
areaText: '',
areaCode: '',
detail: '',
detailAddress: '',
type: 1
},
rules: {
name: [
{
type: 'string',
min: 2,
max: 12,
required: true,
message: '请填写收件人名称',
trigger: ['blur', 'change']
},
{
message: '收件人名称不能为空',
// blurchange
trigger: ['change', 'blur']
}
],
mobile: [
{
type: 'integer',
required: true,
message: '请填写手机号',
trigger: ['blur', 'change']
},
{
//
validator: (rule, value, callback) => {
// truefalse
// uni.$u.test.mobile()truefalse
return uni.$u.test.mobile(value)
},
message: '手机号码不正确',
// blurchange
trigger: ['change', 'blur']
}
],
areaText: {
type: 'string',
required: true,
message: '请选择省市地区',
trigger: ['blur', 'change']
},
detailAddress: {
type: 'string',
min: 6,
max: 30,
required: true,
message: '请填写详细地址',
trigger: ['blur', 'change']
}
}
}
},
onLoad() {},
methods: {
onConfirm(res) {
this.formData.areaText = res.result
this.formData.areaCode = res.value[2]
},
onCancel() {},
hideKeyboard() {
uni.hideKeyboard()
},
handleSubmit() {
this.$refs.form.validate().then(res => {
this.formData.detailAddress = this.formData.areaText + this.formData.detail
console.log(this.formData)
createAddress(this.formData).then(res => {
uni.$u.toast('地址已保存')
setTimeout(() => {
uni.navigateBack()
}, 300)
})
})
}
}
}
</script>
<style lang="scss" scoped>
.address-box {
width: 690rpx;
margin: 0 auto;
padding-top: 30rpx;
}
</style>

@ -0,0 +1,116 @@
<template>
<view class="container">
<view class="address-list" v-for="(item, index) in addressList" :key="item.id">
<view class="address-item" @click="handleClick" @longpress="handleLongPress(item)">
<view class="left">
<u-avatar :text="item.name ? item.name.slice(0, 1) : 'U'" fontSize="18" randomBgColor></u-avatar>
</view>
<view class="middle">
<view class="info">
<view class="name">{{ item.name }}</view>
<view class="mobile">{{ item.mobile }}</view>
<u-tag class="type" v-if="item.type === 1" text="默认" plain size="mini" type="success"></u-tag>
</view>
<view class="detail">
<u--text :lines="2" size="14" color="#939393" :text="item.detailAddress"></u--text>
</view>
</view>
<navigator class="right" :url="`/pages/address/update?addressId=${item.id}`" open-type="navigate" hover-class="none">
<u-icon name="edit-pen" size="28"></u-icon>
</navigator>
</view>
</view>
<navigator class="fixed-btn-box" url="/pages/address/create" open-type="navigate" hover-class="none">
<u-button type="primary" size="large" text="新增地址"></u-button>
</navigator>
<u-safe-bottom customStyle="background: #ffffff"></u-safe-bottom>
</view>
</template>
<script>
import { getAddressList, deleteAddress } from '../../api/address'
export default {
data() {
return {
addressList: []
}
},
onLoad() {},
onShow() {
this.loadAddressListData()
},
methods: {
loadAddressListData() {
getAddressList().then(res => {
this.addressList = res.data
})
},
handleLongPress(item) {
uni.showModal({
title: '提示',
content: `删除收件人[${item.name}${item.mobile}]\n地址${item.detailAddress.slice(0, 5)}......${item.detailAddress.slice(-6)}`,
success: res => {
if (res.confirm) {
deleteAddress({ id: item.id }).then(res => {
uni.$u.toast('地址已删除')
this.loadAddressListData();
})
} else if (res.cancel) {
//console.log('')
}
}
})
},
handleClick(){
// TODO
}
}
}
</script>
<style lang="scss" scoped>
.address-list {
.address-item {
padding: 10rpx 0;
border-bottom: $custom-border-style;
@include flex-space-between;
.left {
margin: 20rpx;
}
.middle {
flex: 1;
margin: 20rpx;
@include flex(column);
.info {
@include flex-left;
.name {
font-size: 30rpx;
font-weight: 700;
}
.mobile {
font-size: 28rpx;
margin-left: 15rpx;
}
.type {
margin-left: 15rpx;
}
}
.detail {
margin-top: 10rpx;
}
}
.right {
margin: 20rpx;
}
}
}
.fixed-btn-box {
position: fixed;
bottom: 0;
left: 0;
width: 750rpx;
}
</style>

@ -0,0 +1,178 @@
<template>
<view class="container">
<view class="address-box">
<u--form labelPosition="left" :model="formData" :rules="rules" ref="form">
<u-form-item label="收件人名称" prop="name" labelWidth="90" borderBottom ref="item-name">
<u-input type="text" maxlength="11" v-model="formData.name" clearable placeholder="请填写收件人名称" border="none"></u-input>
</u-form-item>
<u-form-item label="手机号" prop="mobile" labelWidth="90" borderBottom ref="item-mobile">
<u-input type="number" maxlength="11" v-model="formData.mobile" clearable placeholder="请填写手机号" border="none"></u-input>
</u-form-item>
<u-form-item label="省市地区" prop="areaText" labelWidth="90" borderBottom @click=" regionVisible = true; hideKeyboard()" ref="item-areaText">
<u--input v-model="formData.areaText" disabled disabledColor="#ffffff" placeholder="请选择省市地区" border="none"></u--input>
<u-icon slot="right" name="arrow-right"></u-icon>
<w-picker :visible.sync="regionVisible" mode="region" :value="defaultRegion" default-type="value" :hide-area="false" @confirm="onConfirm($event, 'region')" @cancel="onCancel" ref="region"></w-picker>
</u-form-item>
<u-form-item label="详细地址" prop="detail" labelWidth="90" borderBottom ref="item-detail">
<u--textarea placeholder="请输入街道门牌号不低于6个字" v-model="formData.detail" count></u--textarea>
</u-form-item>
<u-form-item label="默认地址" prop="type" labelWidth="90" borderBottom ref="item-type">
<u-radio-group v-model="formData.type">
<u-radio :customStyle="{ marginRight: '16px' }" v-for="(item, index) in typeList" :key="index" :label="item.name" :name="item.value"></u-radio>
</u-radio-group>
</u-form-item>
<view class="btn-group">
<u-button type="primary" text="更新地址" customStyle="margin-top: 50px" @click="handleSubmit"></u-button>
</view>
</u--form>
</view>
</view>
</template>
<script>
import { getAddressById, updateAddress } from '../../api/address'
export default {
data() {
return {
id: '',
regionVisible: false,
defaultRegion: ['110000', '110100', '110101'],
typeList: [
{
name: '是',
value: 1
},
{
name: '否',
value: 2
}
],
formData: {
id: '',
name: '',
mobile: '',
areaText: '',
areaCode: '',
detail: '',
detailAddress: '',
type: 1
},
rules: {
name: [
{
type: 'string',
min: 2,
max: 12,
required: true,
message: '请填写收件人名称',
trigger: ['blur', 'change']
},
{
message: '收件人名称不能为空',
// blurchange
trigger: ['change', 'blur']
}
],
mobile: [
{
type: 'integer',
required: true,
message: '请填写手机号',
trigger: ['blur', 'change']
},
{
//
validator: (rule, value, callback) => {
// truefalse
// uni.$u.test.mobile()truefalse
return uni.$u.test.mobile(value)
},
message: '手机号码不正确',
// blurchange
trigger: ['change', 'blur']
}
],
areaText: {
type: 'string',
required: true,
message: '请选择省市地区',
trigger: ['blur', 'change']
},
detailAddress: {
type: 'string',
min: 6,
max: 30,
required: true,
message: '请填写详细地址',
trigger: ['blur', 'change']
}
}
}
},
onLoad(e) {
if (!e.addressId) {
uni.$u.toast('请求参数错误')
} else {
this.id = e.addressId
this.loadAddressData()
}
},
methods: {
loadAddressData() {
getAddressById({ id: this.id }).then(res => {
this.formData = res.data
this.initRegionData()
})
},
initRegionData(){
//
if (this.formData.areaCode) {
const areaCode = this.formData.areaCode + ''
//code--code
this.defaultRegion.splice(0, 3, areaCode.substring(0,2).padEnd(6, '0'), areaCode.substring(0,4).padEnd(6, '0'), areaCode)
this.$nextTick(res => {
let areaText = this.$refs.region._data.result.result
this.formData.areaText = areaText
//--
this.formData.detail = this.formData.detailAddress.replace(areaText, '')
this.$forceUpdate();
})
}
},
onConfirm(res) {
this.formData.areaText = res.result
this.formData.areaCode = res.value[2]
},
onCancel() {},
hideKeyboard() {
uni.hideKeyboard()
},
handleSubmit() {
this.$refs.form.validate().then(res => {
this.formData.detailAddress = this.formData.areaText + this.formData.detail
console.log(this.formData)
updateAddress(this.formData).then(res => {
uni.$u.toast('地址已更新')
setTimeout(() => {
uni.navigateBack()
}, 300)
})
})
}
}
}
</script>
<style lang="scss" scoped>
.address-box {
width: 690rpx;
margin: 0 auto;
padding-top: 30rpx;
}
</style>

@ -0,0 +1,225 @@
<template>
<view class="container">
<!-- 购物车为空 -->
<view v-if="!hasLogin || cartList.length === 0">
<view class="cart-empty">
<u-empty text="去逛逛添点什么吧" width="500rpx" height="500rpx" icon="/static/images/empty/cart.png"></u-empty>
</view>
</view>
<!-- 购物车列表 -->
<scroll-view v-if="hasLogin && cartList.length > 0" class="cart-product" scroll-y="true">
<yd-cart-product :product-list="cartList" @productCheckedChange="handleProductCheckedChange" @productCountChange="handleProductCountChange"></yd-cart-product>
</scroll-view>
<!-- 未登录 -->
<view v-if="!hasLogin" class="login-tips-box">
<view class="login-tips">
<navigator url="/pages/login/social" open-type="navigate" hover-class="none">
<view class="login-link">登录查看</view>
</navigator>
</view>
</view>
<!-- 底部菜单 -->
<view class="cart-btn-container">
<view class="btn-box">
<view class="product-check-info">
<view class="check-all-btn" @click.stop="handleCheckAllProduct">
<u-icon v-if="isCheckAll" name="checkmark-circle-fill" color="#3c9cff" size="22"></u-icon>
<view v-else class="un-check-box"></view>
</view>
<view class="info-text">合计</view>
<view>
<yd-text-price color="red" size="15" intSize="20" :price="totalAmount"></yd-text-price>
</view>
</view>
<view v-if="checkedProduct.length > 0" class="cart-btn-group">
<u-button type="warning" shape="circle" size="small" text="移除" @click="handleRemoveProduct"></u-button>
<view class="btn-gap"></view>
<u-button style="margin-left: 10px" class="main-btn" type="primary" shape="circle" size="small" text="去结算" @click="handleCheckoutProduct"></u-button>
</view>
<view v-else class="cart-btn-group">
<u-button type="warning" shape="circle" size="small" text="移除" disabled></u-button>
<view class="btn-gap"></view>
<u-button style="margin-left: 10px" class="main-btn" type="primary" shape="circle" size="small" text="去结算" disabled></u-button>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
title: '',
cartList: [],
checkedNumber: 0,
totalAmount: 0
}
},
computed: {
checkedProduct() {
return this.cartList.filter(item => {
return item.checked
})
},
isCheckAll() {
if (this.cartList.length < 1) {
return false
}
return this.cartList.every(item => {
return item.checked
})
},
hasLogin() {
return this.$store.getters.hasLogin
}
},
onShow() {
if (this.hasLogin) {
this.loadCartDetailData()
} else {
this.cartList =[]
}
},
methods: {
loadCartDetailData() {
this.$store.dispatch('CartProductDetail').then(res => {
this.cartList = res.data || []
})
},
/** 商品全选/取消全选 */
handleCheckAllProduct() {
if (this.cartList.length < 1) {
return
}
const productIds = this.cartList.map(item => {
return item.productId
})
this.$store.dispatch('CartProductCheckChange', { productIds, checked: !this.isCheckAll }).then(res => {
this.cartList = res.data || []
})
},
/** 商品单选/取消单选 */
handleProductCheckedChange(productId, checked) {
this.$store.dispatch('CartProductCheckChange', { productIds: [productId], checked: checked }).then(res => {
this.cartList = res.data || []
})
},
/** 修改购物车商品数量 */
handleProductCountChange(productId, number) {
this.$store.dispatch('CartProductCountChange', { productIds: [productId], productCount: number }).then(res => {
this.cartList = res.data || []
})
},
/** 移除购物车商品 */
handleRemoveProduct() {
if (this.checkedProduct < 1) {
return
}
const productIds = this.checkedProduct.map(item => {
return item.productId
})
uni.showModal({
title: '确定要移除选中的商品?',
cancelText: '取消',
confirmText: '移除',
success: res => {
if (res.confirm) {
this.$store.dispatch('CartProductCountChange', { productIds: productIds, productCount: 0 }).then(res => {
this.cartList = res.data || []
})
} else if (res.cancel) {
//console.log('')
}
}
})
},
/** 购物车提交结算 */
handleCheckoutProduct() {
if (this.checkedProduct < 1) {
return
}
const checkedProduct = this.checkedProduct.map(item => {
return { productId: item.productId, productCount: item.productCount, sellPrice: item.sellPrice }
})
uni.$u.route('/pages/checkout/checkout', {
checkedProduct: JSON.stringify(checkedProduct)
})
}
}
}
</script>
<style lang="scss" scoped>
.login-tips-box {
padding-top: 100rpx;
.login-tips {
@include flex-center;
color: #939393;
font-size: 24rpx;
letter-spacing: 5rpx;
}
.login-link {
width: 160rpx;
height: 50rpx;
line-height: 50rpx;
border-radius: 50rpx;
border: 1px solid #777;
color: #777;
text-align: center;
}
}
.cart-btn-container {
position: fixed;
bottom: 0;
left: 0;
.btn-box {
background: $custom-bg-color;
border-top: $custom-border-style;
width: 750rpx;
@include flex-space-between();
height: 100rpx;
.product-check-info {
@include flex-left;
.check-all-btn {
padding: 20rpx;
.un-check-box {
width: 20px;
height: 20px;
border: 1px solid #939393;
border-radius: 50%;
}
}
.info-text {
font-size: 26rpx;
font-weight: bold;
color: #666666;
}
}
.cart-btn-group {
@include flex-right();
width: 360rpx;
padding-right: 10px;
.btn-gap {
width: 20rpx;
}
}
}
}
</style>

@ -0,0 +1,205 @@
<template>
<view class="container">
<!-- 搜索框 -->
<view class="search-wrap">
<u-search placeholder="搜索" disabled height="32" bgColor="#f2f2f2" margin="0 20rpx" :show-action="false"
@click="handleSearchClick"></u-search>
</view>
<!-- 分类内容 -->
<view class="category-box">
<!-- 左侧导航栏 -->
<scroll-view scroll-y="true" class='box-left'>
<view class="category-item" v-for="(item, index) in categoryList" :key="item.id">
<view class="item-title" :class="{ active: currentIndex === index }" @click="handleCategoryClick(index)">
<text>{{ item.name }}</text>
</view>
</view>
</scroll-view>
<!-- 右侧分类内容 -->
<scroll-view scroll-y="true" class="box-right">
<view class="category-image">
<image :showLoading="true" :src="categoryList[currentIndex].picUrl" mode='widthFix' @click="click"></image>
</view>
<view class="sub-category-box" v-for="(item, index) in categoryList[currentIndex].children" :key="item.id">
<view class="sub-category-header">
<view class="title">{{ item.name }}</view>
<view class="more" @click="handleCategory(item, 0)">查看更多</view>
</view>
<view class="sub-category-grid">
<u-grid col="3">
<u-grid-item v-for="(subItem, subIndex) in item.children" :key="subItem.id">
<view class="sub-category-item" @click="handleCategory(item, subIndex)">
<u-icon name="photo" :size="80" v-if="subItem.picUrl === null"></u-icon>
<image :src="subItem.picUrl" v-if="subItem.picUrl != null" mode='widthFix' />
<text class="sub-category-title">{{ subItem.name }}</text>
</view>
</u-grid-item>
</u-grid>
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import { categoryListData } from '../../api/category';
import { handleTree, convertTree } from '../../utils/tree.js';
export default {
data() {
return {
currentIndex: 0,
categoryList: []
}
},
onLoad() {
this.handleCategoryList();
},
methods: {
//
handleSearchClick(e) {
uni.$u.route('/pages/search/search')
},
//
handleCategoryClick(index) {
if (this.currentIndex !== index) {
this.currentIndex = index
}
},
//
handleCategoryList() {
categoryListData().then(res => {
this.categoryList = handleTree(res.data, "id", "parentId");
})
},
handleCategory(item, index){
// console.log(item)
// console.log(index)
uni.navigateTo({
url:"./product-list?item="+encodeURIComponent(JSON.stringify(item))+"&index="+index
})
}
}
}
</script>
<style lang="scss" scoped>
.search-wrap {
background: #ffffff;
position: fixed;
top: 0;
left: 0;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.07);
padding: 20rpx 0;
width: 100%;
z-index: 3;
}
.category-box {
position: fixed;
display: flex;
overflow: hidden;
margin-top: 100rpx;
height: calc(100% - 100rpx);
.box-left {
width: 200rpx;
padding-top: 5rpx;
overflow: scroll;
z-index: 2;
background-color: #f2f2f2;
.category-item {
line-height: 80rpx;
height: 80rpx;
text-align: center;
color: #777;
.item-title {
font-size: 28rpx;
&.active {
font-size: 28rpx;
font-weight: bold;
position: relative;
background: #fff;
color: $u-primary;
}
&.active::before {
position: absolute;
left: 0;
content: "";
width: 8rpx;
height: 32rpx;
top: 25rpx;
background: $u-primary;
}
}
}
}
.box-right {
width: 550rpx;
height: 100%;
box-sizing: border-box;
z-index: 1;
.category-image {
width: 510rpx;
box-sizing: border-box;
overflow: hidden;
position: relative;
margin: 30rpx 20rpx 0;
image {
width: 100%;
}
}
.sub-category-box {
.sub-category-header {
@include flex-space-between;
padding: 20rpx 20rpx;
.title {
font-size: 28rpx;
font-weight: bolder;
}
.more {
font-size: 22rpx;
color: #939393;
}
}
.sub-category-grid {
padding: 0 15rpx;
.sub-category-item {
@include flex-center(column);
background: #fff;
image {
text-align: center;
width: 150rpx;
height: 150rpx;
line-height: 150rpx;
font-size: 0;
}
.sub-category-title {
margin: 15rpx 0;
font-size: 22rpx;
}
}
}
}
}
}
</style>

@ -0,0 +1,164 @@
<template>
<view class="container">
<u-navbar :title="title" :autoBack="true" placeholder="true" titleStyle="font-size: 28rpx">
</u-navbar>
<view class="context">
<!-- 分类列表 -->
<!-- TODO @Luowenfeng不应该展示商品分类应该是上面一个筛选之后是综合销量价格的排序 -->
<view class="tabs-top">
<u-tabs :list="categoryList" @click="changeTabs" :current="current" lineHeight="2" lineWidth="85rpx"
itemStyle="padding-left: 15px; padding-right: 15px; height: 85rpx;"></u-tabs>
</view>
<!-- 商品列表 -->
<scroll-view scroll-y="true" class="product-list" enable-flex="true">
<view class="flex-box">
<block v-for="(item, index) in productList[current]" :key="index">
<view class="product-item">
<view class="product-image">
<image :src="item.picUrls[0]" mode='widthFix' />
</view>
<view class="product-button">
<view class="product-text">{{ item.name }}</view>
<view class="product-price-button">
<text class="product-price">
<text class="price-size">{{ towNumber(item.minPrice) }}</text></text>
<text class="product-like-count">销量 {{ item.salesCount }}</text>
</view>
</view>
</view>
</block>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import {
productSpuPage
} from '../../api/product';
export default {
data() {
return {
title: "",
current: 0,
categoryList: [],
productList: {}
}
},
onLoad(option) {
const item = JSON.parse(decodeURIComponent(option.item))
this.title = item.name
this.categoryList = item.children
this.handleProductSpu(option.index);
},
methods: {
changeTabs(item) {
if (item.index !== this.current) {
this.handleProductSpu(item.index)
}
},
handleProductSpu(index) {
let param = {}
param.categoryId = this.categoryList[index].id
console.log(this.categoryList)
console.log(index)
productSpuPage(param).then(res => {
this.productList[index] = res.data.list
this.current = index
})
},
towNumber(val) {
return (val / 100).toFixed(2)
}
}
}
</script>
<style lang="scss" scoped>
.context {
width: 100vw;
position: fixed;
top: 160rpx;
left: 0;
}
.tabs-top {
position: relative;
top: 0;
width: 100%;
height: 85rpx;
}
.product-list {
position: relative;
background-color: #f2f2f2;
height: calc(100vh - 88rpx - 100rpx - var(--status-bar-height));
width: 100%;
.flex-box {
width: 730rpx;
margin: 0 auto;
@include flex;
flex-wrap: wrap;
justify-content: left;
.product-item {
width: 345rpx;
height: 450rpx;
background-color: #ffffff;
margin: 20rpx 10rpx 0;
border-radius: 20rpx;
.product-image {
width: 100%;
height: 300rpx;
overflow: hidden;
border-radius: 20rpx;
image {
width: 100%;
}
}
.product-button {
width: 330rpx;
margin: 15rpx auto 0;
.product-text {
font-size: 25rpx;
height: 70rpx;
overflow: hidden;
-webkit-line-clamp: 2;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
}
.product-price-button {
font-size: 20rpx;
margin-top: 20rpx;
.product-price {
color: red;
.price-size {
font-size: 26rpx;
}
}
.product-like-count {
font-size: 16rpx;
margin-left: 10rpx;
}
}
}
}
}
// }
}
</style>

@ -0,0 +1,219 @@
<template>
<view class="container">
<!-- 收货地址选择 -->
<yd-address-select :address="address"></yd-address-select>
<!-- 订单商品信息 -->
<view class="checkout-goods">
<yd-order-goods :goods-list="checkoutList"></yd-order-goods>
</view>
<view class="freight-coupon-box">
<vidw class="box-row">
<view class="title">运费</view>
<yd-text-price class="freight-fee" size="15" :price="freightAmount"></yd-text-price>
</vidw>
<vidw class="box-row">
<view class="coupon-wrap">
<view class="coupon-title-tag">
<view class="title">优惠券</view>
<scroll-view class="coupon-tag-list" scroll-x="true">
<view v-for="item in couponList" :key="item.couponId" class="coupon-tag-item">{{ item.couponTag }}</view>
</scroll-view>
</view>
<yd-text-price class="coupon-fee" color="red" size="15" symbol="-¥" :price="couponAmount"></yd-text-price>
</view>
<u-icon name="arrow-right"></u-icon>
</vidw>
</view>
<!-- 订单备注信息 -->
<view class="user-remark-box">
<view class="title">订单备注</view>
<u--input maxlength="50" border="none" fontSize="14" v-model="remark" placeholder="如您需要请备注"></u--input>
</view>
<view class="cart-btn-container">
<view class="order-total-wrap">
<view class="order-total-info">
<view class="info-text">合计</view>
<view>
<yd-text-price color="red" size="15" intSize="20" :price="totalAmount"></yd-text-price>
</view>
</view>
<view class="cart-btn-group">
<u-button style="margin-left: 10px" class="main-btn" type="primary" shape="circle" size="small" text="提交订单" @click="handleSubmitOrder"></u-button>
</view>
</view>
<u-safe-bottom customStyle="background: #ffffff"></u-safe-bottom>
</view>
</view>
</template>
<script>
import { checkoutCartProduct } from '../../api/order'
export default {
components: {},
data() {
return {
checkedProduct: [],
address: {
name: '客户',
mobile: '139****6563',
area: 'XXX市XXX区',
detail: 'XXX街道XXX小区XXX号楼XX-XXX'
},
checkoutList: [],
couponList: [
{
couponId: 3,
couponTag: '6元运费券'
}
],
totalAmount: 0,
freightAmount: 6,
couponAmount: 6,
remark: ''
}
},
onLoad(e) {
const checkedProduct = e.checkedProduct
if (checkedProduct) {
this.checkedProduct = JSON.parse(checkedProduct)
this.loadCheckoutProductData()
} else {
uni.$u.toast('请求参数错误')
}
},
methods: {
loadCheckoutProductData() {
checkoutCartProduct(this.checkedProduct)
.then(res => {
this.checkoutList = res.data.checkoutList || []
this.totalAmount = res.data.totalAmount || 0
})
.catch(err => {
console.log(err)
})
}
}
}
</script>
<style lang="scss" scoped>
.container {
background-color: $custom-bg-color;
height: 100vh;
overflow-x: scroll;
}
.checkout-goods {
background-color: #fff;
margin-top: 20rpx;
padding: 20rpx;
border-radius: 20rpx;
}
.freight-coupon-box {
background-color: #fff;
margin-top: 20rpx;
padding: 20rpx 30rpx;
border-radius: 20rpx;
.box-row {
@include flex-space-between;
padding: 10rpx 0;
.coupon-wrap {
@include flex-space-between;
width: 670rpx;
.coupon-title-tag {
@include flex-left;
.coupon-tag-list {
@include flex-left;
overflow-x: scroll;
width: 360rpx;
.coupon-tag-item {
display: inline-block;
font-size: 22rpx;
color: red;
border: 1rpx solid red;
padding: 1px 6rpx;
margin-right: 10rpx;
border-radius: 5rpx;
}
}
}
}
.title {
font-weight: 700;
font-size: 30rpx;
color: #333;
margin-right: 30rpx;
}
.freight-fee {
margin-right: 50rpx;
}
.coupon-fee {
margin-right: 20rpx;
}
}
}
.user-remark-box {
@include flex-space-between;
background-color: #fff;
margin-top: 20rpx;
padding: 30rpx;
border-radius: 20rpx;
.title {
font-weight: 700;
font-size: 30rpx;
color: #333;
margin-right: 30rpx;
}
}
.cart-btn-container {
position: fixed;
bottom: 0;
left: 0;
.order-total-wrap {
background: $custom-bg-color;
border-top: $custom-border-style;
width: 750rpx;
@include flex-space-between();
height: 100rpx;
.order-total-info {
@include flex-left;
.info-text {
margin-left: 20rpx;
font-size: 26rpx;
font-weight: bold;
color: #666666;
}
}
.cart-btn-group {
@include flex-right();
width: 360rpx;
padding-right: 10px;
.btn-gap {
width: 20rpx;
}
}
}
}
</style>

@ -0,0 +1,157 @@
<template>
<view class="container">
<view class="unp-header">
<view class="unp-logo">
<u-avatar size="80" icon="github-circle-fill" fontSize="80"></u-avatar>
</view>
</view>
<view class="unp-box">
<u--form class="unp-form" labelPosition="left" :model="formData" :rules="rules" ref="form">
<u-form-item label="账号" prop="username" borderBottom ref="item-username">
<u-input type="text" maxlength="20" v-model="formData.username" clearable placeholder="账号由数字和字母组成" border="none" @change="handleUsernameChange"></u-input>
</u-form-item>
<u-gap height="20"></u-gap>
<u-form-item label="验证码" prop="code" labelWidth="80" borderBottom>
<u--input type="number" maxlength="6" v-model="formData.code" border="none" placeholder="请填写验证码"></u--input>
<u-button slot="right" @tap="getCode" :text="tips" type="success" size="mini" :disabled="codeDisabled"></u-button>
<u-code ref="uCode" @change="codeChange" seconds="60" @start="codeDisabled = true" @end="codeDisabled = false"></u-code>
</u-form-item>
<u-gap height="20"></u-gap>
<u-form-item label="密码" prop="password" borderBottom ref="item-password">
<u-input :type="inputType" maxlength="20" v-model="formData.password" placeholder="新密码由数字、字母和符号组成" border="none" @change="handlePasswordChange">
<template slot="suffix">
<u-icon v-if="inputType === 'password'" size="20" color="#666666" name="eye-fill" @click="inputType = 'text'"></u-icon>
<u-icon v-if="inputType === 'text'" size="20" color="#666666" name="eye-off" @click="inputType = 'password'"></u-icon>
</template>
</u-input>
</u-form-item>
<view class="lk-group">
<!-- 占位 -->
</view>
<u-button type="error" text="重置密码" customStyle="margin-top: 50px" @click="handleSubmit"></u-button>
<u-gap height="20"></u-gap>
<u-button type="info" text="返回" @click="navigateBack()"></u-button>
</u--form>
</view>
</view>
</template>
<script>
export default {
data() {
return {
codeDisabled: false,
tips: '',
inputType: 'password',
formData: {
username: '',
code: '',
password: ''
},
rules: {
username: {
type: 'string',
max: 20,
required: true,
message: '请输入您的账号',
trigger: ['blur', 'change']
},
code: {
type: 'number',
max: 6,
required: true,
message: '请输入验证码',
trigger: ['blur', 'change']
},
password: {
type: 'string',
max: 20,
required: true,
message: '请输入您的新密码',
trigger: ['blur', 'change']
}
}
}
},
onLoad() {},
methods: {
handleUsernameChange(e) {
let str = uni.$u.trim(e, 'all')
this.$nextTick(() => {
this.formData.username = str
})
},
handlePasswordChange(e) {
let str = uni.$u.trim(e, 'all')
this.$nextTick(() => {
this.formData.password = str
})
},
codeChange(text) {
this.tips = text
},
getCode() {
if (this.$refs.uCode.canGetCode) {
//
uni.showLoading({
title: '正在获取验证码'
})
setTimeout(() => {
uni.hideLoading()
// this.start()
uni.$u.toast('验证码已发送')
//
this.$refs.uCode.start()
}, 2000)
} else {
uni.$u.toast('倒计时结束后再发送')
}
},
handleSubmit() {
this.$refs.form
.validate()
.then(res => {
uni.$u.toast('点击了重置密码')
})
},
navigateBack() {
uni.navigateBack()
}
}
}
</script>
<style lang="scss" scoped>
.unp-header {
height: 400rpx;
@include flex-center;
.unp-logo {
@include flex-center;
}
}
.unp-box {
@include flex-center(column);
.unp-form {
width: 560rpx;
}
}
.lk-group {
height: 40rpx;
margin-top: 40rpx;
@include flex-space-between;
font-size: 12rpx;
color: $u-primary;
text-decoration: $u-primary;
}
</style>

@ -0,0 +1,149 @@
<template>
<view class="container">
<!--搜索栏-->
<u-sticky style="top: 0" offset-top="0">
<view class="search-wrap">
<u-search placeholder="搜索" disabled height="32" :show-action="false" @click="handleSearchClick"></u-search>
</view>
</u-sticky>
<!--轮播图-->
<yd-banner :banner-list="bannerList"></yd-banner>
<u-gap height="20px"></u-gap>
<!--宫格菜单按钮-->
<u-grid :border="false" col="4">
<u-grid-item v-for="(item, index) in menuList" :key="index">
<u-icon :name="item.icon" :size="40"></u-icon>
<text class="grid-title">{{ item.title }}</text>
</u-grid-item>
</u-grid>
<u-gap height="15px"></u-gap>
<!--消息滚动栏-->
<u-notice-bar style="padding: 13px 12px" :text="noticeList" mode="link" direction="column" @click="click"></u-notice-bar>
<!--商品展示栏-->
<yd-product-box :product-list="productList" :title="'每日上新'" show-type="normal"></yd-product-box>
<yd-product-box :product-list="productList" :title="'热卖商品'" show-type="half"></yd-product-box>
<yd-product-more :product-list="productList" :more-status="moreStatus"></yd-product-more>
<u-gap height="5px"></u-gap>
</view>
</template>
<script>
import { getBannerData, getNoticeData } from '../../api/index'
export default {
components: {},
data() {
return {
bannerList: [
{
id: 1,
title: '山不在高,有仙则名',
url: 'https://cdn.uviewui.com/uview/swiper/swiper1.png'
},
{
id: 2,
title: '水不在深,有龙则灵',
url: 'https://cdn.uviewui.com/uview/swiper/swiper2.png'
},
{
id: 3,
title: '斯是陋室,惟吾德馨',
url: 'https://cdn.uviewui.com/uview/swiper/swiper3.png'
}
],
menuList: [
{ icon: 'gift', title: '热门推荐' },
{ icon: 'star', title: '收藏转发' },
{ icon: 'thumb-up', title: '点赞投币' },
{ icon: 'heart', title: '感谢支持' }
],
noticeList: ['寒雨连江夜入吴', '平明送客楚山孤', '洛阳亲友如相问', '一片冰心在玉壶'],
productList: [
{
id: 1,
image: 'https://cdn.uviewui.com/uview/album/1.jpg',
title: '山不在高,有仙则名。水不在深,有龙则灵。斯是陋室,惟吾德馨。',
desc: '山不在于高,有了神仙就会有名气。水不在于深,有了龙就会有灵气。这是简陋的房子,只是我品德好就感觉不到简陋了。',
price: '13.00'
},
{
id: 2,
image: 'https://cdn.uviewui.com/uview/album/2.jpg',
title: '商品222',
desc: '',
price: '23.00'
},
{
id: 3,
image: 'https://cdn.uviewui.com/uview/album/3.jpg',
title: '商品333',
desc: '商品描述信息2',
price: '33.00'
},
{
id: 4,
image: 'https://cdn.uviewui.com/uview/album/4.jpg',
title: '商品444',
desc: '商品描述信息4',
price: '43.00'
},
{
id: 5,
image: 'https://cdn.uviewui.com/uview/album/5.jpg',
title: '商品555',
desc: '商品描述信息5',
price: '53.00'
}
],
moreStatus: 'nomore'
}
},
onLoad() {
this.loadBannerData()
this.loadNoticeData()
},
methods: {
loadBannerData() {
getBannerData().then(res => {
this.bannerList = res.data
})
},
loadNoticeData() {
getNoticeData().then(res => {
this.noticeList = res.data
})
},
handleSearchClick(e) {
uni.$u.route('/pages/search/search')
}
},
computed: {
noticeTextList() {
return this.noticeList.map(item => {
if (item.title) {
return item.title
}
})
}
}
}
</script>
<style lang="scss" scoped>
.search-wrap {
background: $custom-bg-color;
padding: 20rpx;
}
.grid-title {
line-height: 50rpx;
font-size: 26rpx;
}
</style>

@ -0,0 +1,198 @@
<template>
<view class="container">
<view class="auth-header">
<view class="auth-logo">
<u-avatar size="100" icon="github-circle-fill" fontSize="100"></u-avatar>
</view>
</view>
<view class="auth-box">
<!-- 登录方式选择 -->
<view class="mode-section">
<u-subsection class="subsection" mode="subsection" fontSize="15" :list="loginModeList" :current="currentModeIndex" @change="handleModeChange"></u-subsection>
</view>
<u-gap height="40"></u-gap>
<!-- 登录表单 -->
<u--form labelPosition="left" :model="formData" :rules="rules" ref="form">
<u-form-item label="手机号" prop="mobile" labelWidth="60" borderBottom ref="item-mobile">
<u-input type="number" maxlength="11" v-model="formData.mobile" clearable placeholder="请填写手机号" border="none"></u-input>
</u-form-item>
<u-gap height="20"></u-gap>
<u-form-item v-if="currentModeIndex === 0" label="密码" prop="password" labelWidth="60" borderBottom ref="item-password">
<u-input :type="inputType" maxlength="16" v-model="formData.password" placeholder="请填写密码" border="none">
<template slot="suffix">
<u-icon v-if="inputType === 'password'" size="20" color="#666666" name="eye-fill" @click="inputType = 'text'"></u-icon>
<u-icon v-if="inputType === 'text'" size="20" color="#666666" name="eye-off" @click="inputType = 'password'"></u-icon>
</template>
</u-input>
</u-form-item>
<u-form-item v-else label="验证码" prop="code" labelWidth="60" borderBottom>
<u--input type="number" maxlength="4" v-model="formData.code" border="none" placeholder="请填写验证码"></u--input>
<u-button slot="right" @tap="getCode" :text="codeTips" type="success" size="mini" :disabled="codeDisabled"></u-button>
<u-code ref="uCode" @change="codeChange" seconds="60" @start="codeDisabled = true" @end="codeDisabled = false"></u-code>
</u-form-item>
<view class="btn-group">
<u-button class="auth-btn" type="primary" customStyle="margin-top: 50px" @click="handleSubmit"></u-button>
</view>
</u--form>
</view>
</view>
</template>
<script>
import { sendSmsCode } from '../../api/auth'
export default {
data() {
return {
currentModeIndex: 0,
loginModeList: ['密码登录', '验证码登录'],
inputType: 'password',
codeDisabled: false,
codeTips: '',
formData: {
mobile: '',
password: '',
code: ''
},
rules: {
mobile: [
{
type: 'integer',
required: true,
message: '请填写手机号',
trigger: ['blur', 'change']
},
{
//
validator: (rule, value, callback) => {
// truefalse
// uni.$u.test.mobile()truefalse
return uni.$u.test.mobile(value)
},
message: '手机号码不正确',
// blurchange
trigger: ['change', 'blur']
}
],
password: {
type: 'string',
min: 4,
max: 16,
required: true,
message: '密码长度4-16位密码',
trigger: ['blur', 'change']
},
code: {
type: 'integer',
len: 4,
required: true,
message: '请填写4位验证码',
trigger: ['blur', 'change']
}
}
}
},
onLoad() {},
onReady() {
// setRules
this.$refs.form.setRules(this.rules)
},
methods: {
handleModeChange(index) {
if (index !== this.currentModeIndex) {
this.currentModeIndex = index
this.$refs.form.clearValidate()
}
},
codeChange(text) {
this.codeTips = text
},
getCode() {
const mobile = this.formData.mobile
if (!mobile) {
uni.$u.toast('请填写手机号')
} else if (!uni.$u.test.mobile(mobile)) {
uni.$u.toast('手机号格式不正确')
} else if (this.$refs.uCode.canGetCode) {
//
uni.showLoading({
title: '正在获取验证码'
})
//scene:1
sendSmsCode({ mobile: mobile, scene: 1 }).then(res => {
//console.log(res)
uni.hideLoading()
uni.$u.toast('验证码已发送')
//
this.$refs.uCode.start()
})
} else {
uni.$u.toast('倒计时结束后再发送')
}
},
handleSubmit() {
this.$refs.form.validate().then(res => {
console.log(123456);
uni.login({
provider: 'weixin',
success: res => {
let data = this.formData
data.socialType = 34 //WECHAT_MINI_APP
data.socialCode = res.code
data.socialState = Math.random() //
this.mobileLogin(data)
},
fail: res => {
this.mobileLogin(this.formData)
}
})
})
},
mobileLogin(data){
this.$store.dispatch('Login', { type: this.currentModeIndex, data: data }).then(res => {
uni.$u.toast('登录成功')
setTimeout(() => {
uni.switchTab({
url: '/pages/user/user'
})
}, 300)
})
}
}
}
</script>
<style lang="scss" scoped>
.auth-header {
height: 400rpx;
@include flex-center;
.auth-logo {
@include flex-center(column);
}
}
.auth-box {
@include flex-center(column);
.mode-section {
width: 600rpx;
.subsection {
height: 60rpx;
}
}
.btn-group {
width: 600rpx;
.auth-btn {
height: 90rpx;
font-size: 32rpx;
}
}
}
</style>

@ -0,0 +1,117 @@
<template>
<view class="container">
<view class="auth-header">
<view class="auth-logo">
<u-avatar size="100" icon="github-circle-fill" fontSize="100"></u-avatar>
</view>
</view>
<view class="auth-box">
<view class="btn-group">
<!-- #ifdef MP-WEIXIN -->
<u-button class="auth-btn" open-type="getPhoneNumber" type="primary" @getphonenumber="getPhoneNumber"></u-button>
<navigator class="reg-login-link" url="/pages/login/mobile" open-type="navigate" hover-class="none">手机号登录/注册 &gt;</navigator>
<!-- #endif -->
<!-- #ifndef MP-WEIXIN -->
<u-button type="primary" text="手机号登录/注册" @click="handleJump"></u-button>
<!-- #endif -->
</view>
<view class="auth-footer">
<view>登录即表示同意<text class="lk-text">用户协议</text> <text class="lk-text">隐私政策</text></view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {}
},
onLoad() {},
onReady() {},
methods: {
getPhoneNumber(e) {
let phoneCode = e.detail.code
if (!e.detail.code) {
uni.showModal({
title: '授权失败',
content: '您已拒绝获取绑定手机号登录授权,可以使用其他手机号验证登录',
cancelText: '知道了',
confirmText: '验证登录',
confirmColor: '#3C9CFFFF',
success: res => {
if (res.confirm) {
uni.$u.route('/pages/login/mobile')
} else if (res.cancel) {
//console.log('')
}
}
})
} else {
uni.login({
provider: 'weixin',
success: res => {
this.$store.dispatch('Login', { type: 2, data: { phoneCode: phoneCode, loginCode: res.code } }).then(res => {
uni.$u.toast('登录成功')
setTimeout(() => {
uni.switchTab({
url: '/pages/user/user'
})
}, 300)
})
}
})
}
},
handleJump() {
uni.$u.route('/pages/login/mobile')
}
}
}
</script>
<style lang="scss" scoped>
.container {
height: calc(100vh - 70px);
@include flex-space-between(column);
}
.auth-header {
flex: 2;
@include flex-center;
.auth-logo {
@include flex-center(column);
}
}
.auth-box {
@include flex-center(column);
.btn-group {
width: 600rpx;
margin-bottom: 200rpx;
.auth-btn {
height: 90rpx;
font-size: 32rpx;
}
}
.reg-login-link {
margin-top: 32rpx;
text-align: center;
color: #636363;
font-size: 30rpx;
}
.auth-footer {
font-size: 26rpx;
color: #939393;
.lk-text {
color: $u-primary;
text-decoration: $u-primary;
}
}
}
</style>

@ -0,0 +1,63 @@
<template>
<view class="container">
<view class="confirm-con">
<!-- TODO 收货地址 -->
<navigator url="/pages/address/list?type=1">
<yd-address-select :address="address"></yd-address-select>
</navigator>
<!-- TODO 商品信息 -->
<view class="goods contain">
<yd-order-product :productList="productList"></yd-order-product>
<view class="item row-between">
<view>买家留言</view>
<u-input v-model="remark" :clearable="false"
placeholder="请添加备注150 字以内)"></u-input>
</view>
</view>
</view>
<!-- TODO 优惠劵 -->
<!-- TODO 价格信息 -->
<!-- TODO 底部提交订单 -->
</view>
</template>
<script>
export default {
data() {
return {
address: { //
name: 'test',
mobile: '15601691300',
area: '奥特曼奥特曼'
},
productList: [{ //
coverUrl: '',
productTitle: '奥特曼',
sellPrice: 1024,
productCount: 2048,
totalPrice: 1024
}],
remark: '', //
x
}
}
}
</script>
<style>
.confirm-con {
overflow: hidden;
padding-bottom: calc(120rpx + env(safe-area-inset-bottom));
}
.contain {
border-radius: 14rpx;
margin: 20rpx 20rpx 0;
background-color: #fff;
overflow: hidden;
}
</style>

@ -0,0 +1,252 @@
<template>
<view class="container">
<viwe class="detail-header">
<view class="order-status">{{ order.status | getStatusName }}</view>
</viwe>
<view class="order-product">
<view class="order-item" v-for="item in order.items" :key="item.id">
<image class="product-image" :src="item.picUrl"></image>
<view class="item-info">
<view class="info-text">
<u--text :lines="1" size="15px" color="#333333" :text="item.spuName"></u--text>
<u-gap height="10"></u-gap>
<yd-text-price class="product-price" size="13" intSize="14" :price="item.originalUnitPrice"></yd-text-price>
</view>
<view class="price-number-box">
<view class="number-box">
<view class="product-number"> {{ item.count }} </view>
小计
</view>
<view class="number-box" @click.stop>
<yd-text-price size="13" intSize="16" :price="item.originalPrice"></yd-text-price>
</view>
</view>
</view>
</view>
</view>
<view v-if="order.no" >
<view v-if="order.no" class="base-info">
<view class="info-item">
<view class="item-name">订单编号</view>
<view class="item-value">{{ order.no }}</view>
</view>
<view class="info-item">
<view class="item-name">下单时间</view>
<view class="item-value">{{ order.createTime }}</view>
</view>
<view v-if="order.payOrderId" class="info-item">
<view class="item-name">支付方式</view>
<view class="item-value">{{ order.payOrderId }}</view>
</view>
<view v-if="order.payTime" class="info-item">
<view class="item-name">支付时间</view>
<view class="item-value">{{ order.payTime }}</view>
</view>
</view>
<view class="delivery-info">
<view class="info-item">
<view class="item-name">收货地址</view>
<view class="item-value">{{ order.receiverAreaName + order.receiverDetailAddress + order.receiverDetailAddress + order.receiverDetailAddress }}</view>
</view>
<view v-if="order.receiverName" class="info-item">
<view class="item-name">收货人</view>
<view class="item-value">{{ order.receiverName }}</view>
</view>
<view v-if="order.receiverMobile" class="info-item">
<view class="item-name">联系电话</view>
<view class="item-value">{{ order.receiverMobile }}</view>
</view>
</view>
<view class="delivery-info">
<view class="info-item">
<view class="item-name">商品总额</view>
<yd-text-price class="product-price" size="13" intSize="16" :price="order.originalPrice"></yd-text-price>
</view>
<view class="info-item">
<view class="item-name">运费</view>
<yd-text-price class="product-price" size="13" intSize="16" :price="order.deliveryPrice"></yd-text-price>
</view>
<view class="info-item">
<view class="item-name">优惠</view>
<yd-text-price class="product-price" size="13" intSize="16" symbol="-¥" :price="order.discountPrice"></yd-text-price>
</view>
<view class="info-item">
<view class="item-name">订单金额</view>
<yd-text-price class="product-price" size="15" intSize="20" :price="order.orderPrice"></yd-text-price>
</view>
</view>
</view>
</view>
</template>
<script>
import { getOrderDetail } from '../../api/order'
import orderStatus from '@/common/orderStatus'
export default {
name: 'orderDetail',
filters: {
getStatusName(status) {
return orderStatus[status + ''].name
}
},
data() {
return {
orderId: undefined,
order: {}
}
},
onLoad(e) {
this.orderId = e.orderId
if (!this.orderId) {
uni.$u.toast('请求参数错误')
} else {
this.loadOrderDetailData()
}
},
methods: {
loadOrderDetailData() {
getOrderDetail({ id: this.orderId })
.then(res => {
this.order = res.data || {}
})
.catch(err => {
console.log(err)
})
}
}
}
</script>
<style lang="scss" scoped>
.container {
background-color: #f3f3f3;
height: 100vh;
}
.detail-header {
@include flex-center();
background-color: $custom-bg-color;
padding: 10rpx 0;
border-radius: 0 0 20rpx 20rpx;
}
.order-product {
background-color: $custom-bg-color;
border-radius: 20rpx;
margin: 20rpx;
padding: 10rpx 20rpx;
.order-item {
background: #ffffff;
@include flex-space-between;
padding: 10rpx 0 10rpx 5rpx;
.product-check {
padding: 20rpx;
.un-check-box {
width: 20px;
height: 20px;
border: 1px solid #939393;
border-radius: 50%;
}
}
.product-image {
width: 180rpx;
height: 180rpx;
border-radius: 10rpx;
}
.item-info {
flex: 1;
padding: 0 20rpx;
.info-text {
padding-bottom: 10rpx;
.product-price {
margin-top: 15rpx;
}
}
.price-number-box {
@include flex-space-between;
.number-box {
font-size: 24rpx;
.product-number {
width: 200rpx;
}
}
.number-box {
height: 60rpx;
@include flex-center;
}
}
}
}
}
.base-info {
background-color: $custom-bg-color;
border-radius: 20rpx;
margin: 20rpx;
padding: 20rpx;
.info-item {
@include flex-left();
padding: 10rpx 0;
.item-name {
color: #999;
font-size: 26rpx;
}
.item-value {
color: #333;
font-size: 26rpx;
}
}
}
.delivery-info {
background-color: $custom-bg-color;
border-radius: 20rpx;
margin: 20rpx;
padding: 20rpx;
.info-item {
@include flex-left();
padding: 10rpx 0;
.item-name {
color: #999;
font-size: 26rpx;
width: 260rpx;
}
.item-value {
color: #333;
font-size: 26rpx;
}
}
}
.delivery-info {
background-color: $custom-bg-color;
border-radius: 20rpx;
margin: 20rpx;
padding: 20rpx;
.info-item {
@include flex-space-between();
padding: 10rpx 0;
.item-name {
color: #999;
font-size: 26rpx;
}
}
}
</style>

@ -0,0 +1,195 @@
<template>
<view class="container">
<u-sticky style="top: 0" offset-top="0">
<u-tabs :list="tabArray" :current="tabIndex" itemStyle="padding-left: 18px; padding-right: 18px; height: 36px;" @change="handleStatusChange"></u-tabs>
</u-sticky>
<view class="order-list">
<view v-for="order in orderList" :key="order.no" class="order-item">
<view class="order-header">
<view class="order-no">订单编号{{ order.no }}</view>
<view class="order-status">{{ order.status | getStatusName }}</view>
</view>
<view v-if="order.items.length === 1" class="order-single-item" @click="handleOrderClick(order.id)">
<view class="item-wrap" v-for="item in order.items" :key="item.id">
<view class="item-info">
<image class="item-cover" :src="item.picUrl"></image>
<u--text :lines="2" size="15px" color="#333333" :text="item.spuName"></u--text>
</view>
<view class="item-count">{{ item.count }}</view>
</view>
</view>
<view v-else class="order-multi-item" @click="handleOrderClick(order.id)">
<u-scroll-list :indicator="false">
<view class="item-wrap" v-for="item in order.items" :key="item.id">
<image class="item-image" :src="item.picUrl"></image>
</view>
</u-scroll-list>
<view class="product-count">{{ order.productCount }}</view>
</view>
<view class="order-btn-group">
<view class="order-btn">再次购买</view>
<view class="order-btn">其他操作</view>
</view>
</view>
</view>
</view>
</template>
<script>
import { getOrderPage } from '../../api/order'
import orderStatus from '@/common/orderStatus'
export default {
name: 'orderList',
filters: {
getStatusName(status) {
return orderStatus[status + ''].name
}
},
data() {
return {
pageNo: 1,
tabIndex: 0,
orderList: []
}
},
computed: {
tabArray() {
let tabArray = [{ name: '全部', status: 'all' }]
for (let status in orderStatus) {
if (status !== '40') {
tabArray.push({ name: orderStatus[status].name, status: status })
}
}
return tabArray
}
},
onLoad(e) {
const status = e.status
if (status !== undefined) {
this.tabArray.forEach((item, index) => {
if (item.status === status) {
this.tabIndex = index
}
})
}
this.loadOrderPageData()
},
methods: {
handleStatusChange({ index }) {
this.tabIndex = index
this.loadOrderPageData()
},
loadOrderPageData() {
let params = { pageNo: this.pageNo }
const status = this.tabArray[this.tabIndex].status
if (status !== 'all') {
params.orderStatus = status
}
getOrderPage(params)
.then(res => {
this.orderList = res.data.list || []
})
.catch(err => {
console.log(err)
})
},
handleOrderClick(orderId) {
uni.$u.route('/pages/order/detail', {
orderId: orderId
})
}
}
}
</script>
<style lang="scss" scoped>
.order-list {
background-color: #f3f3f3;
.order-item {
padding: 20rpx;
background-color: #ffffff;
border-bottom: $custom-border-style;
.order-header {
@include flex-space-between;
height: 80rpx;
.order-no {
font-size: 28rpx;
color: #333;
}
.order-status {
font-size: 24rpx;
color: #999;
}
}
.order-single-item {
.item-wrap {
@include flex-space-between();
.item-info {
@include flex-left();
.item-cover {
width: 100rpx;
height: 100rpx;
border-radius: 10rpx;
margin-right: 15rpx;
}
}
.item-count {
color: #999;
font-size: 24rpx;
width: 120rpx;
text-align: right;
}
}
}
.order-multi-item {
@include flex-space-between();
.item-wrap {
margin-right: 20rpx;
.item-image {
width: 100rpx;
height: 100rpx;
border-radius: 10rpx;
}
}
.product-count {
color: #999;
font-size: 24rpx;
width: 120rpx;
text-align: right;
}
}
.order-btn-group {
margin-top: 10rpx;
@include flex-right();
.order-btn {
width: 120rpx;
height: 36rpx;
line-height: 36rpx;
border-radius: 36rpx;
border: 1px solid #777;
color: #777;
font-size: 22rpx;
text-align: center;
margin-left: 15rpx;
}
}
}
}
</style>

@ -0,0 +1,576 @@
<template>
<view class="container">
<!-- 商品轮播图 -->
<u-swiper :list="spu.picUrls" @change="e => (currentNum = e.current)" :autoplay="false"
height="750rpx" radius="0" indicatorStyle="right: 20px">
<view slot="indicator" class="indicator-num">
<text class="indicator-num__text">{{ currentNum + 1 }}/{{ spu.picUrls.length }}</text>
</view>
</u-swiper>
<view class="product-box">
<!-- TODO @Sfmind样式讨论要不要改成类似 likeshop 的样子第一栏价格 + 分享第二栏商品名第三栏库存销量浏览量 -->
<view class="prod-info">
<view class="info-text">
<u--text :lines="2" size="14px" color="#333333" :text="spu.name"></u--text>
</view>
<view class="price-and-cart">
<!-- TODO @Sfmindcustom-text-price 会报错 -->
<custom-text-price color="red" size="16" intSize="26" :price="spu.minPrice"></custom-text-price>
</view>
</view>
<view class="prod-favor">
<u-icon name="star" color="#2979ff" size="28"></u-icon>
</view>
</view>
<u-gap height="8" bgColor="#f3f3f3"></u-gap>
<view class="row-box">
<view class="row-left">规格</view>
<view class="row-right" @click="skuPopup = true">
<view class="row-content">
<view class="sku-box">
<view v-if="spu.skus.length > 0" class="sku-item">
<view class="sku-desc">{{ spu.skus[currentSkuIndex].desc }}</view>
</view>
</view>
</view>
<view class="row-more">
<u-icon name="more-dot-fill" color="#939393" size="14"></u-icon>
</view>
</view>
</view>
<!-- 商品 SKU 选择弹窗 -->
<u-popup :show="skuPopup" :round="10" :closeable="true" :closeOnClickOverlay="false" @close="skuPopup = false">
<view class="sku-popup-slot">
<view class="current-sku-info">
<u--image class="current-sku-img" :showLoading="true" :src="spu.skus[currentSkuIndex].picUrl"
width="120rpx" height="120rpx"></u--image>
<view class="current-sku-desc">
<!-- TODO @Sfmind:name 这里的选择规格值的拼接 -->
<view class="name">{{ spu.skus[currentSkuIndex].desc }}</view>
<custom-text-price color="red" size="12" intSize="18" :price="spu.skus[currentSkuIndex].price"></custom-text-price>
<view class="current-sku-stock">库存 {{ 1 }}</view>
</view>
</view>
<view class="sku-selection">
<!-- TODO @Sfmind:name 这里是规格的具体选项 -->
<view class="sku-item" :class="{ active: currentSkuIndex === index }" v-for="(item, index) in spu.skus"
:key="item.id" @click="handleSkuItemClick(index)">{{ item.desc }}</view>
</view>
<view class="sku-num-box">
<view class="text">选择数量</view>
<u-number-box integer></u-number-box>
</view>
<view class="sku-btn-group">
<view class="btn-item-main">
<u-button type="warning" shape="circle" size="small" text="加入购物车"></u-button>
</view>
<view class="btn-item-main">
<u-button type="error" shape="circle" size="small" text="立即购买"></u-button>
</view>
</view>
</view>
</u-popup>
<u-gap height="8" bgColor="#f3f3f3"></u-gap>
<view class="row-box">
<view class="row-left">促销</view>
<view class="row-right" v-if="promotionList.length > 0" @click="promotionPopup = true">
<view class="row-content">
<view class="prom-box">
<view class="prom-item">
<view class="prom-title">{{ promotionList[0].title }}</view>
<text class="prom-desc">{{ promotionList[0].desc }}</text>
</view>
</view>
</view>
<view class="row-more">
<u-icon name="more-dot-fill" color="#939393" size="14"></u-icon>
</view>
</view>
</view>
<!-- 促销信息弹窗 -->
<u-popup :show="promotionPopup" :round="10" :closeable="true" :closeOnClickOverlay="false" @close="promotionPopup = false">
<view class="prom-popup-slot">
<view class="prom-info">促销信息</view>
<view class="prom-list">
<view v-for="(item, index) in promotionList" :key="index.id" class="prom-item">
<view class="prom-title">{{ item.title }}</view>
<text class="prom-desc">{{ item.desc }}</text>
</view>
</view>
</view>
</u-popup>
<u-gap height="8" bgColor="#f3f3f3"></u-gap>
<view class="row-box">
<view class="row-left">领券</view>
<view class="row-right" @click="handleCouponClick">
<view class="row-content">
<view class="coupon-box">
<view v-if="couponList.length > 0" class="coupon-list">
<view v-for="(item, index) in couponList" :key="item.id" class="coupon-item">
<view v-if="index < 2" class="coupon-desc">{{ item.desc }}</view>
</view>
</view>
<view class="coupon-total"> {{ couponList.length }} </view>
</view>
</view>
<view class="row-more">
<u-icon name="more-dot-fill" color="#939393" size="14"></u-icon>
</view>
</view>
</view>
<u-gap height="8" bgColor="#f3f3f3"></u-gap>
<view class="evaluation-box-wrap">
<view class="evaluation-box">
<view class="evaluation-title">评价</view>
<view class="evaluation-info">
<view class="evan-type-list">
<view class="evan-type-item" :class="{ active: currentEvanIndex === index }" v-for="(item, index) in evanTypeList" :key="item.id" @click="handleEvanTypeClick(index)"> {{ item.name }}({{ item.count }}) </view>
</view>
<view class="comment-empty" v-if="true">
<u-empty mode="comment" width="350rpx" height="350rpx" icon="/static/images/empty/comment.png"></u-empty>
</view>
<view v-else class="comment-list" style="min-height: 50px"> </view>
</view>
</view>
</view>
<!-- TODO @Sfmind:缺个商品详情 -->
<view class="fixed-btn-box">
<view class="btn-group">
<navigator class="btn-item" url="/pages/index/index" open-type="switchTab" hover-class="none">
<u-icon name="home" :size="24"></u-icon>
<view class="btn-text">首页</view>
</navigator>
<navigator class="btn-item" url="/pages/xxx/xxx" open-type="navigate" hover-class="none">
<u-icon name="server-man" :size="24"></u-icon>
<view class="btn-text">客服</view>
</navigator>
<!-- TODO @Sfmind:改成收藏 -->
<navigator class="btn-item" url="/pages/cart/cart" open-type="switchTab" hover-class="none">
<u-icon name="star" :size="24"></u-icon>
<view class="btn-text">收藏</view>
</navigator>
<view class="btn-item-main">
<u-button type="warning" shape="circle" size="small" text="加入购物车"></u-button>
</view>
<view class="btn-item-main">
<u-button type="error" color="#ea322b" shape="circle" size="small" text="立即购买"></u-button>
</view>
</view>
<u-safe-bottom customStyle="background: #ffffff"></u-safe-bottom>
</view>
</view>
</template>
<script>
import { getSpuDetail } from '../../api/product';
export default {
data() {
return {
current: 0,
currentNum: 0,
currentSkuIndex: 0,
skuPopup: false,
spu: {
id: '',
picUrls: [],
minPrice: '13.00',
sku: [
{
id: 0,
picUrl: 'https://cdn.uviewui.com/uview/album/1.jpg',
price: 13.0,
desc: '山不在高,有仙则名。'
},
{
id: 1,
picUrl: 'https://cdn.uviewui.com/uview/album/2.jpg',
price: 11.0,
desc: '水不在深,有龙则灵。'
},
{
id: 2,
picUrl: 'https://cdn.uviewui.com/uview/album/3.jpg',
price: 10.0,
desc: '斯是陋室,惟吾德馨。'
}
]
},
promotionPopup: false,
promotionList: [
{
id: 0,
title: '满额减',
desc: '全场满500减100'
},
{
id: 1,
title: '满额减',
desc: '全场满300减50'
},
{
id: 2,
title: '满额减',
desc: '全场满200减20'
},
{
id: 3,
title: '满额减',
desc: '全场满100减5'
}
],
couponList: [
{
id: 0,
title: '优惠券',
desc: '满50减10'
},
{
id: 1,
title: '优惠券',
desc: '满30减5'
},
{
id: 2,
title: '优惠券',
desc: '满20减2'
}
],
currentEvanIndex: 0,
evanTypeList: [
{
id: '0',
name: '全部',
count: 0
},
{
id: '1',
name: '好评',
count: 0
},
{
id: '2',
name: '中评',
count: 0
},
{
id: '3',
name: '差评',
count: 0
},
{
id: '4',
name: '有图',
count: 0
}
]
}
},
onLoad(e) {
if (!e.id) {
uni.$u.toast('请求参数错误')
return;
}
//
this.spu.id = e.id
this.loadProductData()
},
methods: {
loadProductData() {
getSpuDetail(this.spu.id).then(res => {
// this.spu.desc = res.data.description.replace(/<[^>]*>/g,'');
// console.log(res)
this.spu = res.data;
})
},
handleSkuItemClick(index) {
this.currentSkuIndex = index
},
handleCouponClick() {
// TODO
},
handleEvanTypeClick(index) {
this.currentEvanIndex = index
// TODO
}
},
computed: {
hasLogin() {
return this.$store.getters.hasLogin
}
}
}
</script>
<style lang="scss" scoped>
.indicator-num {
@include flex-center;
padding: 2px 0;
background-color: rgba(0, 0, 0, 0.35);
border-radius: 100px;
width: 35px;
&__text {
color: #ffffff;
font-size: 12px;
}
}
.product-box {
padding: 40rpx 40rpx 10rpx 40rpx;
@include flex;
border-bottom: $custom-border-style;
.prod-info {
padding-right: 30rpx;
.info-text {
padding-bottom: 10rpx;
}
.price-and-cart {
@include flex-space-between;
}
}
.prod-favor {
margin-top: 15rpx;
}
}
.row-box {
@include flex-left;
padding: 0 30rpx;
height: 70rpx;
.row-left {
width: 70rpx;
font-size: 24rpx;
color: #939393;
}
.row-right {
@include flex-space-between;
flex: 1;
.row-content {
flex: 1;
.prom-box {
@include flex-left;
.prom-item {
@include flex-left;
font-size: 22rpx;
.prom-title {
padding: 1rpx 10rpx;
border: 1rpx solid red;
border-radius: 5rpx;
color: red;
transform: scale(0.9);
}
.prom-desc {
margin-left: 15rpx;
}
}
}
.coupon-box {
@include flex-space-between;
.coupon-list {
@include flex-left;
.coupon-item {
@include flex-left;
font-size: 22rpx;
.coupon-desc {
padding: 2rpx 15rpx;
margin-right: 15rpx;
background: red;
color: #ffffff;
}
}
}
.coupon-total {
color: #939393;
font-size: 12rpx;
padding: 0 15rpx;
}
}
.sku-box {
@include flex-space-between;
.sku-item {
@include flex-left;
font-size: 22rpx;
.sku-desc {
margin-left: 15rpx;
font-weight: 700;
}
}
}
}
.row-more {
@include flex-right;
width: 30rpx;
}
}
}
.sku-popup-slot {
width: 750rpx;
.current-sku-info {
@include flex;
padding: 30rpx 100rpx 0 30rpx;
.current-sku-img {
border-radius: 10rpx;
/deep/ * {
border-radius: 10rpx;
}
}
.current-sku-desc {
padding: 0 30rpx;
font-size: 28rpx;
.current-sku-stock {
height: 40rpx;
line-height: 40rpx;
color: #666666;
font-size: 24rpx;
}
}
}
.sku-selection {
margin: 30rpx;
font-size: 26rpx;
color: #939393;
.sku-item {
margin-bottom: 20rpx;
border-radius: 6rpx;
padding: 5rpx 15rpx;
border: 1rpx solid #e3e3e3;
width: fit-content !important;
&.active {
color: #666666;
border: 1rpx solid #666666;
}
}
}
.sku-num-box {
@include flex-space-between padding: 30rpx;
.text {
font-size: 30rpx;
}
}
.sku-btn-group {
@include flex-space-around;
height: 100rpx;
.btn-item-main {
width: 350rpx;
}
}
}
.prom-popup-slot {
width: 750rpx;
min-height: 500rpx;
.prom-info {
background: #f3f3f3;
line-height: 90rpx;
padding-left: 30rpx;
font-size: 36rpx;
border-radius: 10px 10px 0 0;
}
.prom-list {
padding: 30rpx;
.prom-item {
@include flex-left;
font-size: 22rpx;
margin-bottom: 15rpx;
.prom-title {
padding: 1rpx 10rpx;
border: 1rpx solid red;
border-radius: 5rpx;
color: red;
transform: scale(0.9);
}
.prom-desc {
margin-left: 15rpx;
}
}
}
}
.evaluation-box-wrap {
background: #f3f3f3;
.evaluation-box {
border-radius: 20rpx 20rpx 0 0;
background: $custom-bg-color;
padding-bottom: 120rpx;
.evaluation-title {
border-radius: 20rpx 20rpx 0 0;
padding: 20rpx 30rpx;
border-bottom: $custom-border-style;
font-size: 30rpx;
}
.evaluation-info {
}
.evan-type-list {
padding: 20rpx;
@include flex-space-around;
.evan-type-item {
border-radius: 8rpx;
padding: 7rpx 12rpx;
background: #f3f3f3;
font-size: 22rpx;
text-align: center;
&.active {
background: #ffffff;
border: 1rpx solid red;
padding: 5rpx 10rpx;
color: red;
}
}
}
.comment-empty {
margin-bottom: 100rpx;
}
}
}
.fixed-btn-box {
position: fixed;
bottom: 0;
left: 0;
.btn-group {
background: $custom-bg-color;
border-top: $custom-border-style;
width: 750rpx;
@include flex-space-around;
height: 100rpx;
.btn-item {
width: 80rpx;
@include flex-center(column);
.btn-text {
font-size: 18rpx;
color: #666666;
}
}
.btn-item-main {
width: 200rpx;
}
}
}
</style>

@ -0,0 +1,128 @@
<template>
<view class="container">
<view class="user-info">
<view class="info-item">
<view class="label">头像</view>
<view class="info" @click="handleAvatarClick">
<u-avatar size="50" shape="square" :src="userInfo.avatar"></u-avatar>
<u-icon class="btn" name="arrow-right"></u-icon>
</view>
</view>
<view class="info-item">
<view class="label">昵称</view>
<view class="info">
<u--input maxlength="10" border="none" v-model="userInfo.nickname" inputAlign="right" @change="handleNameChange"></u--input>
</view>
</view>
<view class="info-item">
<view class="label">手机</view>
<view class="info">
<view class="value">{{ userInfo.mobile }}</view>
</view>
</view>
</view>
<view v-if="nameUpdateVisible" class="btn-group">
<u-button type="primary" text="保存" customStyle="margin-top: 50px" @click="handleSaveBtnClick"></u-button>
</view>
</view>
</template>
<script>
import { getUserInfo, updateAvatar, updateNickname } from '../../api/user'
export default {
data() {
return {
userInfo: {
nickname: '',
avatar: '',
mobile: ''
},
avatarFiles: [],
tempName: ''
}
},
computed: {
nameUpdateVisible: function () {
return this.userInfo.nickname !== this.tempName
}
},
onLoad() {
this.loadUserInfoData()
},
methods: {
loadUserInfoData() {
getUserInfo().then(res => {
this.userInfo = res.data
this.tempName = this.userInfo.nickname
})
},
handleAvatarClick() {
uni.chooseImage({
success: chooseImageRes => {
const tempFilePaths = chooseImageRes.tempFilePaths
updateAvatar(tempFilePaths[0]).then(res => {
this.userInfo.avatar = res.data
this.$store.commit('SET_USER_INFO', this.userInfo)
})
}
})
},
handleNameChange(val) {
let str = uni.$u.trim(val, 'all')
this.$nextTick(() => {
this.userInfo.nickname = str
})
},
handleSaveBtnClick() {
updateNickname({ nickname: this.userInfo.nickname }).then(res => {
this.tempName = this.userInfo.nickname
this.$store.commit('SET_USER_INFO', this.userInfo)
uni.$u.toast('已保存')
setTimeout(() => {
uni.switchTab({
url: '/pages/user/user'
})
}, 300)
})
}
}
}
</script>
<style lang="scss" scoped>
.user-info {
.info-item {
padding: 20rpx 60rpx;
border-bottom: $custom-border-style;
@include flex-space-between;
.label {
font-size: 30rpx;
}
.info {
@include flex-left;
.value {
font-size: 30rpx;
}
.btn {
margin-left: 30rpx;
}
}
.name-edit {
@include flex-left;
.edit-btn-group {
@include flex;
.edit-btn {
margin-left: 20rpx;
}
}
}
}
}
.btn-group {
padding: 0 30rpx;
}
</style>

@ -0,0 +1,19 @@
<template>
<view class="container">
搜索页面
</view>
</template>
<script>
export default {
data() {
return {
title: ''
}
},
onLoad() {},
methods: {}
}
</script>
<style lang="scss" scoped></style>

@ -0,0 +1,62 @@
<template>
<view class="container">
<u-gap height="20"></u-gap>
<u-cell-group class="setting-list" :border="false">
<u-cell class="setting-item" icon="lock" title="修改密码" isLink></u-cell>
<u-cell class="setting-item" icon="phone" title="换绑手机" isLink></u-cell>
<u-cell v-if="hasLogin" class="setting-item" icon="minus-circle" title="用户登出" @click="logout" isLink></u-cell>
</u-cell-group>
</view>
</template>
<script>
import UGap from '../../uni_modules/uview-ui/components/u-gap/u-gap'
export default {
components: { UGap },
data() {
return {}
},
computed: {
hasLogin() {
return this.$store.getters.hasLogin
}
},
onLoad() {},
methods: {
logout() {
uni.showModal({
title: '提示',
content: '您确定要退出登录吗',
success: res => {
if (res.confirm) {
this.$store.dispatch('Logout').then(res => {
uni.switchTab({
url: '/pages/user/user'
})
})
} else if (res.cancel) {
//console.log('')
}
}
})
}
}
}
</script>
<style lang="scss" scoped>
.setting-list {
padding: 10rpx 0;
background-color: #fff;
border-radius: 15rpx;
.setting-item {
padding: 10rpx 0;
&:last-child {
border-bottom: none;
}
}
}
</style>

@ -0,0 +1,203 @@
<template>
<view class="container">
<view class="user-header">
<view class="user-info" @click="pageRouter('/pages/profile/profile')">
<u-avatar size="60" shape="square" :src="userInfo.avatar"></u-avatar>
<view class="info-text">
<view class="user-nickname">{{ hasLogin ? userInfo.nickname || '会员用户' : '匿名用户' }}</view>
<view class="user-mobile">{{ hasLogin ? userInfo.mobile || ' ' : '登录/注册' }}</view>
</view>
</view>
<view class="user-setting">
<u-icon v-if="hasLogin" name="setting" color="#939393" size="22" @click="pageRouter('/pages/setting/setting')"></u-icon>
</view>
</view>
<u-gap height="10" bgColor="#f3f3f3"></u-gap>
<view>
<view class="order-header">
<text class="order-title">我的订单</text>
<view class="see-all" @click="pageRouter(orderPage, -1)">
<text>查看全部</text>
<u-icon name="arrow-right"></u-icon>
</view>
</view>
<view class="order-status-box">
<u-grid :border="false" :col="orderStatusList.length">
<u-grid-item v-for="(item, index) in orderStatusList" :key="index" @click="pageRouter(orderPage, item.status)">
<u-icon :name="item.icon" :size="32"></u-icon>
<text class="grid-title">{{ item.name }}</text>
</u-grid-item>
</u-grid>
</view>
</view>
<u-gap height="10" bgColor="#f3f3f3"></u-gap>
<view class="stat-box">
<u-grid :border="false" col="3">
<u-grid-item v-for="(item, index) in statList" :key="index">
<text class="grid-value">{{ item.value }}</text>
<text class="grid-title">{{ item.title }}</text>
</u-grid-item>
</u-grid>
</view>
<u-gap height="10" bgColor="#f3f3f3"></u-gap>
<u-cell-group class="fun-list">
<u-cell class="fun-item" :border="false" icon="gift" title="分销中心" isLink></u-cell>
<u-cell class="fun-item" :border="false" icon="tags" title="领券中心" isLink></u-cell>
<u-cell class="fun-item" :border="false" icon="coupon" title="我的优惠券" isLink></u-cell>
<u-cell class="fun-item" :border="false" icon="map" title="收货地址" @click="pageRouter('/pages/address/list')" isLink></u-cell>
</u-cell-group>
</view>
</template>
<script>
import orderStatus from '@/common/orderStatus'
export default {
data() {
return {
orderPage: '/pages/order/list',
statList: [
{ value: '0', title: '我的收藏' },
{ value: '0', title: '我的消息' },
{ value: '0', title: '我的足迹' }
]
}
},
onLoad() {
if (this.hasLogin) {
this.$store.dispatch('ObtainUserInfo')
}
},
computed: {
userInfo() {
return this.$store.getters.userInfo
},
hasLogin() {
return this.$store.getters.hasLogin
},
orderStatusList() {
let orderStatusList = []
for (let status in orderStatus) {
if (status !== '40') {
orderStatusList.push({ name: orderStatus[status].name, status: status, icon: orderStatus[status].icon })
}
}
return orderStatusList
}
},
methods: {
pageRouter(pageUrl, param) {
if (!this.hasLogin) {
uni.$u.route('/pages/login/social')
} else if (pageUrl === this.orderPage) {
uni.$u.route(this.orderPage, {
status: param
})
} else {
uni.$u.route(pageUrl)
}
},
logout() {
uni.showModal({
title: '提示',
content: '您确定要退出登录吗',
success: res => {
if (res.confirm) {
this.$store.dispatch('Logout')
} else if (res.cancel) {
//console.log('')
}
}
})
}
}
}
</script>
<style lang="scss" scoped>
.user-header {
background-color: #fff;
@include flex-space-between;
padding: 30rpx;
height: 200rpx;
.user-info {
@include flex-left;
align-items: center;
.info-text {
margin-left: 20rpx;
.user-nickname {
font-size: 30rpx;
font-weight: 700;
line-height: 50rpx;
}
.user-mobile {
font-size: 24rpx;
font-weight: 700;
color: #939393;
line-height: 50rpx;
}
}
}
.user-setting {
margin-right: 5rpx;
}
}
.order-header {
@include flex-space-between;
padding: 20rpx 30rpx;
border-bottom: $custom-border-style;
.order-title {
color: #333333;
font-size: 34rpx;
}
.see-all {
height: 40rpx;
@include flex-right;
color: #666666;
font-size: 26rpx;
}
}
.order-status-box {
padding: 40rpx 0;
}
.stat-box {
padding: 20rpx 0;
}
.grid-title {
line-height: 50rpx;
font-size: 26rpx;
}
.grid-value {
line-height: 50rpx;
font-size: 36rpx;
font-weight: 700;
color: #2b85e4;
}
.fun-list {
.fun-item {
padding-top: 10rpx;
padding-bottom: 10rpx;
border-bottom: $custom-border-style;
}
}
</style>

@ -0,0 +1,7 @@
{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "allow",
"page": "*"
}]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

@ -0,0 +1,7 @@
const getters = {
accessToken: state => state.user.accessToken,
refreshToken: state => state.user.refreshToken,
userInfo: state => state.user.userInfo,
hasLogin: state => !!state.user.accessToken
}
export default getters

@ -0,0 +1,17 @@
import Vue from 'vue'
import Vuex from 'vuex'
import user from './mudules/user'
import cart from './mudules/cart'
import getters from './getters'
Vue.use(Vuex) // vue的插件机制
// Vuex.Store 构造器选项
const store = new Vuex.Store({
modules: {
user,
cart
},
getters
})
export default store

@ -0,0 +1,28 @@
import { getCartDetail } from '@/api/cart'
const cart = {
state: {
cartCount: 0
},
mutations: {
//记录购物车商品数量
SET_CART_COUNT(state, data) {
const arr = data.length || []
state.cartNumber = arr.length
}
},
actions: {
//获取购物车数据
CartProductDetail({ state, commit }) {
return getCartDetail()
.then(res => {
commit('SET_CART_COUNT', res.data)
return Promise.resolve(res)
})
.catch(err => {
return Promise.reject(err)
})
}
}
}
export default cart

@ -0,0 +1,101 @@
import { getUserInfo } from '@/api/user'
import { passwordLogin, smsLogin, weixinMiniAppLogin, logout } from '@/api/auth'
const AccessTokenKey = 'ACCESS_TOKEN'
const RefreshTokenKey = 'REFRESH_TOKEN'
const user = {
state: {
accessToken: uni.getStorageSync(AccessTokenKey), // 访问令牌
refreshToken: uni.getStorageSync(RefreshTokenKey), // 刷新令牌
userInfo: {}
},
mutations: {
// 更新 state 的通用方法
SET_STATE_ATTR(state, param) {
if (param instanceof Array) {
for (let item of param) {
state[item.key] = item.val
}
} else {
state[param.key] = param.val
}
},
// 更新令牌
SET_TOKEN(state, data) {
// 设置令牌
const { accessToken, refreshToken } = data
state.accessToken = accessToken
state.refreshToken = refreshToken
uni.setStorageSync(AccessTokenKey, accessToken)
uni.setStorageSync(RefreshTokenKey, refreshToken)
// 加载用户信息
this.dispatch('ObtainUserInfo')
},
// 更新用户信息
SET_USER_INFO(state, data) {
state.userInfo = data
},
// 清空令牌 和 用户信息
CLEAR_LOGIN_INFO(state) {
uni.removeStorageSync(AccessTokenKey)
uni.removeStorageSync(RefreshTokenKey)
state.accessToken = ''
state.refreshToken = ''
state.userInfo = {}
}
},
actions: {
//账号登录
Login({ state, commit }, { type, data }) {
if (type === 0) {
return passwordLogin(data)
.then(res => {
commit('SET_TOKEN', res.data)
return Promise.resolve(res)
})
.catch(err => {
return Promise.reject(err)
})
} else if (type === 1) {
return smsLogin(data)
.then(res => {
commit('SET_TOKEN', res.data)
return Promise.resolve(res)
})
.catch(err => {
return Promise.reject(err)
})
} else {
return weixinMiniAppLogin(data)
.then(res => {
commit('SET_TOKEN', res.data)
return Promise.resolve(res)
})
.catch(err => {
return Promise.reject(err)
})
}
},
// 退出登录
Logout({ state, commit }) {
return logout()
.then(res => {
return Promise.resolve(res)
})
.catch(err => {
return Promise.reject(err)
})
.finally(() => {
commit('CLEAR_LOGIN_INFO')
})
},
// 获得用户基本信息
async ObtainUserInfo({ state, commit }) {
const res = await getUserInfo()
commit('SET_USER_INFO', res.data)
}
}
}
export default user

@ -0,0 +1,5 @@
.row-between {
display: flex;
align-items: center;
justify-content: space-between;
}

@ -0,0 +1,60 @@
/**
* uni-app
*
* uni-app https://ext.dcloud.net.cn使
* 使scss使 import 便App
*
*/
/**
* App使
*
* 使scss scss 使 import
*/
/* 引入uView主题样式 */
@import '@/uni_modules/uview-ui/theme.scss';
/* 全局自定义scss变量 */
/* 页面背景颜色 */
$custom-bg-color: #ffffff;
/* 边框样式 */
$custom-border-style: 1rpx solid #f3f3f3;
@mixin flex-left($direction: row) {
display: flex;
flex-direction: $direction;
align-items: center;
justify-content: flex-start;
}
@mixin flex-right($direction: row) {
display: flex;
flex-direction: $direction;
align-items: center;
justify-content: flex-end;
}
@mixin flex-center($direction: row) {
display: flex;
flex-direction: $direction;
align-items: center;
justify-content: center;
}
@mixin flex-space-between($direction: row) {
display: flex;
flex-direction: $direction;
align-items: center;
justify-content: space-between;
}
@mixin flex-space-around($direction: row) {
display: flex;
flex-direction: $direction;
align-items: center;
justify-content: space-around;
}

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 www.uviewui.com
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

@ -0,0 +1,66 @@
<p align="center">
<img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
</p>
<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView 2.0</h3>
<h3 align="center">多平台快速开发的UI框架</h3>
[![stars](https://img.shields.io/github/stars/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
[![forks](https://img.shields.io/github/forks/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
[![issues](https://img.shields.io/github/issues/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0/issues)
[![Website](https://img.shields.io/badge/uView-up-blue?style=flat-square)](https://uviewui.com)
[![release](https://img.shields.io/github/v/release/umicro/uView2.0?style=flat-square)](https://gitee.com/umicro/uView2.0/releases)
[![license](https://img.shields.io/github/license/umicro/uView2.0?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
## 说明
uView UI是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架全面的组件和便捷的工具会让您信手拈来如鱼得水
## [官方文档https://uviewui.com](https://uviewui.com)
## 预览
您可以通过**微信**扫码,查看最佳的演示效果。
<br>
<br>
<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
## 链接
- [官方文档](https://www.uviewui.com/)
- [更新日志](https://www.uviewui.com/components/changelog.html)
- [升级指南](https://www.uviewui.com/components/changeGuide.html)
- [关于我们](https://www.uviewui.com/cooperation/about.html)
## 交流反馈
欢迎加入我们的QQ群交流反馈[点此跳转](https://www.uviewui.com/components/addQQGroup.html)
## 关于PR
> 我们非常乐意接受各位的优质PR但在此之前我希望您了解uView2.0是一个需要兼容多个平台的小程序、h5、ios app、android app包括nvue页面、vue页面。
> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢
## 安装
#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?id=1593](https://ext.dcloud.net.cn/plugin?id=1593)
请通过[官网安装文档](https://www.uviewui.com/components/install.html)了解更详细的内容
## 快速上手
请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
## 使用方法
配置easycom规则后自动按需引入无需`import`组件,直接引用即可。
```html
<template>
<u-button text="按钮"></u-button>
</template>
```
## 版权信息
uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议意味着您无需支付任何费用也无需授权即可将uView应用到您的产品中。

@ -0,0 +1,357 @@
## 2.0.342022-09-25
# uView2.0重磅发布,利剑出鞘,一统江湖
1. `u-input`、`u-textarea`增加`ignoreCompositionEvent`属性
2. 修复`route`方法调用可能报错的问题
3. 修复`u-no-network`组件`z-index`无效的问题
4. 修复`textarea`组件在h5上confirmType=""报错的问题
5. `u-rate`适配`nvue`
6. 优化验证手机号码的正则表达式(根据工信部发布的《电信网编号计划2017年版》进行修改。)
7. `form-item`添加`labelPosition`属性
8. `u-calendar`修复`maxDate`设置为当前日期并且当前时间大于0800时无法显示日期列表的问题 (#724)
9. `u-radio`增加一个默认插槽用于自定义修改label内容 (#680)
10. 修复`timeFormat`函数在safari重的兼容性问题 (#664)
## 2.0.332022-06-17
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复`loadmore`组件`lineColor`类型错误问题
2. 修复`u-parse`组件`imgtap`、`linktap`不生效问题
## 2.0.322022-06-16
# uView2.0重磅发布,利剑出鞘,一统江湖
1. `u-loadmore`新增自定义颜色、虚/实线
2. 修复`u-swiper-action`组件部分平台不能上下滑动的问题
3. 修复`u-list`回弹问题
4. 修复`notice-bar`组件动画在低端安卓机可能会抖动的问题
5. `u-loading-page`添加控制图标大小的属性`iconSize`
6. 修复`u-tooltip`组件`color`参数不生效的问题
7. 修复`u--input`组件使用`blur`事件输出为`undefined`的bug
8. `u-code-input`组件新增键盘弹起时,是否自动上推页面参数`adjustPosition`
9. 修复`image`组件`load`事件无回调对象问题
10. 修复`button`组件`loadingSize`设置无效问题
10. 其他修复
## 2.0.312022-04-19
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复`upload`在`vue`页面上传成功后没有成功标志的问题
2. 解决演示项目中微信小程序模拟上传图片一直出于上传中问题
3. 修复`u-code-input`组件在`nvue`页面编译到`app`平台上光标异常问题(`app`去除此功能)
4. 修复`actionSheet`组件标题关闭按钮点击事件名称错误的问题
5. 其他修复
## 2.0.302022-04-04
# uView2.0重磅发布,利剑出鞘,一统江湖
1. `u-rate`增加`readonly`属性
2. `tabs`滑块支持设置背景图片
3. 修复`u-subsection` `mode`为`subsection`时,滑块样式不正确的问题
4. `u-code-input`添加光标效果动画
5. 修复`popup`的`open`事件不触发
6. 修复`u-flex-column`无效的问题
7. 修复`u-datetime-picker`索引在特定场合异常问题
8. 修复`u-datetime-picker`最小时间字符串模板错误问题
9. `u-swiper`添加`m3u8`验证
10. `u-swiper`修改判断image和video逻辑
11. 修复`swiper`无法使用本地图片问题,增加`type`参数
12. 修复`u-row-notice`格式错误问题
13. 修复`u-switch`组件当`unit`为`rpx`时,`nodeStyle`消失的问题
14. 修复`datetime-picker`组件`showToolbar`与`visibleItemCount`属性无效的问题
15. 修复`upload`组件条件编译位置判断错误,导致`previewImage`属性设置为`false`时,整个组件都会被隐藏的问题
16. 修复`u-checkbox-group`设置`shape`属性无效的问题
17. 修复`u-upload`的`capture`传入字符串的时候不生效的问题
18. 修复`u-action-sheet`组件,关闭事件逻辑错误的问题
19. 修复`u-list`触顶事件的触发错误的问题
20. 修复`u-text`只有手机号可拨打的问题
21. 修复`u-textarea`不能换行的问题
22. 其他修复
## 2.0.292022-03-13
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复`u--text`组件设置`decoration`属性未生效的问题
2. 修复`u-datetime-picker`使用`formatter`后返回值不正确
3. 修复`u-datetime-picker` `intercept` 可能为undefined
4. 修复已设置单位 uni..config.unit = 'rpx'时,线型指示器 `transform` 的位置翻倍,导致指示器超出宽度
5. 修复mixin中bem方法生成的类名在支付宝和字节小程序中失效
6. 修复默认值传值为空的时候,打开`u-datetime-picker`报错不能选中第一列时间的bug
7. 修复`u-datetime-picker`使用`formatter`后返回值不正确
8. 修复`u-image`组件`loading`无效果的问题
9. 修复`config.unit`属性设为`rpx`时,导航栏占用高度不足导致塌陷的问题
10. 修复`u-datetime-picker`组件`itemHeight`无效问题
11. 其他修复
## 2.0.282022-02-22
# uView2.0重磅发布,利剑出鞘,一统江湖
1. search组件新增searchIconSize属性
2. 兼容Safari/Webkit中传入时间格式如2022-02-17 12:00:56
3. 修复text value.js 判断日期出format错误问题
4. priceFormat格式化金额出现精度错误
5. priceFormat在部分情况下出现精度损失问题
6. 优化表单rules提示
7. 修复avatar组件src为空时展示状态不对
8. 其他修复
## 2.0.272022-01-28
# uView2.0重磅发布,利剑出鞘,一统江湖
1.样式修复
## 2.0.262022-01-28
# uView2.0重磅发布,利剑出鞘,一统江湖
1.样式修复
## 2.0.252022-01-27
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复text组件mode=price时可能会导致精度错误的问题
2. 添加$u.setConfig()方法可设置uView内置的config, props, zIndex, color属性详见[修改uView内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
3. 优化form组件在errorType=toast时如果输入错误页面会有抖动的问题
4. 修复$u.addUnit()对配置默认单位可能无效的问题
## 2.0.242022-01-25
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复swiper在current指定非0时缩放有误
2. 修复u-icon添加stop属性的时候报错
3. 优化遗留的通过正则判断rpx单位的问题
4. 优化Layout布局 vue使用gutter时会超出固定区域
5. 优化search组件高度单位问题rpx -> px
6. 修复u-image slot 加载和错误的图片失去了高度
7. 修复u-index-list中footer插槽与header插槽存在性判断错误
8. 修复部分机型下u-popup关闭时会闪烁
9. 修复u-image在nvue-app下失去宽高
10. 修复u-popup运行报错
11. 修复u-tooltip报错
12. 修复box-sizing在app下的警告
13. 修复u-navbar在小程序中报运行时错误
14. 其他修复
## 2.0.232022-01-24
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复image组件在hx3.3.9的nvue下可能会显示异常的问题
2. 修复col组件gutter参数带rpx单位处理不正确的问题
3. 修复text组件单行时无法显示省略号的问题
4. navbar添加titleStyle参数
5. 升级到hx3.3.9可消除nvue下控制台样式警告的问题
## 2.0.222022-01-19
# uView2.0重磅发布,利剑出鞘,一统江湖
1. $u.page()方法优化,避免在特殊场景可能报错的问题
2. picker组件添加immediateChange参数
3. 新增$u.pages()方法
## 2.0.212022-01-19
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 优化form组件在用户设置rules的时候提示用户model必传
2. 优化遗留的通过正则判断rpx单位的问题
3. 修复微信小程序环境中tabbar组件开启safeAreaInsetBottom属性后placeholder高度填充不正确
4. 修复swiper在current指定非0时缩放有误
5. 修复u-icon添加stop属性的时候报错
6. 修复upload组件在accept=all的时候没有作用
7. 修复在text组件mode为phone时call属性无效的问题
8. 处理u-form clearValidate方法
9. 其他修复
## 2.0.202022-01-14
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复calendar默认会选择一个日期如果直接点确定的话无法取到值的问题
2. 修复Slider缺少disabled props 还有注释
3. 修复u-notice-bar点击事件无法拿到index索引值的问题
4. 修复u-collapse-item在vue文件下app端自定义插槽不生效的问题
5. 优化头像为空时显示默认头像
6. 修复图片地址赋值后判断加载状态为完成问题
7. 修复日历滚动到默认日期月份区域
8. search组件暴露点击左边icon事件
9. 修复u-form clearValidate方法不生效
10. upload h5端增加返回文件参数文件的name参数
11. 处理upload选择文件后url为blob类型无法预览的问题
12. u-code-input 修复输入框没有往左移出一半屏幕
13. 修复Upload上传 disabled为true时控制台报hoverClass类型错误
14. 临时处理ios app下grid点击坍塌问题
15. 其他修复
## 2.0.192021-12-29
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 优化微信小程序包体积可在微信中预览请升级HbuilderX3.3.4,同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码”
2. 优化微信小程序setData性能处理某些方法如$u.route()无法在模板中使用的问题
3. navbar添加autoBack参数
4. 允许avatar组件的事件冒泡
5. 修复cell组件报错问题
6. 其他修复
## 2.0.182021-12-28
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复app端编译报错问题
2. 重新处理微信小程序端setData过大的性能问题
3. 修复边框问题
4. 修复最大最小月份不大于0则没有数据出现的问题
5. 修复SwipeAction微信小程序端无法上下滑动问题
6. 修复input的placeholder在小程序端默认显示为true问题
7. 修复divider组件click事件无效问题
8. 修复u-code-input maxlength 属性值为 String 类型时显示异常
9. 修复当 grid只有 1到2时 在小程序端algin设置无效的问题
10. 处理form-item的label为top时取消错误提示的左边距
11. 其他修复
## 2.0.172021-12-26
## uView正在参与开源中国的“年度最佳项目”评选之前投过票的现在也可以投票恳请同学们投一票[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 解决HBuilderX3.3.3.20211225版本导致的样式问题
2. calendar日历添加monthNum参数
3. navbar添加center slot
## 2.0.162021-12-25
## uView正在参与开源中国的“年度最佳项目”评选之前投过票的现在也可以投票恳请同学们投一票[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 解决微信小程序setData性能问题
2. 修复count-down组件change事件不触发问题
## 2.0.152021-12-21
## uView正在参与开源中国的“年度最佳项目”评选之前投过票的现在也可以投票恳请同学们投一票[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复Cell单元格titleWidth无效
2. 修复cheakbox组件ischecked不更新
3. 修复keyboard是否显示"."按键默认值问题
4. 修复number-keyboard是否显示键盘的"."符号问题
5. 修复Input输入框 readonly无效
6. 修复u-avatar 导致打包app、H5时候报错问题
7. 修复Upload上传deletable无效
8. 修复upload当设置maxSize时无效的问题
9. 修复tabs lineWidth传入带单位的字符串的时候偏移量计算错误问题
10. 修复rate组件在有padding的view内显示的星星位置和可触摸区域不匹配无法正常选中星星
## 2.0.132021-12-14
## [点击加群交流反馈364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复配置默认单位为rpx可能会导致自定义导航栏高度异常的问题
## 2.0.122021-12-14
## [点击加群交流反馈364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复tabs组件在vue环境下划线消失的问题
2. 修复upload组件在安卓小程序无法选择视频的问题
3. 添加uni.$u.config.unit配置用于配置参数默认单位详见[默认单位配置](https://www.uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
4. 修复textarea组件在没绑定v-model时字符统计不生效问题
5. 修复nvue下控制是否出现滚动条失效问题
## 2.0.112021-12-13
## [点击加群交流反馈364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. text组件align参数无效的问题
2. subsection组件添加keyName参数
3. upload组件无法判断[Object file]类型的问题
4. 处理notify层级过低问题
5. codeInput组件添加disabledDot参数
6. 处理actionSheet组件round参数无效的问题
7. calendar组件添加round参数用于控制圆角值
8. 处理swipeAction组件在vue环境下默认被打开的问题
9. button组件的throttleTime节流参数无效的问题
10. 解决u-notify手动关闭方法close()无效的问题
11. input组件readonly不生效问题
12. tag组件type参数为info不生效问题
## 2.0.102021-12-08
## [点击加群交流反馈364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复button sendMessagePath属性不生效
2. 修复DatetimePicker选择器title无效
3. 修复u-toast设置loading=true不生效
4. 修复u-text金额模式传0报错
5. 修复u-toast组件的icon属性配置不生效
6. button的icon在特殊场景下的颜色优化
7. IndexList优化增加#
## 2.0.92021-12-01
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 优化swiper的height支持100%值(仅vue有效)修复嵌入视频时click事件无法触发的问题
2. 优化tabs组件对list值为空的判断或者动态变化list时重新计算相关尺寸的问题
3. 优化datetime-picker组件逻辑让其后续打开的默认值为上一次的选中值需要通过v-model绑定值才有效
4. 修复upload内嵌在其他组件中选择图片可能不会换行的问题
## 2.0.82021-12-01
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复toast的position参数无效问题
2. 处理input在ios nvue上无法获得焦点的问题
3. avatar-group组件添加extraValue参数让剩余展示数量可手动控制
4. tabs组件添加keyName参数用于配置从对象中读取的键名
5. 处理text组件名字脱敏默认配置无效的问题
6. 处理picker组件item文本太长换行问题
## 2.0.72021-11-30
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 修复radio和checkbox动态改变v-model无效的问题。
2. 优化form规则validator在微信小程序用法
3. 修复backtop组件mode参数在微信小程序无效的问题
4. 处理Album的previewFullImage属性无效的问题
5. 处理u-datetime-picker组件mode='time'在选择改变时间时,控制台报错的问题
## 2.0.62021-11-27
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. 处理tag组件在vue下边框无效的问题。
2. 处理popup组件圆角参数可能无效的问题。
3. 处理tabs组件lineColor参数可能无效的问题。
4. propgress组件在值很小时显示异常的问题。
## 2.0.52021-11-25
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. calendar在vue下显示异常问题。
2. form组件labelPosition和errorType参数无效的问题
3. input组件inputAlign无效的问题
4. 其他一些修复
## 2.0.42021-11-23
## [点击加群交流反馈232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
0. input组件缺失@confirm事件以及subfix和prefix无效问题
1. component.scss文件样式在vue下干扰全局布局问题
2. 修复subsection在vue环境下表现异常的问题
3. tag组件的bgColor等参数无效的问题
4. upload组件不换行的问题
5. 其他的一些修复处理
## 2.0.32021-11-16
## [点击加群交流反馈1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. uView2.0已实现全面兼容nvue
2. uView2.0对1.x进行了架构重构细节和性能都有极大提升
3. 目前uView2.0为公测阶段,相关细节可能会有变动
4. 我们写了一份与1.x的对比指南详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
5. 处理modal的confirm回调事件拼写错误问题
6. 处理input组件@input事件参数错误问题
7. 其他一些修复
## 2.0.22021-11-16
## [点击加群交流反馈1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. uView2.0已实现全面兼容nvue
2. uView2.0对1.x进行了架构重构细节和性能都有极大提升
3. 目前uView2.0为公测阶段,相关细节可能会有变动
4. 我们写了一份与1.x的对比指南详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
5. 修复input组件formatter参数缺失问题
6. 优化loading-icon组件的scss写法问题防止不兼容新版本scss
## 2.0.0(2020-11-15)
## [点击加群交流反馈1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
# uView2.0重磅发布,利剑出鞘,一统江湖
1. uView2.0已实现全面兼容nvue
2. uView2.0对1.x进行了架构重构细节和性能都有极大提升
3. 目前uView2.0为公测阶段,相关细节可能会有变动
4. 我们写了一份与1.x的对比指南详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
5. 修复input组件formatter参数缺失问题

@ -0,0 +1,78 @@
<template>
<uvForm
ref="uForm"
:model="model"
:rules="rules"
:errorType="errorType"
:borderBottom="borderBottom"
:labelPosition="labelPosition"
:labelWidth="labelWidth"
:labelAlign="labelAlign"
:labelStyle="labelStyle"
:customStyle="customStyle"
>
<slot />
</uvForm>
</template>
<script>
/**
* 此组件存在的理由是在nvue下u-form被uni-app官方占用了u-form在nvue中相当于form组件
* 所以在nvue下取名为u--form内部其实还是u-form.vue只不过做一层中转
*/
import uvForm from '../u-form/u-form.vue';
import props from '../u-form/props.js'
export default {
// #ifdef MP-WEIXIN
name: 'u-form',
// #endif
// #ifndef MP-WEIXIN
name: 'u--form',
// #endif
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
components: {
uvForm
},
created() {
this.children = []
},
methods: {
//
setRules(rules) {
this.$refs.uForm.setRules(rules)
},
validate() {
/**
* 在微信小程序中通过this.$parent拿到的父组件是u--form而不是其内嵌的u-form
* 导致在u-form组件中拿不到对应的children数组从而校验无效所以这里每次调用u-form组件中的
* 对应方法的时候在小程序中都先将u--form的children赋值给u-form中的children
*/
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.validate()
},
validateField(value, callback) {
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.validateField(value, callback)
},
resetFields() {
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.resetFields()
},
clearValidate(props) {
// #ifdef MP-WEIXIN
this.setMpData()
// #endif
return this.$refs.uForm.clearValidate(props)
},
setMpData() {
this.$refs.uForm.children = this.children
}
},
}
</script>

@ -0,0 +1,47 @@
<template>
<uvImage
:src="src"
:mode="mode"
:width="width"
:height="height"
:shape="shape"
:radius="radius"
:lazyLoad="lazyLoad"
:showMenuByLongpress="showMenuByLongpress"
:loadingIcon="loadingIcon"
:errorIcon="errorIcon"
:showLoading="showLoading"
:showError="showError"
:fade="fade"
:webp="webp"
:duration="duration"
:bgColor="bgColor"
:customStyle="customStyle"
@click="$emit('click')"
@error="$emit('error')"
@load="$emit('load')"
>
<template v-slot:loading>
<slot name="loading"></slot>
</template>
<template v-slot:error>
<slot name="error"></slot>
</template>
</uvImage>
</template>
<script>
/**
* 此组件存在的理由是在nvue下u-image被uni-app官方占用了u-image在nvue中相当于image组件
* 所以在nvue下取名为u--image内部其实还是u-iamge.vue只不过做一层中转
*/
import uvImage from '../u-image/u-image.vue';
import props from '../u-image/props.js';
export default {
name: 'u--image',
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
components: {
uvImage
},
}
</script>

@ -0,0 +1,73 @@
<template>
<uvInput
:value="value"
:type="type"
:fixed="fixed"
:disabled="disabled"
:disabledColor="disabledColor"
:clearable="clearable"
:password="password"
:maxlength="maxlength"
:placeholder="placeholder"
:placeholderClass="placeholderClass"
:placeholderStyle="placeholderStyle"
:showWordLimit="showWordLimit"
:confirmType="confirmType"
:confirmHold="confirmHold"
:holdKeyboard="holdKeyboard"
:focus="focus"
:autoBlur="autoBlur"
:disableDefaultPadding="disableDefaultPadding"
:cursor="cursor"
:cursorSpacing="cursorSpacing"
:selectionStart="selectionStart"
:selectionEnd="selectionEnd"
:adjustPosition="adjustPosition"
:inputAlign="inputAlign"
:fontSize="fontSize"
:color="color"
:prefixIcon="prefixIcon"
:suffixIcon="suffixIcon"
:suffixIconStyle="suffixIconStyle"
:prefixIconStyle="prefixIconStyle"
:border="border"
:readonly="readonly"
:shape="shape"
:customStyle="customStyle"
:formatter="formatter"
:ignoreCompositionEvent="ignoreCompositionEvent"
@focus="$emit('focus')"
@blur="e => $emit('blur', e)"
@keyboardheightchange="$emit('keyboardheightchange')"
@change="e => $emit('change', e)"
@input="e => $emit('input', e)"
@confirm="e => $emit('confirm', e)"
@clear="$emit('clear')"
@click="$emit('click')"
>
<!-- #ifdef MP -->
<slot name="prefix"></slot>
<slot name="suffix"></slot>
<!-- #endif -->
<!-- #ifndef MP -->
<slot name="prefix" slot="prefix"></slot>
<slot name="suffix" slot="suffix"></slot>
<!-- #endif -->
</uvInput>
</template>
<script>
/**
* 此组件存在的理由是在nvue下u-input被uni-app官方占用了u-input在nvue中相当于input组件
* 所以在nvue下取名为u--input内部其实还是u-input.vue只不过做一层中转
*/
import uvInput from '../u-input/u-input.vue';
import props from '../u-input/props.js'
export default {
name: 'u--input',
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
components: {
uvInput
},
}
</script>

@ -0,0 +1,44 @@
<template>
<uvText
:type="type"
:show="show"
:text="text"
:prefixIcon="prefixIcon"
:suffixIcon="suffixIcon"
:mode="mode"
:href="href"
:format="format"
:call="call"
:openType="openType"
:bold="bold"
:block="block"
:lines="lines"
:color="color"
:decoration="decoration"
:size="size"
:iconStyle="iconStyle"
:margin="margin"
:lineHeight="lineHeight"
:align="align"
:wordWrap="wordWrap"
:customStyle="customStyle"
@click="$emit('click')"
></uvText>
</template>
<script>
/**
* 此组件存在的理由是在nvue下u-text被uni-app官方占用了u-text在nvue中相当于input组件
* 所以在nvue下取名为u--input内部其实还是u-text.vue只不过做一层中转
* 不使用v-bind="$attrs"而是分开独立写传参是因为微信小程序不支持此写法
*/
import uvText from "../u-text/u-text.vue";
import props from "../u-text/props.js";
export default {
name: "u--text",
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
components: {
uvText,
},
};
</script>

@ -0,0 +1,48 @@
<template>
<uvTextarea
:value="value"
:placeholder="placeholder"
:height="height"
:confirmType="confirmType"
:disabled="disabled"
:count="count"
:focus="focus"
:autoHeight="autoHeight"
:fixed="fixed"
:cursorSpacing="cursorSpacing"
:cursor="cursor"
:showConfirmBar="showConfirmBar"
:selectionStart="selectionStart"
:selectionEnd="selectionEnd"
:adjustPosition="adjustPosition"
:disableDefaultPadding="disableDefaultPadding"
:holdKeyboard="holdKeyboard"
:maxlength="maxlength"
:border="border"
:customStyle="customStyle"
:formatter="formatter"
:ignoreCompositionEvent="ignoreCompositionEvent"
@focus="e => $emit('focus')"
@blur="e => $emit('blur')"
@linechange="e => $emit('linechange', e)"
@confirm="e => $emit('confirm')"
@input="e => $emit('input', e)"
@keyboardheightchange="e => $emit('keyboardheightchange')"
></uvTextarea>
</template>
<script>
/**
* 此组件存在的理由是在nvue下u--textarea被uni-app官方占用了u-textarea在nvue中相当于textarea组件
* 所以在nvue下取名为u--textarea内部其实还是u-textarea.vue只不过做一层中转
*/
import uvTextarea from '../u-textarea/u-textarea.vue';
import props from '../u-textarea/props.js'
export default {
name: 'u--textarea',
mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
components: {
uvTextarea
},
}
</script>

@ -0,0 +1,54 @@
export default {
props: {
// 操作菜单是否展示 默认false
show: {
type: Boolean,
default: uni.$u.props.actionSheet.show
},
// 标题
title: {
type: String,
default: uni.$u.props.actionSheet.title
},
// 选项上方的描述信息
description: {
type: String,
default: uni.$u.props.actionSheet.description
},
// 数据
actions: {
type: Array,
default: uni.$u.props.actionSheet.actions
},
// 取消按钮的文字,不为空时显示按钮
cancelText: {
type: String,
default: uni.$u.props.actionSheet.cancelText
},
// 点击某个菜单项时是否关闭弹窗
closeOnClickAction: {
type: Boolean,
default: uni.$u.props.actionSheet.closeOnClickAction
},
// 处理底部安全区默认true
safeAreaInsetBottom: {
type: Boolean,
default: uni.$u.props.actionSheet.safeAreaInsetBottom
},
// 小程序的打开方式
openType: {
type: String,
default: uni.$u.props.actionSheet.openType
},
// 点击遮罩是否允许关闭 (默认true)
closeOnClickOverlay: {
type: Boolean,
default: uni.$u.props.actionSheet.closeOnClickOverlay
},
// 圆角值
round: {
type: [Boolean, String, Number],
default: uni.$u.props.actionSheet.round
}
}
}

@ -0,0 +1,278 @@
<template>
<u-popup
:show="show"
mode="bottom"
@close="closeHandler"
:safeAreaInsetBottom="safeAreaInsetBottom"
:round="round"
>
<view class="u-action-sheet">
<view
class="u-action-sheet__header"
v-if="title"
>
<text class="u-action-sheet__header__title u-line-1">{{title}}</text>
<view
class="u-action-sheet__header__icon-wrap"
@tap.stop="cancel"
>
<u-icon
name="close"
size="17"
color="#c8c9cc"
bold
></u-icon>
</view>
</view>
<text
class="u-action-sheet__description"
:style="[{
marginTop: `${title && description ? 0 : '18px'}`
}]"
v-if="description"
>{{description}}</text>
<slot>
<u-line v-if="description"></u-line>
<view class="u-action-sheet__item-wrap">
<template v-for="(item, index) in actions">
<!-- #ifdef MP -->
<button
:key="index"
class="u-reset-button"
:openType="item.openType"
@getuserinfo="onGetUserInfo"
@contact="onContact"
@getphonenumber="onGetPhoneNumber"
@error="onError"
@launchapp="onLaunchApp"
@opensetting="onOpenSetting"
:lang="lang"
:session-from="sessionFrom"
:send-message-title="sendMessageTitle"
:send-message-path="sendMessagePath"
:send-message-img="sendMessageImg"
:show-message-card="showMessageCard"
:app-parameter="appParameter"
@tap="selectHandler(index)"
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
>
<!-- #endif -->
<view
class="u-action-sheet__item-wrap__item"
@tap.stop="selectHandler(index)"
:hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
:hover-stay-time="150"
>
<template v-if="!item.loading">
<text
class="u-action-sheet__item-wrap__item__name"
:style="[itemStyle(index)]"
>{{ item.name }}</text>
<text
v-if="item.subname"
class="u-action-sheet__item-wrap__item__subname"
>{{ item.subname }}</text>
</template>
<u-loading-icon
v-else
custom-class="van-action-sheet__loading"
size="18"
mode="circle"
/>
</view>
<!-- #ifdef MP -->
</button>
<!-- #endif -->
<u-line v-if="index !== actions.length - 1"></u-line>
</template>
</view>
</slot>
<u-gap
bgColor="#eaeaec"
height="6"
v-if="cancelText"
></u-gap>
<view hover-class="u-action-sheet--hover">
<text
@touchmove.stop.prevent
:hover-stay-time="150"
v-if="cancelText"
class="u-action-sheet__cancel-text"
@tap="cancel"
>{{cancelText}}</text>
</view>
</view>
</u-popup>
</template>
<script>
import openType from '../../libs/mixin/openType'
import button from '../../libs/mixin/button'
import props from './props.js';
/**
* ActionSheet 操作菜单
* @description 本组件用于从底部弹出一个操作菜单供用户选择并返回结果本组件功能类似于uni的uni.showActionSheetAPI配置更加灵活所有平台都表现一致
* @tutorial https://www.uviewui.com/components/actionSheet.html
*
* @property {Boolean} show 操作菜单是否展示 默认 false
* @property {String} title 操作菜单标题
* @property {String} description 选项上方的描述信息
* @property {Array<Object>} actions 按钮的文字数组见官方文档示例
* @property {String} cancelText 取消按钮的提示文字,不为空时显示按钮
* @property {Boolean} closeOnClickAction 点击某个菜单项时是否关闭弹窗 默认 true
* @property {Boolean} safeAreaInsetBottom 处理底部安全区 默认 true
* @property {String} openType 小程序的打开方式 (contact | launchApp | getUserInfo | openSetting getPhoneNumber error )
* @property {Boolean} closeOnClickOverlay 点击遮罩是否允许关闭 (默认 true )
* @property {Number|String} round 圆角值默认无圆角 (默认 0 )
* @property {String} lang 指定返回用户信息的语言zh_CN 简体中文zh_TW 繁体中文en 英文
* @property {String} sessionFrom 会话来源openType="contact"时有效
* @property {String} sendMessageTitle 会话内消息卡片标题openType="contact"时有效
* @property {String} sendMessagePath 会话内消息卡片点击跳转小程序路径openType="contact"时有效
* @property {String} sendMessageImg 会话内消息卡片图片openType="contact"时有效
* @property {Boolean} showMessageCard 是否显示会话内消息卡片设置此参数为 true用户进入客服会话会在右下角显示"可能要发送的小程序"提示用户点击后可以快速发送小程序消息openType="contact"时有效 默认 false
* @property {String} appParameter 打开 APP APP 传递的参数openType=launchApp 时有效
*
* @event {Function} select 点击ActionSheet列表项时触发
* @event {Function} close 点击取消按钮时触发
* @event {Function} getuserinfo 用户点击该按钮时会返回获取到的用户信息回调的 detail 数据与 wx.getUserInfo 返回的一致openType="getUserInfo"时有效
* @event {Function} contact 客服消息回调openType="contact"时有效
* @event {Function} getphonenumber 获取用户手机号回调openType="getPhoneNumber"时有效
* @event {Function} error 当使用开放能力时发生错误的回调openType="error"时有效
* @event {Function} launchapp 打开 APP 成功的回调openType="launchApp"时有效
* @event {Function} opensetting 在打开授权设置页后回调openType="openSetting"时有效
* @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
*/
export default {
name: "u-action-sheet",
// propsmethodsmixin
mixins: [openType, button, uni.$u.mixin, props],
data() {
return {
}
},
computed: {
//
itemStyle() {
return (index) => {
let style = {};
if (this.actions[index].color) style.color = this.actions[index].color
if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize)
//
if (this.actions[index].disabled) style.color = '#c0c4cc'
return style;
}
},
},
methods: {
closeHandler() {
// close
if(this.closeOnClickOverlay) {
this.$emit('close')
}
},
//
cancel() {
this.$emit('close')
},
selectHandler(index) {
const item = this.actions[index]
if (item && !item.disabled && !item.loading) {
this.$emit('select', item)
if (this.closeOnClickAction) {
this.$emit('close')
}
}
},
}
}
</script>
<style lang="scss" scoped>
@import "../../libs/css/components.scss";
$u-action-sheet-reset-button-width:100% !default;
$u-action-sheet-title-font-size: 16px !default;
$u-action-sheet-title-padding: 12px 30px !default;
$u-action-sheet-title-color: $u-main-color !default;
$u-action-sheet-header-icon-wrap-right:15px !default;
$u-action-sheet-header-icon-wrap-top:15px !default;
$u-action-sheet-description-font-size:13px !default;
$u-action-sheet-description-color:14px !default;
$u-action-sheet-description-margin: 18px 15px !default;
$u-action-sheet-item-wrap-item-padding:15px !default;
$u-action-sheet-item-wrap-name-font-size:16px !default;
$u-action-sheet-item-wrap-subname-font-size:13px !default;
$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
$u-action-sheet-item-wrap-subname-margin-top:10px !default;
$u-action-sheet-cancel-text-font-size:16px !default;
$u-action-sheet-cancel-text-color:$u-content-color !default;
$u-action-sheet-cancel-text-font-size:15px !default;
$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
.u-reset-button {
width: $u-action-sheet-reset-button-width;
}
.u-action-sheet {
text-align: center;
&__header {
position: relative;
padding: $u-action-sheet-title-padding;
&__title {
font-size: $u-action-sheet-title-font-size;
color: $u-action-sheet-title-color;
font-weight: bold;
text-align: center;
}
&__icon-wrap {
position: absolute;
right: $u-action-sheet-header-icon-wrap-right;
top: $u-action-sheet-header-icon-wrap-top;
}
}
&__description {
font-size: $u-action-sheet-description-font-size;
color: $u-tips-color;
margin: $u-action-sheet-description-margin;
text-align: center;
}
&__item-wrap {
&__item {
padding: $u-action-sheet-item-wrap-item-padding;
@include flex;
align-items: center;
justify-content: center;
flex-direction: column;
&__name {
font-size: $u-action-sheet-item-wrap-name-font-size;
color: $u-main-color;
text-align: center;
}
&__subname {
font-size: $u-action-sheet-item-wrap-subname-font-size;
color: $u-action-sheet-item-wrap-subname-color;
margin-top: $u-action-sheet-item-wrap-subname-margin-top;
text-align: center;
}
}
}
&__cancel-text {
font-size: $u-action-sheet-cancel-text-font-size;
color: $u-action-sheet-cancel-text-color;
text-align: center;
padding: $u-action-sheet-cancel-text-font-size;
}
&--hover {
background-color: $u-action-sheet-cancel-text-hover-background-color;
}
}
</style>

@ -0,0 +1,59 @@
export default {
props: {
// 图片地址Array<String>|Array<Object>形式
urls: {
type: Array,
default: uni.$u.props.album.urls
},
// 指定从数组的对象元素中读取哪个属性作为图片地址
keyName: {
type: String,
default: uni.$u.props.album.keyName
},
// 单图时,图片长边的长度
singleSize: {
type: [String, Number],
default: uni.$u.props.album.singleSize
},
// 多图时,图片边长
multipleSize: {
type: [String, Number],
default: uni.$u.props.album.multipleSize
},
// 多图时,图片水平和垂直之间的间隔
space: {
type: [String, Number],
default: uni.$u.props.album.space
},
// 单图时,图片缩放裁剪的模式
singleMode: {
type: String,
default: uni.$u.props.album.singleMode
},
// 多图时,图片缩放裁剪的模式
multipleMode: {
type: String,
default: uni.$u.props.album.multipleMode
},
// 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
maxCount: {
type: [String, Number],
default: uni.$u.props.album.maxCount
},
// 是否可以预览图片
previewFullImage: {
type: Boolean,
default: uni.$u.props.album.previewFullImage
},
// 每行展示图片数量如设置singleSize和multipleSize将会无效
rowCount: {
type: [String, Number],
default: uni.$u.props.album.rowCount
},
// 超出maxCount时是否显示查看更多的提示
showMore: {
type: Boolean,
default: uni.$u.props.album.showMore
}
}
}

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

Loading…
Cancel
Save