个人中心开发联调完

master
fuxiaochun 2023-08-15 02:04:19 +08:00
parent cb5f50940c
commit b0268b83c3
8 changed files with 482 additions and 72 deletions

View File

@ -218,7 +218,7 @@ const getUserInfo = () => {
http('/api/user/profile', {}, 'get').then(res => { http('/api/user/profile', {}, 'get').then(res => {
localCache.set('userInfo', res.data); localCache.set('userInfo', res.data);
userInfo.updateUserInfo(res.data); userInfo.updateUserInfo(res.data);
// window.location.reload(); window.location.reload();
}).catch(err => { }).catch(err => {
showToast(err.message); showToast(err.message);
}) })

View File

@ -94,7 +94,7 @@ const onLogout = ()=>{
http('/api/auth/logout').then(res => { http('/api/auth/logout').then(res => {
localCache.remove('userInfo'); localCache.remove('userInfo');
localCache.remove('auth'); localCache.remove('auth');
userInfo.updateUserInfo(); userInfo.updateUserInfo({});
showToast('已退出登录!'); showToast('已退出登录!');
// window.location.replace('/'); // window.location.replace('/');
}).catch(err => { }).catch(err => {

View File

@ -35,7 +35,16 @@ const router = createRouter({
group: 'ucenter' group: 'ucenter'
}, },
component: () => import("@/views/ucenter/userInfo.vue"), component: () => import("@/views/ucenter/userInfo.vue"),
} },
{
path: 'order',
name: 'order',
meta: {
title: '我的订单',
group: 'ucenter'
},
component: () => import("@/views/ucenter/order.vue"),
},
] ]
}, },
] ]

View File

@ -0,0 +1,58 @@
export function DateFormat(date, format) {
var o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
'q+': Math.floor((date.getMonth() + 3) / 3),
'S': date.getMilliseconds() //毫秒
};
if (/(y+)/.test(format))
format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
for (let k in o)
if (new RegExp('(' + k + ')').test(format))
format = format.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)));
return format;
}
export function formatDate(dateTime = null, formatStr = 'yyyy-MM-dd HH:mm:ss') {
if (!dateTime) return ''
let date
// 若传入时间为假值,则取当前时间
if (!dateTime) {
date = new Date()
} else if (/^\d{10}$/.test(dateTime.toString().trim())) {
date = new Date(dateTime * 1000)
} else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) {
date = new Date(Number(dateTime))
} else {
date = new Date(
typeof dateTime === 'string' ?
dateTime.replace(/-/g, '/') :
dateTime
)
}
const timeSource = {
'y': date.getFullYear().toString(), // 年
'M': (date.getMonth() + 1).toString().padStart(2, '0'), // 月
'd': date.getDate().toString().padStart(2, '0'), // 日
'H': date.getHours().toString().padStart(2, '0'), // 时
'm': date.getMinutes().toString().padStart(2, '0'), // 分
's': date.getSeconds().toString().padStart(2, '0') // 秒
}
for (const key in timeSource) {
const [ret] = new RegExp(`${key}+`).exec(formatStr) || []
if (ret) {
const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0
formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))
}
}
return formatStr
}

View File

@ -0,0 +1,212 @@
<template>
<van-popup :show="true" closeable @click-close-icon="onCancel" style="background: none;width:90%;">
<div class="formBox">
<div class="item">
<div class="label">{{ opts[props.type].title }}</div>
<div class="val">
<van-field :clearable="true" :center="true" class="txt" v-model="account" :placeholder="opts[props.type].placeholder" />
</div>
</div>
<div class="item">
<div class="label">验证码</div>
<div class="val">
<van-field :clearable="true" :center="true" class="stxt" v-model="code" placeholder="请输入验证码" />
<van-button class="sbtn" type="primary" :disabled="CountDown !== getCodeTime" @click="getCode">{{ btnTxt }}</van-button>
</div>
</div>
<div class="item">
<van-button class="submitBtn" @click="onSubmit" type="primary">确定</van-button>
</div>
</div>
</van-popup>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { showToast } from 'vant';
import http from '@/io/http';
import { localCache } from '@/io/cache';
const props = defineProps({
type: String,
onOk: Function,
onClose: Function
})
const opts = {
tel: {
title: '手机号码换绑',
placeholder: '请输入要绑定的手机号'
},
email: {
title: '联系邮箱换绑',
placeholder: '请输入要绑定的邮箱'
}
};
const account = ref();
const code = ref();
const getCodeTime = 60;
const btnTxt = ref('获取验证码');
const CountDown = ref(getCodeTime);
const telTimer = ref(0);
const telReg = /^1[3-9]\d{9}$/;
const emailReg = /^[\w\.-]+@[a-zA-Z\d\.-]+\.[a-zA-Z]{2,}$/;
const getCode = ()=>{
if(props.type == 'tel'){
getTelCode();
}
if(props.type == 'email'){
getEmailCode();
}
};
const getTelCode = () => {
if (!telReg.test(account.value)) {
showToast('请输入正确的手机号!');
return false;
}
http('/api/sms/phone', { phone: account.value, type: 'update-phone' }, 'POST').then(res => {
reRenderGetcodebtn();
}).catch(err => {
showToast(err.message);
})
};
const getEmailCode = () => {
if (!emailReg.test(account.value)) {
showToast('请输入正确的邮箱地址!');
return false;
}
http('/api/sms/email', { email: account.value, type: 'update-email' }).then(res => {
reRenderGetcodebtn();
}).catch(err => {
showToast(err.message);
})
};
const reRenderGetcodebtn = () => {
let time = --CountDown.value;
btnTxt.value = `${time}s后重新获取`;
if (time == 0) {
clearTimeout(telTimer.value);
btnTxt.value = '获取验证码';
CountDown.value = getCodeTime;
} else {
telTimer.value = setTimeout(reRenderGetcodebtn, 1000);
}
}
const validate = ()=>{
if (props.type == 'tel') {
if (!telReg.test(account.value)) {
showToast('请输入正确的手机号!');
return false;
}
}
if (props.type == 'email') {
if (!emailReg.test(account.value)) {
showToast('请输入正确的邮箱地址!');
return false;
}
}
if (!code.value) {
showToast('请输入验证码!');
return false;
}
return true;
};
const onSubmit = ()=>{
if(!validate()){
return false;
}
let url = '';
let formData = {};
if(props.type == 'tel'){
url = '/api/user/update-phone';
formData = {
phone: account.value,
code: code.value
};
}
if(props.type == 'email'){
url = '/api/user/update-email';
formData = {
email: account.value,
code: code.value
};
}
http(url, formData).then(res => {
props.onOk(account.value);
}).catch(err => {
showToast(err.message);
})
};
const onCancel = ()=>{
props.onClose();
};
</script>
<style lang="scss" scoped>
.formBox{
color: #FFF;
background: #161718;
padding: 45px;
:deep(.van-cell:after){
display: none;
}
.item{
margin-bottom: 15px;
.label{
line-height: 66px;
font-size: 27px;
}
.val{
display: flex;
}
.txt,
.stxt{
height: 72px;
border: 2px solid rgba($color: #414548, $alpha: 0.4);
border-radius: 4px;
background: none;
color: #FFF;
padding: 0 20px;
:deep(input){
background: none;
color: #FFF;
line-height: 72px;
&::placeholder{
color: #999;
}
}
}
.sbtn {
width: 400px;
margin-left: 10px;
font-size: 23px;
height: 72px;
&:disabled{
opacity: 1;
color: #FFF;
border: 2px solid #FFF;
background: none;
}
}
}
.submitBtn{
height: 66px;
}
}
</style>

View File

@ -0,0 +1,145 @@
<template>
<div class="container">
<div class="title">订单列表</div>
<div class="list">
<div class="tHeader">
<div class="li">
<span>订单编号</span>
<span>交易流水号</span>
<span>服务内容</span>
<span>订单金额()</span>
<span>订单时间</span>
</div>
</div>
<div class="tBody">
<div class="li" :key="item.id" v-for="item in dataList">
<span>{{item.order_sn }}</span>
<span>{{item.pay_sn }}</span>
<span>{{item.product_name }}</span>
<span>{{item.money }}</span>
<span>{{formatDate(item.pay_at,'yyyy.MM.dd HH:mm:ss') }}</span>
</div>
<div class="loadMore" @click="loadMore" v-if="hasMore"></div>
<div class="noData" v-if="dataList.length == 0">
<div class="img"><i class="icon-dingdan"></i></div>
<p>暂无订单</p>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onBeforeMount, onMounted } from 'vue';
import http from '@/io/http';
import { showToast } from 'vant';
import { formatDate } from '@/utils/format.js'
const orderData = ref({});
const dataList = ref([]);
const pageSize = ref(15);
const pageNum = ref(1);
const hasMore = ref(false);
onBeforeMount(()=>{
getList();
});
const getList = ()=>{
let params = {
per_page: pageSize.value,
page: pageNum.value
};
http('/api/order', params, 'get').then(res => {
orderData.value = res.data;
dataList.value = dataList.value.concat(res.data.data);
hasMore.value = res.data.current_page !== res.data.last_page;
}).catch(err => {
showToast(err.message);
});
}
const loadMore = ()=>{
pageNum.value++;
getList();
}
</script>
<style lang="scss" scoped>
.container{
padding: 45px;
color: #FFF;
.title{
height: 52px;
line-height: 52px;
font-size: 28px;
font-weight: bold;
margin-bottom: 20px;
}
.list{
.tHeader,
.tBody{
.li{
display: flex;
flex-wrap: nowrap;
flex: 1;
span{
display: inline-block;
flex: 1;
min-width: 110px;
padding: 0 10px;
margin-right: 10px;
line-height: 38px;
font-size: 22px;
word-wrap: break-word;
word-break: break-all;
}
}
}
.tHeader span{
background: #757576;
border-radius: 4px;
}
.tBody{
padding-top: 10px;
.li{
padding: 10px 0;
border-bottom: 1px solid #999;
color: #999;
}
}
.loadMore{
width: 180px;
height: 66px;
font-size: 22px;
display: flex;
align-items: center;
justify-content: center;
margin: 40px auto;
cursor: pointer;
border: 1px solid #999;
border-radius: 4px;
}
.noData{
min-height: 400px;
width: 100%;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
.img{
width: 72px;
height: 106px;
font-size: 72px;
color: #757576;
}
p{
margin-top: 20px;
color: #999;
font-size: 27px;
}
}
}
}
</style>

View File

@ -13,7 +13,7 @@
<div class="label"><span>*</span>地区</div> <div class="label"><span>*</span>地区</div>
<div class="val"> <div class="val">
<van-field <van-field
v-model="fieldValue" v-model="areaValue"
class="areaInput" class="areaInput"
is-link is-link
readonly readonly
@ -22,26 +22,15 @@
/> />
<van-popup v-model:show="showAreaPopup" round position="bottom"> <van-popup v-model:show="showAreaPopup" round position="bottom">
<van-cascader <van-cascader
:field-names="{text: 'name', value: 'id', children: 'children'}" style="color: #333;"
v-model="cascaderValue" v-model="cascaderValue"
:field-names="{text: 'name', value: 'id', children: 'children'}"
title="请选择所在地区" title="请选择所在地区"
:options="options" :options="options"
@close="showAreaPopup = false" @close="showAreaPopup = false"
@finish="onFinish" @finish="onFinish"
/> />
</van-popup> </van-popup>
<!-- <a-select class="areaInput" v-model="province" @change="provinceChoosed">
<a-select-option :value="item.id" :key="item.id"
v-for="item in provinces">{{ item.name }}</a-select-option>
</a-select>
<a-select class="areaInput" v-model="city" @change="cityChoosed">
<a-select-option :value="item.id" :key="item.id"
v-for="item in cities">{{ item.name }}</a-select-option>
</a-select>
<a-select class="areaInput" v-model="area">
<a-select-option :value="item.id" :key="item.id"
v-for="item in areas">{{ item.name }}</a-select-option>
</a-select> -->
</div> </div>
</div> </div>
<div class="item"> <div class="item">
@ -75,9 +64,6 @@
</div> </div>
</div> </div>
</div> </div>
<div class="saveBtn">
<van-button type="primary" @click="onSave"></van-button>
</div>
</section> </section>
<section> <section>
<div class="blockTitle">会员信息</div> <div class="blockTitle">会员信息</div>
@ -101,8 +87,12 @@
</div> </div>
</section> </section>
<!-- <UpdateContactModal v-if="!!updateType" :type="updateType" :onOk="onUpdateContactOk" <div class="saveBtn">
:onClose="onUpdateContactClose" /> --> <van-button type="primary" @click="onSave"><i class="icon-save"></i>保存</van-button>
</div>
<UpdateContactModal v-if="!!updateType" :type="updateType" :onOk="onUpdateContactOk"
:onClose="onUpdateContactClose" />
</div> </div>
</template> </template>
@ -112,7 +102,7 @@ import http from '@/io/http';
import { localCache } from '@/io/cache'; import { localCache } from '@/io/cache';
import { showToast } from 'vant'; import { showToast } from 'vant';
import { useUserInfo } from '@/stores/userInfo'; import { useUserInfo } from '@/stores/userInfo';
// import UpdateContactModal from './components/updateContactModal.vue'; import UpdateContactModal from './components/updateContactModal.vue';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
const router = useRouter(); const router = useRouter();
@ -124,40 +114,37 @@ const updateType = ref('');
const name = ref(); const name = ref();
const industry = ref(); // const industry = ref(); //
const job = ref(); // const job = ref(); //
const provinces = ref([]); const areaValue = ref();
const cities = ref([]);
const areas = ref([]);
const province = ref(); const province = ref();
const city = ref(); const city = ref();
const area = ref(); const area = ref();
const showAreaPopup = ref(false); const showAreaPopup = ref(false);
const cascaderValue = ref();
const options = ref([]); const options = ref([]);
const fieldValue = ref([]);
const cascaderValue = ref([]);
const onFinish = (v)=>{ const onFinish = (data)=>{
console.log(v); showAreaPopup.value = false;
let selectedOptions = data.selectedOptions || [];
let _areaValue = [];
selectedOptions.forEach((v,i)=>{
_areaValue.push(v.name);
i == 0 && (province.value = v.id);
i == 1 && (city.value = v.id);
i == 2 && (area.value = v.id);
});
areaValue.value = _areaValue.join('/');
} }
onBeforeMount(() => {
getAreas();
});
onMounted(() => { onMounted(() => {
let userData = localCache.get('userInfo') || {};
if(userData.id){
initData(); initData();
if (province.value) { getAreas();
getAreaByPid(province.value, res => {
cities.value = res.data;
});
}
if (city.value) {
getAreaByPid(city.value, res => {
areas.value = res.data;
});
} }
}); });
const initData = () => { const initData = () => {
@ -168,41 +155,26 @@ const initData = () => {
province.value = data.province_id; province.value = data.province_id;
city.value = data.city_id; city.value = data.city_id;
area.value = data.district_id; area.value = data.district_id;
cascaderValue.value = data.district_id;
}; };
const initAreaData = (data = [])=>{
let _province = data.filter(v=>v.id == province.value)[0];
let _city = _province.children.filter(v=>v.id == city.value)[0];
let _area = _city.children.filter(v=>v.id == area.value)[0];
areaValue.value = [_province, _city, _area].map(v=>v.name).join('/');
};
const getAreas = () => { const getAreas = () => {
http('/api/region/tree', {}, 'get').then(res => { http('/api/region/tree', {}, 'get').then(res => {
options.value = res.data; options.value = res.data;
initAreaData(res.data);
}).catch(err => { }).catch(err => {
showToast(err.message); showToast(err.message);
}); });
} }
const getAreaByPid = (parent_key, cb) => {
http('/api/region', { parent_key }, 'get').then(res => {
cb(res);
}).catch(err => {
showToast(err.message);
});
}
const provinceChoosed = (pid) => {
city.value = null;
area.value = null;
getAreaByPid(pid, res => {
cities.value = res.data;
});
};
const cityChoosed = (pid) => {
area.value = null;
getAreaByPid(pid, res => {
areas.value = res.data;
});
};
const onUpdateContactOk = (newVal) => { const onUpdateContactOk = (newVal) => {
let temp = { ...userInfo.userData }; let temp = { ...userInfo.userData };
if (updateType.value == 'tel') { if (updateType.value == 'tel') {
@ -254,7 +226,7 @@ const onSave = () => {
}; };
http('/api/user/profile', formData).then(res => { http('/api/user/profile', formData).then(res => {
message.success('保存成功!'); showToast('保存成功!');
let temp = { ...userInfo.userData }; let temp = { ...userInfo.userData };
temp.name = name.value; temp.name = name.value;
temp.province_id = province.value; temp.province_id = province.value;
@ -277,7 +249,10 @@ const jump = (path) => {
<style lang="scss" scoped> <style lang="scss" scoped>
.container { .container {
padding: 47px; padding: 0 47px 47px 47px;
section{
padding-top: 40px;
}
} }
.blockTitle { .blockTitle {
@ -342,6 +317,9 @@ const jump = (path) => {
} }
} }
} }
:deep(.van-cell:after){
display: none;
}
} }
} }
@ -351,16 +329,21 @@ const jump = (path) => {
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
align-items: center; align-items: center;
margin-top: 50px; margin-top: 70px;
button { button {
width: 100%; width: 100%;
height: 72px; height: 72px;
line-height: 68px;
border-radius: 4px; border-radius: 4px;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 30px; font-size: 30px;
i{
margin-right: 10px;
font-size: 30px;
}
} }
} }
@ -385,9 +368,10 @@ const jump = (path) => {
justify-content: center; justify-content: center;
align-items: center; align-items: center;
font-size: 27px; font-size: 27px;
padding: 0;
i{ i{
font-size: 30px; font-size: 30px;
margin-right: 10px; margin-right: 5px;
} }
} }
} }

View File

@ -45,7 +45,9 @@ export default defineConfig({
postcss: { postcss: {
plugins: [ plugins: [
postcssPxtoRem({ postcssPxtoRem({
rootValue: 75, rootValue({ file }) {
return file.indexOf('vant') !== -1 ? 37.5 : 75;
},
unitPrecision: 5, unitPrecision: 5,
selectorBlackList: [], // 忽略转换正则匹配项 selectorBlackList: [], // 忽略转换正则匹配项
propList: ['*'], propList: ['*'],