优化模板界面内容

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

View File

@ -2,22 +2,24 @@
import { ref, reactive, onMounted } from 'vue'; import { ref, reactive, onMounted } from 'vue';
import tab from '@/plugins/tab'; import tab from '@/plugins/tab';
import citySelect from '@/components/u-city-select/u-city-select.vue'; import citySelect from '@/components/u-city-select/u-city-select.vue';
import { useAddressEditPage } from './index';
// // 使Hook
const show = ref(false); const {
const defaultAddress = ref(false); isEdit,
const selectedTag = ref('家'); form,
const isEdit = ref(false); // defaultAddress,
const editId = ref(''); // ID selectedTag,
addressTags,
initEditPage,
saveAddress,
deleteAddress
} = useAddressEditPage();
// //
const form = reactive({ const showRegionPicker = ref(false);
name: '',
phone: '',
region: '',
address: ''
});
// - Vue
const formErrors = reactive({ const formErrors = reactive({
name: false, name: false,
phone: false, phone: false,
@ -25,101 +27,69 @@ const formErrors = reactive({
address: false address: false
}); });
onMounted(() => { //
// function resetFormErrors() {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
const options = currentPage.$page?.options;
if (options && options.id) {
isEdit.value = true;
editId.value = options.id;
loadAddressData(options.id);
}
});
//
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) => {
defaultAddress.value = e.detail.value;
};
//
const showRegionPicker = () => {
show.value = true;
};
//
const cityChange = (e) => {
form.region = e.province.label + e.city.label + e.area.label;
formErrors.region = false;
};
//
const selectTag = (tag: string) => {
selectedTag.value = tag;
};
//
const validateForm = () => {
let isValid = true;
//
if (!form.name.trim()) {
formErrors.name = true;
isValid = false;
} else {
formErrors.name = false; formErrors.name = false;
}
//
const phoneReg = /^1[3-9]\d{9}$/;
if (!phoneReg.test(form.phone)) {
formErrors.phone = true;
isValid = false;
} else {
formErrors.phone = false; formErrors.phone = false;
}
//
if (!form.region) {
formErrors.region = true;
isValid = false;
} else {
formErrors.region = false; formErrors.region = false;
}
//
if (!form.address.trim()) {
formErrors.address = true;
isValid = false;
} else {
formErrors.address = false; formErrors.address = false;
} }
return isValid; //
}; onMounted(() => {
const pages = getCurrentPages();
const currentPage: any = pages[pages.length - 1];
const options = currentPage.$page?.options;
// hook
initEditPage(options?.id);
//
resetFormErrors();
});
//
function handleSetDefault(e: any) {
defaultAddress.value = e.detail.value;
}
//
function handleShowRegionPicker() {
showRegionPicker.value = true;
}
//
function handleCityChange(e: any) {
form.region = e.province.label + e.city.label + e.area.label;
formErrors.region = false;
}
//
function handleSelectTag(tag: string) {
selectedTag.value = tag;
}
//
function validateForm(): boolean {
//
formErrors.name = !form.name.trim();
//
const phoneReg = /^1[3-9]\d{9}$/;
formErrors.phone = !phoneReg.test(form.phone);
//
formErrors.region = !form.region;
//
formErrors.address = !form.address.trim();
// false
return !(formErrors.name || formErrors.phone || formErrors.region || formErrors.address);
}
// //
const saveAddress = () => { function handleSaveAddress() {
// 使
if (!validateForm()) { if (!validateForm()) {
uni.showToast({ uni.showToast({
title: '请填写完整信息', title: '请填写完整信息',
@ -129,42 +99,9 @@ const saveAddress = () => {
} }
try { try {
// const success = saveAddress();
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 };
});
}
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);
if (success) {
uni.showToast({ uni.showToast({
title: isEdit.value ? '修改成功' : '添加成功', title: isEdit.value ? '修改成功' : '添加成功',
icon: 'success' icon: 'success'
@ -174,6 +111,12 @@ const saveAddress = () => {
setTimeout(() => { setTimeout(() => {
tab.navigateBack(); tab.navigateBack();
}, 1000); }, 1000);
} else {
uni.showToast({
title: '保存失败',
icon: 'none'
});
}
} catch (e) { } catch (e) {
console.error('保存地址失败', e); console.error('保存地址失败', e);
uni.showToast({ uni.showToast({
@ -181,10 +124,10 @@ const saveAddress = () => {
icon: 'none' icon: 'none'
}); });
} }
}; }
// //
const deleteAddress = () => { function handleDeleteAddress() {
if (!isEdit.value) return; if (!isEdit.value) return;
uni.showModal({ uni.showModal({
@ -193,10 +136,9 @@ const deleteAddress = () => {
success: (res) => { success: (res) => {
if (res.confirm) { if (res.confirm) {
try { try {
let addressList = uni.getStorageSync('addressList') || []; const success = deleteAddress();
addressList = addressList.filter((item: any) => item.id !== editId.value);
uni.setStorageSync('addressList', addressList);
if (success) {
uni.showToast({ uni.showToast({
title: '删除成功', title: '删除成功',
icon: 'success' icon: 'success'
@ -205,6 +147,12 @@ const deleteAddress = () => {
setTimeout(() => { setTimeout(() => {
tab.navigateBack(); tab.navigateBack();
}, 1000); }, 1000);
} else {
uni.showToast({
title: '删除失败',
icon: 'none'
});
}
} catch (e) { } catch (e) {
console.error('删除地址失败', e); console.error('删除地址失败', e);
uni.showToast({ uni.showToast({
@ -215,8 +163,9 @@ const deleteAddress = () => {
} }
} }
}); });
}; }
</script> </script>
<template> <template>
<view class="wrap"> <view class="wrap">
<view class="container"> <view class="container">
@ -225,13 +174,8 @@ const deleteAddress = () => {
<view class="left"> <view class="left">
<text class="required">*</text>收货人 <text class="required">*</text>收货人
</view> </view>
<input <input type="text" v-model="form.name" placeholder-class="line" placeholder="请填写收货人姓名"
type="text" :class="{ 'error-input': formErrors.name }" />
v-model="form.name"
placeholder-class="line"
placeholder="请填写收货人姓名"
:class="{ 'error-input': formErrors.name }"
/>
<u-icon name="account" size="36rpx" color="#999"></u-icon> <u-icon name="account" size="36rpx" color="#999"></u-icon>
</view> </view>
<view class="error-msg" v-if="formErrors.name">请输入收货人姓名</view> <view class="error-msg" v-if="formErrors.name">请输入收货人姓名</view>
@ -240,30 +184,18 @@ const deleteAddress = () => {
<view class="left"> <view class="left">
<text class="required">*</text>手机号码 <text class="required">*</text>手机号码
</view> </view>
<input <input type="number" v-model="form.phone" placeholder-class="line" placeholder="请填写收货人手机号" maxlength="11"
type="number" :class="{ 'error-input': formErrors.phone }" />
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> <u-icon name="phone" size="36rpx" color="#999"></u-icon>
</view> </view>
<view class="error-msg" v-if="formErrors.phone">请输入正确的手机号码</view> <view class="error-msg" v-if="formErrors.phone">请输入正确的手机号码</view>
<view class="item" @tap="showRegionPicker"> <view class="item" @tap="handleShowRegionPicker">
<view class="left"> <view class="left">
<text class="required">*</text>所在地区 <text class="required">*</text>所在地区
</view> </view>
<input <input disabled v-model="form.region" type="text" placeholder-class="line" placeholder="省市区县、乡镇等"
disabled :class="{ 'error-input': formErrors.region }" />
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> <u-icon name="arrow-right" size="36rpx" color="#999"></u-icon>
</view> </view>
<view class="error-msg" v-if="formErrors.region">请选择所在地区</view> <view class="error-msg" v-if="formErrors.region">请选择所在地区</view>
@ -272,13 +204,8 @@ const deleteAddress = () => {
<view class="left"> <view class="left">
<text class="required">*</text>详细地址 <text class="required">*</text>详细地址
</view> </view>
<textarea <textarea v-model="form.address" type="text" placeholder-class="line" placeholder="街道、楼牌等"
v-model="form.address" :class="{ 'error-textarea': formErrors.address }" />
type="text"
placeholder-class="line"
placeholder="街道、楼牌等"
:class="{ 'error-textarea': formErrors.address }"
/>
</view> </view>
<view class="error-msg" v-if="formErrors.address">请输入详细地址</view> <view class="error-msg" v-if="formErrors.address">请输入详细地址</view>
</view> </view>
@ -287,9 +214,10 @@ const deleteAddress = () => {
<view class="tag"> <view class="tag">
<view class="left">标签</view> <view class="left">标签</view>
<view class="right"> <view class="right">
<text class="tags" :class="{'active': selectedTag === '家'}" @tap="selectTag('家')"></text> <text v-for="tag in addressTags" :key="tag" class="tags" :class="{ 'active': selectedTag === tag }"
<text class="tags" :class="{'active': selectedTag === '公司'}" @tap="selectTag('公司')">公司</text> @tap="handleSelectTag(tag)">
<text class="tags" :class="{'active': selectedTag === '学校'}" @tap="selectTag('学校')">学校</text> {{ tag }}
</text>
<view class="tags plus"><u-icon size="22" name="plus" color="#999"></u-icon></view> <view class="tags plus"><u-icon size="22" name="plus" color="#999"></u-icon></view>
</view> </view>
</view> </view>
@ -299,22 +227,22 @@ const deleteAddress = () => {
<view class="tips">提醒每次下单会默认推荐该地址</view> <view class="tips">提醒每次下单会默认推荐该地址</view>
</view> </view>
<view class="right"> <view class="right">
<switch color="#fa3534" :checked="defaultAddress" @change="setDefault" /> <switch color="#fa3534" :checked="defaultAddress" @change="handleSetDefault" />
</view> </view>
</view> </view>
</view> </view>
<view class="button-group"> <view class="button-group">
<view class="save-btn" @tap="saveAddress"> <view class="save-btn" @tap="handleSaveAddress">
{{ isEdit ? '保存修改' : '保存地址' }} {{ isEdit ? '保存修改' : '保存地址' }}
</view> </view>
<view v-if="isEdit" class="delete-btn" @tap="deleteAddress"> <view v-if="isEdit" class="delete-btn" @tap="handleDeleteAddress">
删除地址 删除地址
</view> </view>
</view> </view>
</view> </view>
<city-select v-model="show" @city-change="cityChange"></city-select> <city-select v-model="showRegionPicker" @city-change="handleCityChange"></city-select>
</view> </view>
</template> </template>

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

View File

@ -1,11 +1,6 @@
<script setup> <script setup>
import { ref } from 'vue'; import { ref } from 'vue';
import citySelect from '@/components/u-city-select/u-city-select.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 value = ref(false);
const input = ref(''); const input = ref('');

View File

@ -126,29 +126,43 @@ const getComment = () => {
.comment { .comment {
display: flex; display: flex;
padding: 30rpx; 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 { .left {
image { image {
width: 64rpx; width: 72rpx;
height: 64rpx; height: 72rpx;
border-radius: 50%; border-radius: 50%;
background-color: #f2f2f2; background-color: #f2f2f2;
border: 2rpx solid #eaeaea;
box-shadow: 0 2rpx 6rpx rgba(0, 0, 0, 0.1);
} }
} }
.right { .right {
flex: 1; flex: 1;
padding-left: 20rpx; padding-left: 24rpx;
font-size: 30rpx; font-size: 28rpx;
line-height: 1.6;
.top { .top {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: 10rpx; margin-bottom: 12rpx;
.name { .name {
color: #5677fc; color: #5677fc;
font-weight: 500;
font-size: 30rpx;
} }
.like { .like {
@ -156,9 +170,16 @@ const getComment = () => {
align-items: center; align-items: center;
color: #9a9a9a; color: #9a9a9a;
font-size: 26rpx; font-size: 26rpx;
padding: 4rpx 12rpx;
border-radius: 30rpx;
transition: all 0.2s;
&:active {
background-color: rgba(86, 119, 252, 0.1);
}
.num { .num {
margin-right: 4rpx; margin-right: 8rpx;
color: #9a9a9a; color: #9a9a9a;
} }
} }
@ -173,20 +194,32 @@ const getComment = () => {
} }
.content { .content {
margin-bottom: 10rpx; margin-bottom: 16rpx;
color: #333333;
line-height: 1.8;
} }
.reply-box { .reply-box {
background-color: rgb(242, 242, 242); background-color: #f7f7f7;
border-radius: 12rpx; border-radius: 12rpx;
margin-top: 12rpx;
margin-bottom: 8rpx;
overflow: hidden;
.item { .item {
padding: 20rpx; padding: 20rpx;
border-bottom: solid 2rpx $u-border-color; border-bottom: solid 1rpx rgba(0, 0, 0, 0.05);
.username { .username {
font-size: 24rpx; font-size: 26rpx;
color: #999999; color: #5677fc;
font-weight: 500;
margin-bottom: 6rpx;
}
.text {
font-size: 28rpx;
color: #333333;
} }
} }
@ -195,6 +228,12 @@ const getComment = () => {
display: flex; display: flex;
color: #5677fc; color: #5677fc;
align-items: center; align-items: center;
font-size: 26rpx;
transition: all 0.2s;
&:active {
background-color: rgba(86, 119, 252, 0.1);
}
.more { .more {
margin-left: 6rpx; margin-left: 6rpx;
@ -207,10 +246,18 @@ const getComment = () => {
display: flex; display: flex;
font-size: 24rpx; font-size: 24rpx;
color: #9a9a9a; color: #9a9a9a;
align-items: center;
.reply { .reply {
color: #5677fc; 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> <script setup>
import { ref, reactive, onMounted } from 'vue'; import { ref, reactive, onMounted } from 'vue';
import OrderItem from './OrderItem.vue';
const orderList = ref([[], [], [], []]); const orderList = ref([[], [], [], []]);
const dataList = reactive([ const dataList = reactive([
@ -119,18 +120,6 @@ onMounted(() => {
getOrderList(3); 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 = () => { const reachBottom = () => {
loadStatus.value.splice(current.value, 1, "loading"); loadStatus.value.splice(current.value, 1, "loading");
@ -151,34 +140,21 @@ const getOrderList = (idx) => {
loadStatus.value.splice(current.value, 1, "loadmore"); 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 // tab
const change = ({ index }) => { const change = ({ index }) => {
current.value = index; // current
swiperCurrent.value = index; swiperCurrent.value = index;
getOrderList(index); getOrderList(index);
}; };
const animationfinish = ({ detail: { current } }) => { const animationfinish = (e) => {
swiperCurrent.value = current; const currentIndex = e.detail.current;
current.value = current; swiperCurrent.value = currentIndex;
current.value = currentIndex; // currentswipercurrent
}; };
// tabsref
const tabs = ref(null);
</script> </script>
<template> <template>
@ -193,55 +169,14 @@ const animationfinish = ({ detail: { current } }) => {
<scroll-view scroll-y style="height: 100%;width: 100%;" @scrolltolower="reachBottom" <scroll-view scroll-y style="height: 100%;width: 100%;" @scrolltolower="reachBottom"
v-if="orderlist.length !== 0"> v-if="orderlist.length !== 0">
<view class="page-box"> <view class="page-box">
<view class="order" v-for="(res, index) in orderlist" :key="res.id"> <OrderItem v-for="res in orderlist" :key="res.id" :order="res" />
<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>
<u-loadmore :status="loadStatus[0]" bgColor="#f2f2f2"></u-loadmore> <u-loadmore :status="loadStatus[0]" bgColor="#f2f2f2"></u-loadmore>
</view> </view>
</scroll-view> </scroll-view>
<scroll-view scroll-y style="height: 100%;width: 100%;" v-else> <scroll-view scroll-y style="height: 100%;width: 100%;" v-else>
<view class="page-box"> <view class="page-box">
<view>
<view class="centre"> <view class="centre">
<image src="https://cdn.uviewui.com/uview/template/taobao-order.png" mode=""> <image src="https://cdn.uviewui.com/uview/template/taobao-order.png" mode="aspectFit" class="empty-image">
</image> </image>
<view class="explain"> <view class="explain">
您还没有相关的订单 您还没有相关的订单
@ -250,7 +185,6 @@ const animationfinish = ({ detail: { current } }) => {
<view class="btn">随便逛逛</view> <view class="btn">随便逛逛</view>
</view> </view>
</view> </view>
</view>
</scroll-view> </scroll-view>
</swiper-item> </swiper-item>
</swiper> </swiper>
@ -268,128 +202,18 @@ page {
</style> </style>
<style lang="scss" scoped> <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 { .centre {
text-align: center; text-align: center;
margin: 200rpx auto; margin: 200rpx auto;
font-size: 32rpx; font-size: 32rpx;
width: 100%;
image { .empty-image {
width: 164rpx; width: 164rpx;
height: 164rpx; height: 164rpx;
border-radius: 50%; border-radius: 50%;
margin-bottom: 20rpx; margin: 0 auto 20rpx;
display: block; /* 确保图片作为块级元素 */
} }
.tips { .tips {