2025-06-11 16:34:15 +08:00

764 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div>
<el-dialog v-model="dialogVisible" title="地址" width="40%" :before-close="handleClose">
<el-form :label-position="labelPosition" label-width="130px" :model="formLabelAlign" style="max-width: 1000px">
<el-form-item label="联系人" prop="receiverName" :rules="[{ required: true, message: '请输入联系人' }]">
<el-input v-model="formLabelAlign.receiverName" />
</el-form-item>
<el-form-item label="联系人手机号" prop="receiverPhone" :rules="[{ required: true, message: '请输入联系人手机号' }]">
<el-input v-model="formLabelAlign.receiverPhone" />
</el-form-item>
<el-form-item label="省市区" prop="postArea" :rules="[{ required: true, message: '省市区' }]">
<el-cascader v-model="formLabelAlign.postArea" style="width: 100%" :props="props" :options="options" @change="handleChange" />
</el-form-item>
<el-form-item label="详细地址" prop="postAddress" :rules="[{ required: true, message: '详细地址' }]">
<el-input v-model="formLabelAlign.postAddress" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" />
</el-form-item>
<el-form-item label="默认地址" prop="isDefault" :rules="[{ required: true, message: '默认地址' }]">
<el-switch v-model="formLabelAlign.isDefault" style="margin-top: -3px" size="large" active-text="是" inactive-text="否" />
</el-form-item>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" @click="submit()"> 确定 </el-button>
</span>
</template>
</el-dialog>
<common current-name="addressList">
<template #main>
<div class="my-shoping-car-warp">
<userHeader :title="'我的地址'"></userHeader>
<div class="page-content-warp">
<div class="fix-top">
<div class="do-all" @click="toCheckAll">
<!-- <div class="do-all-pos">
<ischeck :value="isAll"></ischeck>
<span class="all-txt">全选</span>
</div> -->
</div>
<div>
<div class="up-text" @click="add()">
添加地址
<!-- <el-icon><Plus color="#000000" /></el-icon> <span class="del-txt">新增</span> -->
</div>
<!-- <div class="batch-del" @click="doBatchDel">
<el-icon><Delete /></el-icon> <span class="del-txt">批量删除</span>
</div> -->
</div>
</div>
<div v-if="data.length <= 0" class="empty">
<img style="width: 300px; height: 300px" src="../../assets/images/empty.png" alt="" />
</div>
<div v-else class="conetnt-warp">
<div class="content-item-warp" :style="{ height: tableViewportHeight + 'px' }">
<tableComponent
:table-data="data"
:columns="columns"
:show-border="false"
:show-selection="fasle"
:header-cell-class-name="getHeaderClass"
@page-change="handlePaginationChange"
@selection-change="handleSelectionChange"
>
<template #isDefault="slotProps">
<div
:class="[slotProps.row.isDefault == '1' ? 'pr' : 'er']"
style="width: 120px; display: flex; align-items: center; justify-content: flex-start"
>
{{ slotProps.row.isDefault == '1' ? '是' : '否' }}
<!-- <el-checkbox
:disabled="slotProps.row.isDefault == '1' ? false : true"
:checked="slotProps.row.isDefault == '1' ? true : false"
size="large"
/> -->
</div>
</template>
<template #postAddress="slotProps">
<div style="display: flex; align-items: center">{{ slotProps.row.postArea.join(',') }}{{ slotProps.row.postAddress }}</div>
</template>
<template #action="slotProps">
<div style="width: 100px; display: flex; align-items: center; justify-content: space-between">
<div class="upButton" @click="showDialogVisible(slotProps.row)">编辑</div>
<div class="upButton" @click="del(slotProps.row)">删除</div>
</div>
</template>
</tableComponent>
<!-- <el-table
:show-selection="true"
:header-cell-class-name="getHeaderClass"
:data="data"
style="width: 100%"
@page-change="handlePaginationChange"
@selection-change="handleSelectionChange"
>
<el-table-column prop="date" label="默认收货地址" width="120">
<template #default="slotProps">
<div style="width: 120px; display: flex; align-items: center; justify-content: flex-start">
<el-checkbox
:disabled="slotProps.row.isDefault == '1' ? false : true"
:checked="slotProps.row.isDefault == '1' ? true : false"
size="large"
/>
</div>
</template>
</el-table-column>
<el-table-column prop="receiverName" label="收货人" width="120" />
<el-table-column prop="receiverPhone" label="联系电话" width="160" />
<el-table-column label="详细地址">
<template #default="slotProps">
<div style="display: flex; align-items: center">{{ slotProps.row.postArea.join(',') }}{{ slotProps.row.postAddress }}</div>
</template>
</el-table-column>
<el-table-column prop="date" label="操作" width="180">
<template #default="slotProps">
<div style="width: 100px; display: flex; align-items: center; justify-content: space-between">
<div class="upButton" @click="showDialogVisible(slotProps.row)">编辑</div>
<div class="upButton" @click="del(slotProps.row)">删除</div>
</div>
</template>
</el-table-column>
</el-table> -->
<!-- <div v-for="(n, index) in data" :key="n.id" class="content-item">
<div class="shop-info">
<div class="shop-do" @click="toCheckShop(index)"></div>
<span style="border: 1px solid #dddddd; border-radius: 5px; padding: 3px 5px" class="shop-name txt-ellipsis clamp2"
>联系人{{ n.receiverName }}联系人手机号{{ n.receiverPhone }}地址{{ n.postArea }}{{ n.postAddress }}</span
>
<div style="color: #25bf82; font-size: 20px" @click="showDialogVisible(n)">修改</div>
<div style="color: red; font-size: 20px" @click="del(n)">删除</div>
</div>
</div> -->
</div>
</div>
</div>
</div>
</template>
</common>
</div>
</template>
<script setup>
import common from './components/common.vue';
import { ref, reactive, computed, onMounted } from 'vue';
import { isEmpty, getAssetsFile } from '@/utils';
import { useRoute, useRouter } from 'vue-router';
import userHeader from './components/userHeader.vue';
import ischeck from './components/ischeck.vue';
import costomImg from '@/components/costomImg.vue';
import { useApp } from '@/hooks';
import tableComponent from './components/tableComponent.vue';
import {
shoppingCart,
deleteChooseGoods,
userPostAddressed,
addToCart,
delCartGood,
userPostAddress,
addUserPostAddress,
deleteAddress,
} from '../../apis/user';
import json from '../../assets/address.json';
const app = useApp();
const route = useRoute();
const router = useRouter();
let number = ref(1);
let total = ref(99);
let isAll = ref(false);
let data = ref([]);
let page = reactive({
current: 1,
size: 10,
});
let ids = ref([]);
let dialogVisible = ref(false);
let formLabelAlign = reactive({
receiverName: '',
receiverPhone: '',
postArea: '',
postAddress: '',
isDefault: false,
});
const columns = ref([
{ prop: 'isDefault', label: '是否默认地址', slotName: 'isDefault', width: '180px' },
{ prop: 'receiverName', label: '联系人' },
{ prop: 'receiverPhone', label: '联系人手机号' },
{ prop: 'postAddress', label: '详细地址', slotName: 'postAddress', width: '250px' },
{ prop: 'action', label: '操作', slotName: 'action', width: '180' },
]);
const titleRef = ref(null);
const searchBarRef = ref(null);
const tableViewportHeight = ref(0);
// 精确计算可用高度
const calculateTableHeight = () => {
// 获取窗口总高度
const windowHeight = window.innerHeight;
// 获取各组件高度
const headerHeight = titleRef.value?.$el?.offsetHeight || 0;
const searchBarHeight = searchBarRef.value?.offsetHeight || 0;
// 计算容器内边距补偿(根据实际样式调整)
const paddingCompensation = 60;
// 最终计算
tableViewportHeight.value = windowHeight - headerHeight - searchBarHeight - paddingCompensation - 80;
// console.log(tableViewportHeight.value);
};
// 自定义表头类名,也可以在columns指定列中添加headerClassName: 'custom-header'
const getHeaderClass = ({ column }) => {
return 'custom-header';
};
// 分页变化
const handlePaginationChange = ({ page, pageSize }) => {
console.log('分页变化:', page, pageSize);
// 这里可以调用API获取新数据
// fetchData(page, limit,pageSize)
};
// 多选框变化
const handleSelectionChange = (selection) => {
console.log('选中项:', selection);
};
let nowClickRow = ref(null);
// 编辑操作
const handleEdit = (row, num) => {
nowClickRow.value = row;
console.log('编辑的行:', row);
console.log('第几个按钮:', num);
};
const options = json;
const handleClose = () => {
dialogVisible.value = false;
};
const props = ref({
value: 'label',
});
const showDialogVisible = (data) => {
dialogVisible.value = true;
formLabelAlign = data;
formLabelAlign.isDefault == '1' ? (formLabelAlign.isDefault = true) : (formLabelAlign.isDefault = false);
console.log(data);
};
const submit = (value) => {
formLabelAlign.isDefault ? (formLabelAlign.isDefault = '1') : (formLabelAlign.isDefault = '0');
if (typeof formLabelAlign.postArea !== 'string') {
formLabelAlign.postArea = formLabelAlign.postArea.join(',');
}
if (formLabelAlign.id) {
userPostAddressed(formLabelAlign).then((res) => {
dialogVisible.value = false;
getUserAddressList();
});
} else {
addUserPostAddress(formLabelAlign).then((res) => {
dialogVisible.value = false;
getUserAddressList();
});
}
// console.log(formLabelAlign);
};
onMounted(() => {
getUserAddressList();
calculateTableHeight();
});
const handleChange = (value) => {
console.log(value);
formLabelAlign.postArea = value;
};
const getUserAddressList = () => {
userPostAddress(page).then((res) => {
res.data.records.forEach((item) => {
item.postArea = item.postArea.split(',');
});
console.log(res.data.records);
data.value = res.data.records;
addIsCheckProperty(data);
});
};
function addIsCheckProperty(data) {
if (Array.isArray(data)) {
data.forEach((item) => {
item.ischeck = false; // 为当前层级添加属性
if (item.cartDetails) {
// 处理子集
addIsCheckProperty(item.cartDetails);
}
if (item.records) {
// 处理父级(如存在嵌套结构)
addIsCheckProperty(item.records);
}
});
} else if (typeof data === 'object' && data !== null) {
data.ischeck = false;
for (let key in data) {
if (typeof data[key] === 'object') {
addIsCheckProperty(data[key]); // 递归处理嵌套对象
}
}
}
}
const numberChange = (value, index, indexg) => {
console.log(value, index, indexg);
if (number.value < value) {
// addToCart();
}
number.value = value;
};
// let totalAmout = computed(() => {
// let num = 0;
// let list = [];
// if (data && data.value.length > 0) {
// list = data.value
// .map((m) => {
// return m.cartDetails;
// })
// .flat();
// if (list && list.length > 0) {
// num = list.reduce((acc, current) => {
// return acc + (current.ischeck ? current.price * current.quantity : 0);
// }, 0);
// }
// }
// return num;
// });
const toSettlement = () => {
router.push('/sub-operation-service/sureOrder');
};
const toCheckAll = () => {
isAll.value = !isAll.value;
console.info('操作全选', isAll.value);
if (data && data.value.length > 0) {
data.value.forEach((m) => {
m.ischeck = isAll.value;
if (m.cartDetails && m.cartDetails.length > 0) {
m.cartDetails = setCheck(m.cartDetails, isAll.value);
m.cartDetails.forEach((res, index) => {
ids.value.push(res.id);
});
}
});
}
};
const setCheck = (data, val) => {
let list = [];
if (data && data.length > 0) {
list = data.map((m) => {
return { ...m, ischeck: val };
});
}
return list;
};
const toCheckShop = (index) => {
if (index > -1) {
data.value[index].ischeck = !data.value[index].ischeck;
if (data.value[index].cartDetails && data.value[index].cartDetails.length > 0) {
data.value[index].cartDetails = setCheck(data.value[index].cartDetails, data.value[index].ischeck);
}
setIsAll();
}
};
const toCheckGood = (indexP, index) => {
if (indexP > -1 && index > -1) {
let list = data.value[indexP].cartDetails;
list[index].ischeck = !list[index].ischeck;
let len = list.length || 0;
let checkNum = list.reduce((acc, current) => {
return acc + (current.ischeck ? 1 : 0);
}, 0);
if (checkNum > 0 && checkNum < len) {
data.value[indexP].ischeck = false;
} else if (checkNum > 0 && checkNum == len) {
data.value[indexP].ischeck = true;
} else {
data.value[indexP].ischeck = false;
}
setIsAll();
}
};
const setIsAll = () => {
let len = data.value.length || 0;
let checkNum = data.value.reduce((acc, current) => {
return acc + (current.ischeck ? 1 : 0);
}, 0);
if (checkNum > 0 && checkNum < len) {
isAll.value = false;
} else if (checkNum > 0 && checkNum == len) {
isAll.value = true;
} else {
isAll.value = false;
}
};
const doSingleDel = (indexP, index) => {
if (indexP > -1 && index > -1) {
app
.$confirm(`删除后信息将不可查看,确认要删除吗?`, '确定删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
deleteChooseGoods({ goodsIds: [data.value[indexP].cartDetails[index].id] });
data.value[indexP].cartDetails.splice(index, 1);
})
.catch(() => {});
}
};
//单个删除
const del = (data) => {
app
.$confirm(`删除后信息将不可查看,确认要删除吗?`, '确定删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
deleteAddress(data.id);
getUserAddressList();
})
.catch(() => {});
};
//批量删除
const doBatchDel = () => {
const allUnchecked = isAllUnchecked(data.value);
console.log(allUnchecked); // true 或 false
if (allUnchecked) {
app.$message({
message: '请先选择要删除的商品',
type: 'warning',
});
} else {
app
.$confirm(`删除后信息将不可查看,确认要删除吗?`, '确定删除', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
// 删除逻辑
const filteredData = removeCheckedItems(data);
// console.log(filteredData);
data.value = filteredData;
deleteAddress(ids.value[0]);
})
.catch(() => {});
}
};
const add = () => {
formLabelAlign = reactive({
receiverName: '',
receiverPhone: '',
postArea: '',
postAddress: '',
isDefault: false,
});
dialogVisible.value = true;
};
// 判断是否全部未选中
function isAllUnchecked(data) {
return data.every((item) => {
// 检查当前项的 ischeck 是否为 false
const currentUnchecked = item.ischeck === false;
// 如果有子项 goodlist递归检查子项
const childrenUnchecked = item.cartDetails ? item.cartDetails.every((child) => child.ischeck === false) : true;
return currentUnchecked && childrenUnchecked;
});
}
//批量删除逻辑
function removeCheckedItems(data) {
return data.filter((item) => {
// 如果父项的 ischeck 为 true直接过滤掉包括所有子项
if (item.ischeck === true) {
return false;
}
// 如果父项的 ischeck 为 false但子项可能存在 ischeck 为 true 的情况
if (item.cartDetails) {
item.cartDetails = item.cartDetails.filter((child) => child.ischeck !== true);
}
return true;
});
}
</script>
<style lang="scss" scoped>
.pr {
color: #25bf82;
}
.er {
color: #999999;
}
:deep(.el-checkbox__inner) {
border-radius: 50%;
width: 16px;
height: 16px;
background-color: #25bf82;
}
:deep(.el-checkbox__inner::after) {
border: none;
width: 6px;
height: 6px;
background: #25bf82;
}
.up-text {
font-size: 20px;
color: #25bf82;
margin-right: 75px;
cursor: pointer;
}
.upButton {
cursor: pointer;
color: #25bf82;
font-size: 15px;
}
.my-shoping-car-warp {
width: 100%;
.page-content-warp {
position: relative;
overflow: hidden;
margin: 16px 0 0;
width: 100%;
height: calc(100vh - 135px);
border-radius: 16px;
background: $color-fff;
.fix-top,
.fix-bottom {
position: absolute;
left: 0;
z-index: 1;
padding: 16px;
width: 100%;
background: $color-fff;
}
.fix-top {
top: 0;
display: inline-flex;
justify-content: space-between;
.do-all,
.batch-del {
display: inline-block;
vertical-align: middle;
cursor: pointer;
}
.do-all {
display: inline-flex;
flex-direction: column;
justify-content: center;
.all-txt {
display: inline-block;
padding-left: 16px;
font-size: 16px;
vertical-align: middle;
}
}
.batch-del {
padding: 8px 16px;
font-size: 16px;
border: 1px solid $color-333;
border-radius: 16px;
.el-icon {
font-size: 18px;
}
.del-txt,
.el-icon {
display: inline-block;
vertical-align: middle;
}
.del-txt {
padding-left: 16px;
}
}
}
.fix-bottom {
bottom: 0;
display: inline-flex;
justify-content: space-between;
width: 100%;
.bottom-total,
.bottom-do {
display: inline-block;
vertical-align: middle;
}
.bottom-total {
.tips,
.total {
display: inline-block;
vertical-align: middle;
}
.tips {
font-size: 30px;
}
.total {
padding-left: 16px;
font-size: 42px;
color: $color-main;
}
.total::before {
content: '¥';
}
}
.bottom-do {
display: inline-flex;
justify-content: center;
flex-direction: column;
::v-deep() {
.el-button {
display: inline-block !important;
padding: 0 40px !important;
height: 42px !important;
font-size: 18px !important;
line-height: 42px !important;
}
}
}
}
.conetnt-warp {
overflow-y: auto;
// padding: 80px 16px 96px;
width: 100%;
height: 100%;
.content-item-warp {
width: 100%;
.content-item {
margin-bottom: 16px;
width: 100%;
cursor: pointer;
.shop-info {
display: inline-flex;
justify-content: flex-start;
width: 100%;
gap: 16px;
.shop-do,
.shop-img,
.shop-name {
display: inline-block;
vertical-align: middle;
}
.shop-img {
display: inline-flex;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 4px;
flex-direction: column;
}
.shop-do {
display: inline-flex;
justify-content: center;
width: 30px;
flex-direction: column;
}
.shop-name {
width: calc(100% - 200px);
font-size: 18px;
text-align: left;
}
}
.good-list {
width: 100%;
.good-item {
display: inline-flex;
justify-content: flex-start;
margin: 8px 0;
padding-left: 16px;
width: 100%;
gap: 16px;
}
.good-do,
.good-img,
.good-info,
.good-price-num {
display: inline-block;
vertical-align: middle;
}
.good-do {
display: inline-flex;
flex-direction: column;
justify-content: center;
.good-do-pos {
}
}
.good-img {
width: 120px;
height: 120px;
}
.good-info {
display: inline-flex;
justify-content: center;
width: 200px;
flex-direction: column;
.good-info-pos {
font-size: 18px;
color: $color-666;
.txt-ellipsis {
text-align: left;
}
}
}
.good-price-num {
display: inline-flex;
justify-content: center;
width: calc(100% - 340px);
flex-direction: column;
.good-price-num-pos {
display: inline-flex;
justify-content: space-around;
gap: 16px;
.price,
.total {
font-size: 20px;
}
.price {
font-weight: 400;
}
.total {
font-weight: 700;
color: $color-main;
}
.total::before {
content: '¥';
}
.good-del {
font-size: 16px;
color: $color-999;
}
}
}
}
}
}
}
}
}
</style>