优化模板界面内容

This commit is contained in:
dftre 2025-03-21 18:34:24 +08:00
parent 76e0db644d
commit f0200ac849
8 changed files with 878 additions and 619 deletions

View File

@ -56,169 +56,191 @@
</u-popup>
</template>
<script>
import provinces from "./province.js";
import citys from "./city.js";
import areas from "./area.js";
/**
* city-select 省市区级联选择器
* @property {String Number} z-index 弹出时的z-index值默认1075
* @property {Boolean} mask-close-able 是否允许通过点击遮罩关闭Picker默认true
* @property {String} default-region 默认选中的地区中文形式
* @property {String} default-code 默认选中的地区编号形式
*/
export default {
name: 'u-city-select',
props: {
//
modelValue: {
type: Boolean,
default: false
},
// ["", "", ""]
defaultRegion: {
type: Array,
default() {
return [];
}
},
// defaultRegionareaCodeareaCode["13", "1303", "130304"]
areaCode: {
type: Array,
default() {
return [];
}
},
// Picker
maskCloseAble: {
type: Boolean,
default: true
},
// z-index
zIndex: {
type: [String, Number],
default: 0
}
<script setup lang="ts">
import { ref, computed, onMounted, PropType } from 'vue';
import provincesSource from "./province.js";
import citysSource from "./city.js";
import areasSource from "./area.js";
//
interface Region {
label: string;
value: string;
}
interface CitySelectResult {
province: Region;
city: Region;
area: Region;
}
interface TabItem {
name: string;
}
// Props
const props = defineProps({
//
modelValue: {
type: Boolean,
default: false
},
data() {
return {
cityValue: "",
isChooseP: false, //
province: 0, //
provinces: provinces,
isChooseC: false, //
city: 0, //
citys: citys[0],
isChooseA: false, //
area: 0, //
areas: areas[0][0],
tabsIndex: 0,
}
// ["", "", ""]
defaultRegion: {
type: Array as PropType<string[]>,
default: () => []
},
mounted() {
this.init();
// defaultRegionareaCodeareaCode["13", "1303", "130304"]
areaCode: {
type: Array as PropType<string[]>,
default: () => []
},
computed: {
isChange() {
return this.tabsIndex > 1;
},
genTabsList() {
let tabsList = [{
name: "请选择"
}];
if (this.isChooseP) {
tabsList[0]['name'] = this.provinces[this.province]['label'];
tabsList[1] = {
name: "请选择"
};
}
if (this.isChooseC) {
tabsList[1]['name'] = this.citys[this.city]['label'];
tabsList[2] = {
name: "请选择"
};
}
if (this.isChooseA) {
tabsList[2]['name'] = this.areas[this.area]['label'];
}
return tabsList;
},
uZIndex() {
// z-index使
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
}
// Picker
maskCloseAble: {
type: Boolean,
default: true
},
emits: ['city-change'],
methods: {
init() {
if (this.areaCode.length == 3) {
this.setProvince("", this.areaCode[0]);
this.setCity("", this.areaCode[1]);
this.setArea("", this.areaCode[2]);
} else if (this.defaultRegion.length == 3) {
this.setProvince(this.defaultRegion[0], "");
this.setCity(this.defaultRegion[1], "");
this.setArea(this.defaultRegion[2], "");
};
},
setProvince(label = "", value = "") {
this.provinces.map((v, k) => {
if (value ? v.value == value : v.label == label) {
this.provinceChange(k);
}
})
},
setCity(label = "", value = "") {
this.citys.map((v, k) => {
if (value ? v.value == value : v.label == label) {
this.cityChange(k);
}
})
},
setArea(label = "", value = "") {
this.areas.map((v, k) => {
if (value ? v.value == value : v.label == label) {
this.isChooseA = true;
this.area = k;
}
})
},
close() {
this.$emit('update:modelValue', false);
this.$emit('close');
},
tabsChange(value) {
this.tabsIndex = value.index;
},
provinceChange(index) {
this.isChooseP = true;
this.isChooseC = false;
this.isChooseA = false;
this.province = index;
this.citys = citys[index];
this.tabsIndex = 1;
},
cityChange(index) {
this.isChooseC = true;
this.isChooseA = false;
this.city = index;
this.areas = areas[this.province][index];
this.tabsIndex = 2;
},
areaChange(index) {
this.isChooseA = true;
this.area = index;
let result = {};
result.province = this.provinces[this.province];
result.city = this.citys[this.city];
result.area = this.areas[this.area];
this.$emit('city-change', result);
this.close();
}
// z-index
zIndex: {
type: [String, Number],
default: 0
}
});
//
const emit = defineEmits<{
(e: 'update:modelValue', value: boolean): void;
(e: 'close'): void;
(e: 'city-change', result: CitySelectResult): void;
}>();
const cityValue = ref("");
const isChooseP = ref(false); //
const province = ref(0); //
const provinces = ref<Region[]>(provincesSource);
const isChooseC = ref(false); //
const city = ref(0); //
const citys = ref<Region[]>(citysSource[0]);
const isChooseA = ref(false); //
const area = ref(0); //
const areas = ref<Region[]>(areasSource[0][0]);
const tabsIndex = ref(0);
const tabs = ref();
//
const isChange = computed(() => {
return tabsIndex.value > 1;
});
const genTabsList = computed((): TabItem[] => {
let tabsList: TabItem[] = [{
name: "请选择"
}];
if (isChooseP.value) {
tabsList[0].name = provinces.value[province.value].label;
tabsList[1] = {
name: "请选择"
};
}
}
if (isChooseC.value) {
tabsList[1].name = citys.value[city.value].label;
tabsList[2] = {
name: "请选择"
};
}
if (isChooseA.value) {
tabsList[2].name = areas.value[area.value].label;
}
return tabsList;
});
const uZIndex = computed(() => {
// z-index使
return props.zIndex ? props.zIndex : 1075; // $u.zIndex.popup1075
});
//
const setProvince = (label = "", value = "") => {
provinces.value.map((v, k) => {
if (value ? v.value == value : v.label == label) {
provinceChange(k);
}
});
};
const setCity = (label = "", value = "") => {
citys.value.map((v, k) => {
if (value ? v.value == value : v.label == label) {
cityChange(k);
}
});
};
const setArea = (label = "", value = "") => {
areas.value.map((v, k) => {
if (value ? v.value == value : v.label == label) {
isChooseA.value = true;
area.value = k;
}
});
};
const close = () => {
emit('update:modelValue', false);
emit('close');
};
const tabsChange = (value: { index: number }) => {
tabsIndex.value = value.index;
};
const provinceChange = (index: number) => {
isChooseP.value = true;
isChooseC.value = false;
isChooseA.value = false;
province.value = index;
citys.value = citysSource[index];
tabsIndex.value = 1;
};
const cityChange = (index: number) => {
isChooseC.value = true;
isChooseA.value = false;
city.value = index;
areas.value = areasSource[province.value][index];
tabsIndex.value = 2;
};
const areaChange = (index: number) => {
isChooseA.value = true;
area.value = index;
const result: CitySelectResult = {
province: provinces.value[province.value],
city: citys.value[city.value],
area: areas.value[area.value]
};
emit('city-change', result);
close();
};
//
onMounted(() => {
if (props.areaCode.length == 3) {
setProvince("", props.areaCode[0]);
setCity("", props.areaCode[1]);
setArea("", props.areaCode[2]);
} else if (props.defaultRegion.length == 3) {
setProvince(props.defaultRegion[0], "");
setCity(props.defaultRegion[1], "");
setArea(props.defaultRegion[2], "");
}
});
</script>
<style lang="scss">
.area-box {
width: 100%;

View File

@ -2,22 +2,24 @@
import { ref, reactive, onMounted } from 'vue';
import tab from '@/plugins/tab';
import citySelect from '@/components/u-city-select/u-city-select.vue';
import { useAddressEditPage } from './index';
//
const show = ref(false);
const defaultAddress = ref(false);
const selectedTag = ref('家');
const isEdit = ref(false); //
const editId = ref(''); // ID
// 使Hook
const {
isEdit,
form,
defaultAddress,
selectedTag,
addressTags,
initEditPage,
saveAddress,
deleteAddress
} = useAddressEditPage();
//
const form = reactive({
name: '',
phone: '',
region: '',
address: ''
});
//
const showRegionPicker = ref(false);
// - Vue
const formErrors = reactive({
name: false,
phone: false,
@ -25,101 +27,69 @@ const formErrors = reactive({
address: false
});
//
function resetFormErrors() {
formErrors.name = false;
formErrors.phone = false;
formErrors.region = false;
formErrors.address = false;
}
//
onMounted(() => {
//
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const currentPage: any = pages[pages.length - 1];
const options = currentPage.$page?.options;
if (options && options.id) {
isEdit.value = true;
editId.value = options.id;
loadAddressData(options.id);
}
// hook
initEditPage(options?.id);
//
resetFormErrors();
});
//
const loadAddressData = (id: string) => {
try {
const addressList = uni.getStorageSync('addressList') || [];
const address = addressList.find((item: any) => item.id === id);
if (address) {
form.name = address.name;
form.phone = address.phoneOriginal || address.phone; // 使
form.region = address.region;
form.address = address.address;
selectedTag.value = address.tag || '家';
defaultAddress.value = address.isDefault;
}
} catch (e) {
console.error('加载地址数据失败', e);
}
};
//
const setDefault = (e: any) => {
function handleSetDefault(e: any) {
defaultAddress.value = e.detail.value;
};
}
//
const showRegionPicker = () => {
show.value = true;
};
function handleShowRegionPicker() {
showRegionPicker.value = true;
}
//
const cityChange = (e) => {
function handleCityChange(e: any) {
form.region = e.province.label + e.city.label + e.area.label;
formErrors.region = false;
};
}
//
const selectTag = (tag: string) => {
function handleSelectTag(tag: string) {
selectedTag.value = tag;
};
}
//
const validateForm = () => {
let isValid = true;
function validateForm(): boolean {
//
if (!form.name.trim()) {
formErrors.name = true;
isValid = false;
} else {
formErrors.name = false;
}
formErrors.name = !form.name.trim();
//
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(form.phone)) {
formErrors.phone = true;
isValid = false;
} else {
formErrors.phone = false;
}
formErrors.phone = !phoneReg.test(form.phone);
//
if (!form.region) {
formErrors.region = true;
isValid = false;
} else {
formErrors.region = false;
}
formErrors.region = !form.region;
//
if (!form.address.trim()) {
formErrors.address = true;
isValid = false;
} else {
formErrors.address = false;
}
return isValid;
};
formErrors.address = !form.address.trim();
// false
return !(formErrors.name || formErrors.phone || formErrors.region || formErrors.address);
}
//
const saveAddress = () => {
function handleSaveAddress() {
// 使
if (!validateForm()) {
uni.showToast({
title: '请填写完整信息',
@ -127,53 +97,26 @@ const saveAddress = () => {
});
return;
}
try {
//
let addressList = uni.getStorageSync('addressList') || [];
//
const addressData = {
id: isEdit.value ? editId.value : Date.now().toString(),
name: form.name,
phone: form.phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2'), // 4
phoneOriginal: form.phone, //
region: form.region,
address: form.address,
tag: selectedTag.value,
isDefault: defaultAddress.value
};
if (defaultAddress.value) {
//
addressList = addressList.map((item: any) => {
return { ...item, isDefault: false };
const success = saveAddress();
if (success) {
uni.showToast({
title: isEdit.value ? '修改成功' : '添加成功',
icon: 'success'
});
//
setTimeout(() => {
tab.navigateBack();
}, 1000);
} else {
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
if (isEdit.value) {
//
const index = addressList.findIndex((item: any) => item.id === editId.value);
if (index !== -1) {
addressList[index] = addressData;
}
} else {
//
addressList.push(addressData);
}
//
uni.setStorageSync('addressList', addressList);
uni.showToast({
title: isEdit.value ? '修改成功' : '添加成功',
icon: 'success'
});
//
setTimeout(() => {
tab.navigateBack();
}, 1000);
} catch (e) {
console.error('保存地址失败', e);
uni.showToast({
@ -181,30 +124,35 @@ const saveAddress = () => {
icon: 'none'
});
}
};
}
//
const deleteAddress = () => {
function handleDeleteAddress() {
if (!isEdit.value) return;
uni.showModal({
title: '提示',
content: '确定要删除此地址吗?',
success: (res) => {
if (res.confirm) {
try {
let addressList = uni.getStorageSync('addressList') || [];
addressList = addressList.filter((item: any) => item.id !== editId.value);
uni.setStorageSync('addressList', addressList);
uni.showToast({
title: '删除成功',
icon: 'success'
});
setTimeout(() => {
tab.navigateBack();
}, 1000);
const success = deleteAddress();
if (success) {
uni.showToast({
title: '删除成功',
icon: 'success'
});
setTimeout(() => {
tab.navigateBack();
}, 1000);
} else {
uni.showToast({
title: '删除失败',
icon: 'none'
});
}
} catch (e) {
console.error('删除地址失败', e);
uni.showToast({
@ -215,8 +163,9 @@ const deleteAddress = () => {
}
}
});
};
}
</script>
<template>
<view class="wrap">
<view class="container">
@ -225,71 +174,50 @@ const deleteAddress = () => {
<view class="left">
<text class="required">*</text>收货人
</view>
<input
type="text"
v-model="form.name"
placeholder-class="line"
placeholder="请填写收货人姓名"
:class="{ 'error-input': formErrors.name }"
/>
<input type="text" v-model="form.name" placeholder-class="line" placeholder="请填写收货人姓名"
:class="{ 'error-input': formErrors.name }" />
<u-icon name="account" size="36rpx" color="#999"></u-icon>
</view>
<view class="error-msg" v-if="formErrors.name">请输入收货人姓名</view>
<view class="item">
<view class="left">
<text class="required">*</text>手机号码
</view>
<input
type="number"
v-model="form.phone"
placeholder-class="line"
placeholder="请填写收货人手机号"
maxlength="11"
:class="{ 'error-input': formErrors.phone }"
/>
<input type="number" v-model="form.phone" placeholder-class="line" placeholder="请填写收货人手机号" maxlength="11"
:class="{ 'error-input': formErrors.phone }" />
<u-icon name="phone" size="36rpx" color="#999"></u-icon>
</view>
<view class="error-msg" v-if="formErrors.phone">请输入正确的手机号码</view>
<view class="item" @tap="showRegionPicker">
<view class="item" @tap="handleShowRegionPicker">
<view class="left">
<text class="required">*</text>所在地区
</view>
<input
disabled
v-model="form.region"
type="text"
placeholder-class="line"
placeholder="省市区县、乡镇等"
:class="{ 'error-input': formErrors.region }"
/>
<input disabled v-model="form.region" type="text" placeholder-class="line" placeholder="省市区县、乡镇等"
:class="{ 'error-input': formErrors.region }" />
<u-icon name="arrow-right" size="36rpx" color="#999"></u-icon>
</view>
<view class="error-msg" v-if="formErrors.region">请选择所在地区</view>
<view class="item address">
<view class="left">
<text class="required">*</text>详细地址
</view>
<textarea
v-model="form.address"
type="text"
placeholder-class="line"
placeholder="街道、楼牌等"
:class="{ 'error-textarea': formErrors.address }"
/>
<textarea v-model="form.address" type="text" placeholder-class="line" placeholder="街道、楼牌等"
:class="{ 'error-textarea': formErrors.address }" />
</view>
<view class="error-msg" v-if="formErrors.address">请输入详细地址</view>
</view>
<view class="bottom">
<view class="tag">
<view class="left">标签</view>
<view class="right">
<text class="tags" :class="{'active': selectedTag === '家'}" @tap="selectTag('家')"></text>
<text class="tags" :class="{'active': selectedTag === '公司'}" @tap="selectTag('公司')">公司</text>
<text class="tags" :class="{'active': selectedTag === '学校'}" @tap="selectTag('学校')">学校</text>
<text v-for="tag in addressTags" :key="tag" class="tags" :class="{ 'active': selectedTag === tag }"
@tap="handleSelectTag(tag)">
{{ tag }}
</text>
<view class="tags plus"><u-icon size="22" name="plus" color="#999"></u-icon></view>
</view>
</view>
@ -299,22 +227,22 @@ const deleteAddress = () => {
<view class="tips">提醒每次下单会默认推荐该地址</view>
</view>
<view class="right">
<switch color="#fa3534" :checked="defaultAddress" @change="setDefault" />
<switch color="#fa3534" :checked="defaultAddress" @change="handleSetDefault" />
</view>
</view>
</view>
<view class="button-group">
<view class="save-btn" @tap="saveAddress">
<view class="save-btn" @tap="handleSaveAddress">
{{ isEdit ? '保存修改' : '保存地址' }}
</view>
<view v-if="isEdit" class="delete-btn" @tap="deleteAddress">
<view v-if="isEdit" class="delete-btn" @tap="handleDeleteAddress">
删除地址
</view>
</view>
</view>
<city-select v-model="show" @city-change="cityChange"></city-select>
<city-select v-model="showRegionPicker" @city-change="handleCityChange"></city-select>
</view>
</template>
@ -352,7 +280,7 @@ const deleteAddress = () => {
width: 180rpx;
font-weight: 500;
color: #333;
.required {
color: #fa3534;
margin-right: 4rpx;
@ -364,17 +292,17 @@ const deleteAddress = () => {
flex: 1;
height: 100rpx;
font-size: 30rpx;
&.error-input {
border-bottom: 1px solid #fa3534;
}
}
u-icon {
margin-left: 10rpx;
}
}
.error-msg {
color: #fa3534;
font-size: 24rpx;
@ -400,7 +328,7 @@ const deleteAddress = () => {
padding: 20rpx;
border-radius: 12rpx;
font-size: 30rpx;
&.error-textarea {
border: 1px solid #fa3534;
}
@ -444,7 +372,7 @@ const deleteAddress = () => {
color: #333;
line-height: 1;
transition: all 0.3s;
&.active {
background-color: #ffebec;
color: #fa3534;
@ -471,7 +399,7 @@ const deleteAddress = () => {
color: #333;
font-size: 30rpx;
}
.tips {
font-size: 24rpx;
color: #999;
@ -480,12 +408,12 @@ const deleteAddress = () => {
}
}
}
.button-group {
display: flex;
flex-direction: column;
margin-top: 60rpx;
.save-btn {
background: linear-gradient(90deg, #ff4034, #fa3534);
color: #fff;
@ -498,7 +426,7 @@ const deleteAddress = () => {
box-shadow: 0 10rpx 20rpx rgba(250, 53, 52, 0.2);
letter-spacing: 2rpx;
}
.delete-btn {
margin-top: 30rpx;
background: #ffffff;

View File

@ -0,0 +1,256 @@
import { ref, reactive, computed } from 'vue';
import { onShow } from '@dcloudio/uni-app';
/**
*
*/
export interface AddressInfo {
id: string; // 地址ID
name: string; // 收货人姓名
phone: string; // 手机号码(已脱敏)
region: string; // 地区(如: 广东省深圳市南山区)
address: string; // 详细地址
tag: string; // 地址标签(如: 家、公司、学校)
isDefault: boolean; // 是否为默认地址
}
/**
*
*/
const sampleAddresses: AddressInfo[] = [
{
id: '1',
name: '张三',
phone: '13712348888',
region: '广东省深圳市南山区',
address: '科技园南路888号创新大厦A座10楼',
tag: '公司',
isDefault: true
},
{
id: '2',
name: '李四',
phone: '13912345678',
region: '广东省深圳市福田区',
address: '福中路1000号海城大厦B座20楼2001室',
tag: '家',
isDefault: false
},
{
id: '3',
name: '王五',
phone: '15812342233',
region: '广东省广州市天河区',
address: '天河路100号天河城购物中心附近小区A栋3单元701室',
tag: '学校',
isDefault: false
}
];
// 共享的地址数据
const addressStore = {
list: ref<AddressInfo[]>([]),
tags: ref(['家', '公司', '学校'])
};
/**
* 4
*/
export function formatPhoneNumber(phone: string): string {
return phone.replace(/(\d{3})\d{4}(\d{4})/, '$1****$2');
}
/**
* Hook
* @description
*/
export function useAddressListPage() {
// 从共享store获取响应式数据
const addressList = addressStore.list;
// 使用计算属性计算是否为空状态
const emptyStatus = computed(() => addressList.value.length === 0);
// 更新地址列表
function refreshAddressList() {
addressList.value = sampleAddresses
// 实际项目中这里应该调用API获取最新的地址列表
// const response = await api.getAddressList();
// addressList.value = response.data;
}
// 设置默认地址
function setDefaultAddress(id: string): boolean {
const index = addressList.value.findIndex(item => item.id === id);
if (index === -1) return false;
// 更新所有地址的默认状态
addressList.value = addressList.value.map(item => ({
...item,
isDefault: item.id === id
}));
return true;
}
// 删除地址
function deleteAddress(id: string): boolean {
const initialLength = addressList.value.length;
addressList.value = addressList.value.filter(item => item.id !== id);
return addressList.value.length !== initialLength;
}
// 页面显示时刷新数据
onShow(() => {
refreshAddressList();
});
return {
// 响应式状态
addressList,
emptyStatus,
// 方法
setDefaultAddress,
deleteAddress,
refreshAddressList
};
}
/**
* Hook
* @description
*/
export function useAddressEditPage() {
// 从共享store获取响应式数据
const addressList = addressStore.list;
const addressTags = addressStore.tags;
// 页面状态
const isEdit = ref(false);
const editId = ref('');
const defaultAddress = ref(false);
const selectedTag = ref('家');
// 表单数据
const form = reactive({
name: '',
phone: '',
region: '',
address: ''
});
// 加载编辑数据
function loadAddressData(id: string): boolean {
const address = addressList.value.find(item => item.id === id);
if (!address) return false;
// 填充表单数据
form.name = address.name;
form.phone = address.phone;
form.region = address.region;
form.address = address.address;
selectedTag.value = address.tag;
defaultAddress.value = address.isDefault;
return true;
}
// 初始化页面
function initEditPage(id?: string) {
// 重置状态
isEdit.value = !!id;
editId.value = id || '';
defaultAddress.value = false;
selectedTag.value = '家';
form.name = '';
form.phone = '';
form.region = '';
form.address = '';
// 如果是编辑模式,加载地址数据
if (id) {
loadAddressData(id);
}
}
// 保存地址
function saveAddress(): boolean {
if (isEdit.value) {
// 编辑现有地址
const index = addressList.value.findIndex(item => item.id === editId.value);
if (index === -1) return false;
// 如果设为默认,更新其他地址
if (defaultAddress.value) {
addressList.value = addressList.value.map(item => {
if (item.id !== editId.value) {
return { ...item, isDefault: false };
}
return item;
});
}
// 更新当前地址
addressList.value[index] = {
...addressList.value[index],
name: form.name,
phone: form.phone,
region: form.region,
address: form.address,
tag: selectedTag.value,
isDefault: defaultAddress.value
};
} else {
// 添加新地址
const newId = Date.now().toString();
// 如果设为默认,更新其他地址
if (defaultAddress.value) {
addressList.value = addressList.value.map(item => ({
...item,
isDefault: false
}));
}
// 添加新地址
addressList.value.push({
id: newId,
name: form.name,
phone: form.phone,
region: form.region,
address: form.address,
tag: selectedTag.value,
isDefault: defaultAddress.value
});
}
return true;
}
// 删除地址
function deleteAddress(): boolean {
if (!isEdit.value) return false;
const initialLength = addressList.value.length;
addressList.value = addressList.value.filter(item => item.id !== editId.value);
return addressList.value.length !== initialLength;
}
return {
// 响应式状态
isEdit,
editId,
form,
defaultAddress,
selectedTag,
addressTags,
// 方法
initEditPage,
saveAddress,
deleteAddress
};
}

View File

@ -1,30 +1,14 @@
<script setup>
import { ref, onMounted } from 'vue';
import { onShow } from '@dcloudio/uni-app';
<script setup lang="ts">
import tab from '@/plugins/tab';
import { AddressInfo, useAddressListPage } from './index';
const siteList = ref([]);
const emptyStatus = ref(false);
// 使onShow
onShow(() => {
getData();
});
//
function getData() {
try {
const addressList = uni.getStorageSync('addressList') || [];
siteList.value = addressList;
emptyStatus.value = addressList.length === 0;
} catch (e) {
console.error('获取地址列表失败', e);
uni.showToast({
title: '获取地址列表失败',
icon: 'none'
});
}
}
// 使Hook
const {
addressList,
emptyStatus,
setDefaultAddress,
deleteAddress
} = useAddressListPage();
//
function toAddSite() {
@ -32,25 +16,26 @@ function toAddSite() {
}
//
function toEditSite(id) {
function toEditSite(id: string) {
tab.navigateTo(`/pages_template/pages/address/addSite?id=${id}`);
}
//
function setAsDefault(id) {
function handleSetDefault(id: string) {
try {
let addressList = uni.getStorageSync('addressList') || [];
addressList = addressList.map(item => {
return { ...item, isDefault: item.id === id };
});
const success = setDefaultAddress(id);
uni.setStorageSync('addressList', addressList);
getData(); //
uni.showToast({
title: '设置成功',
icon: 'success'
});
if (success) {
uni.showToast({
title: '设置成功',
icon: 'success'
});
} else {
uni.showToast({
title: '设置失败',
icon: 'none'
});
}
} catch (e) {
console.error('设置默认地址失败', e);
uni.showToast({
@ -61,22 +46,26 @@ function setAsDefault(id) {
}
//
function deleteAddress(id) {
function handleDeleteAddress(id: string) {
uni.showModal({
title: '提示',
content: '确定要删除此地址吗?',
success: (res) => {
if (res.confirm) {
try {
let addressList = uni.getStorageSync('addressList') || [];
addressList = addressList.filter(item => item.id !== id);
uni.setStorageSync('addressList', addressList);
getData(); //
const success = deleteAddress(id);
uni.showToast({
title: '删除成功',
icon: 'success'
});
if (success) {
uni.showToast({
title: '删除成功',
icon: 'success'
});
} else {
uni.showToast({
title: '删除失败',
icon: 'none'
});
}
} catch (e) {
console.error('删除地址失败', e);
uni.showToast({
@ -90,9 +79,9 @@ function deleteAddress(id) {
}
//
function selectAddress(address) {
function selectAddress(address: AddressInfo) {
const pages = getCurrentPages();
const prevPage = pages[pages.length - 2];
const prevPage: any = pages[pages.length - 2];
//
if (prevPage && prevPage.$page?.options?.from === 'order') {
@ -102,6 +91,7 @@ function selectAddress(address) {
}
}
</script>
<template>
<view class="address-container">
<!-- 空状态 -->
@ -112,7 +102,7 @@ function selectAddress(address) {
<!-- 地址列表 -->
<view v-else>
<view class="item" v-for="(address, index) in siteList" :key="address.id">
<view class="item" v-for="(address, index) in addressList" :key="address.id">
<view class="top" @tap="selectAddress(address)">
<view class="name">{{ address.name }}</view>
<view class="phone">{{ address.phone }}</view>
@ -125,7 +115,7 @@ function selectAddress(address) {
{{ address.region }} {{ address.address }}
</view>
<view class="actions">
<view class="action-btn" @tap="setAsDefault(address.id)" v-if="!address.isDefault">
<view class="action-btn" @tap="handleSetDefault(address.id)" v-if="!address.isDefault">
<u-icon name="checkmark-circle" color="#999" size="40rpx"></u-icon>
<text>设为默认</text>
</view>
@ -133,7 +123,7 @@ function selectAddress(address) {
<u-icon name="edit-pen" color="#999" size="40rpx"></u-icon>
<text>编辑</text>
</view>
<view class="action-btn" @tap="deleteAddress(address.id)">
<view class="action-btn" @tap="handleDeleteAddress(address.id)">
<u-icon name="trash" color="#999" size="40rpx"></u-icon>
<text>删除</text>
</view>

View File

@ -1,11 +1,6 @@
<script setup>
import { ref } from 'vue';
import citySelect from '@/components/u-city-select/u-city-select.vue';
const height = ref(30);
const bgColor = ref(uni.$u.color.bgColor);
const marginTop = ref(30);
const marginBottom = ref(30);
const value = ref(false);
const input = ref('');

View File

@ -126,29 +126,43 @@ const getComment = () => {
.comment {
display: flex;
padding: 30rpx;
margin-bottom: 20rpx;
background-color: #ffffff;
border-radius: 12rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s;
&:hover {
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.1);
}
.left {
image {
width: 64rpx;
height: 64rpx;
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background-color: #f2f2f2;
border: 2rpx solid #eaeaea;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
}
}
.right {
flex: 1;
padding-left: 20rpx;
font-size: 30rpx;
padding-left: 24rpx;
font-size: 28rpx;
line-height: 1.6;
.top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10rpx;
margin-bottom: 12rpx;
.name {
color: #5677fc;
font-weight: 500;
font-size: 30rpx;
}
.like {
@ -156,9 +170,16 @@ const getComment = () => {
align-items: center;
color: #9a9a9a;
font-size: 26rpx;
padding: 4rpx 12rpx;
border-radius: 30rpx;
transition: all 0.2s;
&:active {
background-color: rgba(86, 119, 252, 0.1);
}
.num {
margin-right: 4rpx;
margin-right: 8rpx;
color: #9a9a9a;
}
}
@ -173,20 +194,32 @@ const getComment = () => {
}
.content {
margin-bottom: 10rpx;
margin-bottom: 16rpx;
color: #333333;
line-height: 1.8;
}
.reply-box {
background-color: rgb(242, 242, 242);
background-color: #f7f7f7;
border-radius: 12rpx;
margin-top: 12rpx;
margin-bottom: 8rpx;
overflow: hidden;
.item {
padding: 20rpx;
border-bottom: solid 2rpx $u-border-color;
border-bottom: solid 1rpx rgba(0, 0, 0, 0.05);
.username {
font-size: 24rpx;
color: #999999;
font-size: 26rpx;
color: #5677fc;
font-weight: 500;
margin-bottom: 6rpx;
}
.text {
font-size: 28rpx;
color: #333333;
}
}
@ -195,6 +228,12 @@ const getComment = () => {
display: flex;
color: #5677fc;
align-items: center;
font-size: 26rpx;
transition: all 0.2s;
&:active {
background-color: rgba(86, 119, 252, 0.1);
}
.more {
margin-left: 6rpx;
@ -207,10 +246,18 @@ const getComment = () => {
display: flex;
font-size: 24rpx;
color: #9a9a9a;
align-items: center;
.reply {
color: #5677fc;
margin-left: 10rpx;
margin-left: 16rpx;
padding: 4rpx 16rpx;
border-radius: 30rpx;
transition: all 0.2s;
&:active {
background-color: rgba(86, 119, 252, 0.1);
}
}
}
}

View File

@ -0,0 +1,197 @@
<script setup>
import { defineProps } from 'vue';
const props = defineProps({
order: {
type: Object,
required: true
}
});
//
const priceDecimal = (val) => {
if (val !== parseInt(val)) return val.slice(-2);
else return '00';
};
//
const priceInt = (val) => {
if (val !== parseInt(val)) return val.split('.')[0];
else return val;
};
//
const totalPrice = (item) => {
let price = 0;
item.forEach(val => {
price += parseFloat(val.price);
});
return price.toFixed(2);
};
//
const totalNum = (item) => {
let num = 0;
item.forEach(val => {
num += val.number;
});
return num;
};
</script>
<template>
<view class="order">
<view class="top">
<view class="left">
<u-icon name="home" :size="30" color="rgb(94,94,94)"></u-icon>
<view class="store">{{ order.store }}</view>
<u-icon name="arrow-right" color="rgb(203,203,203)" :size="26"></u-icon>
</view>
<view class="right">{{ order.deal }}</view>
</view>
<view class="item" v-for="(item, index) in order.goodsList" :key="index">
<view class="left">
<image :src="item.goodsUrl" mode="aspectFill"></image>
</view>
<view class="content">
<view class="title u-line-2">{{ item.title }}</view>
<view class="type">{{ item.type }}</view>
<view class="delivery-time">发货时间 {{ item.deliveryTime }}</view>
</view>
<view class="right">
<view class="price">
{{ priceInt(item.price) }}
<text class="decimal">.{{ priceDecimal(item.price) }}</text>
</view>
<view class="number">x{{ item.number }}</view>
</view>
</view>
<view class="total">
{{ totalNum(order.goodsList) }}件商品 合计:
<text class="total-price">
{{ priceInt(totalPrice(order.goodsList)) }}.
<text class="decimal">{{ priceDecimal(totalPrice(order.goodsList)) }}</text>
</text>
</view>
<view class="bottom">
<view class="more"><u-icon name="more-dot-fill" color="rgb(203,203,203)"></u-icon></view>
<view class="logistics btn">查看物流</view>
<view class="exchange btn">卖了换钱</view>
<view class="evaluate btn">评价</view>
</view>
</view>
</template>
<style lang="scss" scoped>
.order {
width: 710rpx;
background-color: #ffffff;
margin: 20rpx auto;
border-radius: 20rpx;
box-sizing: border-box;
padding: 20rpx;
font-size: 28rpx;
.top {
display: flex;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.store {
margin: 0 10rpx;
font-size: 32rpx;
font-weight: bold;
}
}
.right {
color: $u-warning-dark;
}
}
.item {
display: flex;
margin: 20rpx 0 0;
.left {
margin-right: 20rpx;
image {
width: 200rpx;
height: 200rpx;
border-radius: 10rpx;
}
}
.content {
.title {
font-size: 28rpx;
line-height: 50rpx;
}
.type {
margin: 10rpx 0;
font-size: 24rpx;
color: $u-tips-color;
}
.delivery-time {
color: #e5d001;
font-size: 24rpx;
}
}
.right {
margin-left: 10rpx;
padding-top: 20rpx;
text-align: right;
.decimal {
font-size: 24rpx;
margin-top: 4rpx;
}
.number {
color: $u-tips-color;
font-size: 24rpx;
}
}
}
.total {
margin-top: 20rpx;
text-align: right;
font-size: 24rpx;
.total-price {
font-size: 32rpx;
}
}
.bottom {
display: flex;
margin-top: 40rpx;
padding: 0 10rpx;
justify-content: space-between;
align-items: center;
.btn {
line-height: 52rpx;
width: 160rpx;
border-radius: 26rpx;
border: 2rpx solid $u-border-color;
font-size: 26rpx;
text-align: center;
color: $u-info-dark;
}
.evaluate {
color: $u-warning-dark;
border-color: $u-warning-dark;
}
}
}
</style>

View File

@ -1,5 +1,6 @@
<script setup>
import { ref, reactive, onMounted } from 'vue';
import OrderItem from './OrderItem.vue';
const orderList = ref([[], [], [], []]);
const dataList = reactive([
@ -119,18 +120,6 @@ onMounted(() => {
getOrderList(3);
});
//
const priceDecimal = (val) => {
if (val !== parseInt(val)) return val.slice(-2);
else return '00';
};
//
const priceInt = (val) => {
if (val !== parseInt(val)) return val.split('.')[0];
else return val;
};
//
const reachBottom = () => {
loadStatus.value.splice(current.value, 1, "loading");
@ -151,34 +140,21 @@ const getOrderList = (idx) => {
loadStatus.value.splice(current.value, 1, "loadmore");
};
//
const totalPrice = (item) => {
let price = 0;
item.forEach(val => {
price += parseFloat(val.price);
});
return price.toFixed(2);
};
//
const totalNum = (item) => {
let num = 0;
item.forEach(val => {
num += val.number;
});
return num;
};
// tab
const change = ({ index }) => {
current.value = index; // current
swiperCurrent.value = index;
getOrderList(index);
};
const animationfinish = ({ detail: { current } }) => {
swiperCurrent.value = current;
current.value = current;
const animationfinish = (e) => {
const currentIndex = e.detail.current;
swiperCurrent.value = currentIndex;
current.value = currentIndex; // currentswipercurrent
};
// tabsref
const tabs = ref(null);
</script>
<template>
@ -193,62 +169,20 @@ const animationfinish = ({ detail: { current } }) => {
<scroll-view scroll-y style="height: 100%;width: 100%;" @scrolltolower="reachBottom"
v-if="orderlist.length !== 0">
<view class="page-box">
<view class="order" v-for="(res, index) in orderlist" :key="res.id">
<view class="top">
<view class="left">
<u-icon name="home" :size="30" color="rgb(94,94,94)"></u-icon>
<view class="store">{{ res.store }}</view>
<u-icon name="arrow-right" color="rgb(203,203,203)" :size="26"></u-icon>
</view>
<view class="right">{{ res.deal }}</view>
</view>
<view class="item" v-for="(item, index) in res.goodsList" :key="index">
<view class="left">
<image :src="item.goodsUrl" mode="aspectFill"></image>
</view>
<view class="content">
<view class="title u-line-2">{{ item.title }}</view>
<view class="type">{{ item.type }}</view>
<view class="delivery-time">发货时间 {{ item.deliveryTime }}</view>
</view>
<view class="right">
<view class="price">
{{ priceInt(item.price) }}
<text class="decimal">.{{ priceDecimal(item.price) }}</text>
</view>
<view class="number">x{{ item.number }}</view>
</view>
</view>
<view class="total">
{{ totalNum(res.goodsList) }}件商品 合计:
<text class="total-price">
{{ priceInt(totalPrice(res.goodsList)) }}.
<text class="decimal">{{ priceDecimal(totalPrice(res.goodsList)) }}</text>
</text>
</view>
<view class="bottom">
<view class="more"><u-icon name="more-dot-fill" color="rgb(203,203,203)"></u-icon>
</view>
<view class="logistics btn">查看物流</view>
<view class="exchange btn">卖了换钱</view>
<view class="evaluate btn">评价</view>
</view>
</view>
<OrderItem v-for="res in orderlist" :key="res.id" :order="res" />
<u-loadmore :status="loadStatus[0]" bgColor="#f2f2f2"></u-loadmore>
</view>
</scroll-view>
<scroll-view scroll-y style="height: 100%;width: 100%;" v-else>
<view class="page-box">
<view>
<view class="centre">
<image src="https://cdn.uviewui.com/uview/template/taobao-order.png" mode="">
</image>
<view class="explain">
您还没有相关的订单
<view class="tips">可以去看看有那些想买的</view>
</view>
<view class="btn">随便逛逛</view>
<view class="centre">
<image src="https://cdn.uviewui.com/uview/template/taobao-order.png" mode="aspectFit" class="empty-image">
</image>
<view class="explain">
您还没有相关的订单
<view class="tips">可以去看看有那些想买的</view>
</view>
<view class="btn">随便逛逛</view>
</view>
</view>
</scroll-view>
@ -268,128 +202,18 @@ page {
</style>
<style lang="scss" scoped>
.order {
width: 710rpx;
background-color: #ffffff;
margin: 20rpx auto;
border-radius: 20rpx;
box-sizing: border-box;
padding: 20rpx;
font-size: 28rpx;
.top {
display: flex;
justify-content: space-between;
.left {
display: flex;
align-items: center;
.store {
margin: 0 10rpx;
font-size: 32rpx;
font-weight: bold;
}
}
.right {
color: $u-warning-dark;
}
}
.item {
display: flex;
margin: 20rpx 0 0;
.left {
margin-right: 20rpx;
image {
width: 200rpx;
height: 200rpx;
border-radius: 10rpx;
}
}
.content {
.title {
font-size: 28rpx;
line-height: 50rpx;
}
.type {
margin: 10rpx 0;
font-size: 24rpx;
color: $u-tips-color;
}
.delivery-time {
color: #e5d001;
font-size: 24rpx;
}
}
.right {
margin-left: 10rpx;
padding-top: 20rpx;
text-align: right;
.decimal {
font-size: 24rpx;
margin-top: 4rpx;
}
.number {
color: $u-tips-color;
font-size: 24rpx;
}
}
}
.total {
margin-top: 20rpx;
text-align: right;
font-size: 24rpx;
.total-price {
font-size: 32rpx;
}
}
.bottom {
display: flex;
margin-top: 40rpx;
padding: 0 10rpx;
justify-content: space-between;
align-items: center;
.btn {
line-height: 52rpx;
width: 160rpx;
border-radius: 26rpx;
border: 2rpx solid $u-border-color;
font-size: 26rpx;
text-align: center;
color: $u-info-dark;
}
.evaluate {
color: $u-warning-dark;
border-color: $u-warning-dark;
}
}
}
.centre {
text-align: center;
margin: 200rpx auto;
font-size: 32rpx;
width: 100%;
image {
.empty-image {
width: 164rpx;
height: 164rpx;
border-radius: 50%;
margin-bottom: 20rpx;
margin: 0 auto 20rpx;
display: block; /* 确保图片作为块级元素 */
}
.tips {