dm-design/用户端APP/我的订单/refund-standalone.html

1058 lines
33 KiB
HTML
Raw Permalink 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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>退款申请</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Helvetica Neue', Arial, sans-serif;
background-color: #f5f5f5;
overflow-y: auto;
}
.header {
background-color: #013820;
color: white;
padding: 15px 20px;
font-size: 18px;
font-weight: bold;
text-align: center;
position: relative;
z-index: 100;
}
.feed-all {
min-height: auto;
}
.group {
background-color: white;
margin: 10px;
border-radius: 8px;
overflow: hidden;
border: 1px solid #e5e5e5;
}
.form-item {
padding: 15px;
border-bottom: 1px solid #f0f0f0;
}
.form-item:last-child {
border-bottom: none;
}
.form-label {
display: block;
color: #333;
font-size: 15px;
margin-bottom: 10px;
font-weight: 500;
}
.form-label.required::before {
content: '*';
color: #ff0000;
margin-right: 4px;
}
.textarea-wrapper {
position: relative;
}
.textarea-field {
width: 100%;
min-height: 120px;
padding: 10px;
border: 1px solid #dcdcdc;
border-radius: 4px;
font-size: 14px;
resize: vertical;
font-family: inherit;
line-height: 1.5;
}
.textarea-field:focus {
outline: none;
border-color: #013820;
}
.textarea-field::placeholder {
color: #999;
}
.word-limit {
text-align: right;
color: #999;
font-size: 12px;
margin-top: 5px;
}
.upload-section {
margin-top: 10px;
}
.upload-wrapper {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 10px;
}
.upload-item {
position: relative;
width: 80px;
height: 80px;
border: 1px dashed #dcdcdc;
border-radius: 4px;
overflow: hidden;
}
.upload-item img {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-item .remove-btn {
position: absolute;
top: -8px;
right: -8px;
width: 20px;
height: 20px;
background-color: #ff0000;
color: white;
border: none;
border-radius: 50%;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
line-height: 1;
}
.upload-btn {
width: 80px;
height: 80px;
border: 1px dashed #dcdcdc;
border-radius: 4px;
background-color: #fafafa;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: #999;
font-size: 12px;
transition: all 0.3s;
}
.upload-btn:hover {
border-color: #013820;
color: #013820;
}
.upload-btn .icon {
font-size: 24px;
margin-bottom: 5px;
}
.upload-input {
display: none;
}
.footer {
padding: 15px 25px;
background-color: white;
margin: 10px;
border-radius: 8px;
}
.submit-btn {
width: 100%;
padding: 15px;
background-color: #013820;
color: white;
border: none;
border-radius: 4px;
font-size: 16px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s;
}
.submit-btn:hover {
background-color: #025530;
}
.submit-btn:active {
transform: scale(0.98);
}
.error-message {
color: #ff0000;
font-size: 12px;
margin-top: 5px;
}
.toast {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 15px 25px;
border-radius: 4px;
z-index: 1000;
display: none;
}
.toast.show {
display: block;
animation: fadeIn 0.3s;
}
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
}
.loading {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: none;
align-items: center;
justify-content: center;
z-index: 1000;
}
.loading.show {
display: flex;
}
.loading-content {
background-color: rgba(0, 0, 0, 0.7);
color: white;
padding: 20px 30px;
border-radius: 8px;
text-align: center;
}
.spinner {
border: 3px solid rgba(255, 255, 255, 0.3);
border-top: 3px solid white;
border-radius: 50%;
width: 40px;
height: 40px;
animation: spin 1s linear infinite;
margin: 0 auto 10px;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* 订单信息样式 */
.order-info {
background-color: #f9f9f9;
padding: 15px;
border-radius: 4px;
}
.order-info-row {
display: flex;
justify-content: space-between;
padding: 8px 0;
font-size: 14px;
}
.order-info-row .label {
color: #666;
}
.order-info-row .value {
color: #333;
font-weight: 500;
}
.order-info-row .price {
color: #013820;
font-weight: bold;
}
/* 选择框样式 */
.select-header {
display: flex;
align-items: center;
padding: 5px 0;
}
.checkbox-wrapper {
display: flex;
align-items: center;
cursor: pointer;
}
.checkbox {
width: 18px;
height: 18px;
margin-right: 8px;
cursor: pointer;
}
.checkbox-label {
font-size: 15px;
color: #333;
font-weight: 500;
}
/* 商品列表样式 */
.goods-list {
border-top: 1px solid #f0f0f0;
}
.shop-group {
border-bottom: 1px solid #f0f0f0;
}
.shop-group:last-child {
border-bottom: none;
}
.shop-header {
display: flex;
align-items: center;
padding: 12px 15px;
background-color: #fafafa;
border-bottom: 1px solid #f0f0f0;
}
.shop-name {
margin-left: 8px;
font-size: 14px;
color: #333;
font-weight: 500;
}
.goods-item {
display: flex;
padding: 12px 15px;
border-bottom: 1px solid #f5f5f5;
}
.goods-item:last-child {
border-bottom: none;
}
.goods-checkbox {
display: flex;
align-items: center;
margin-right: 10px;
}
.goods-content {
flex: 1;
display: flex;
gap: 10px;
}
.goods-image {
width: 60px;
height: 60px;
border-radius: 4px;
overflow: hidden;
flex-shrink: 0;
background-color: #f0f0f0;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 12px;
text-align: center;
border: 1px solid #e5e5e5;
}
.goods-image img {
width: 100%;
height: 100%;
object-fit: cover;
display: none;
}
.goods-image .placeholder {
font-size: 24px;
color: #ccc;
}
.goods-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.goods-name {
font-size: 14px;
color: #333;
line-height: 1.4;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.goods-spec {
font-size: 12px;
color: #999;
margin-top: 4px;
}
.goods-bottom {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 8px;
}
.goods-price {
font-size: 15px;
color: #013820;
font-weight: bold;
}
.goods-quantity {
font-size: 13px;
color: #666;
}
/* 退款金额样式 */
.refund-amount {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 15px;
background-color: #f9f9f9;
border-radius: 4px;
}
.refund-amount .label {
font-size: 15px;
color: #333;
font-weight: 500;
}
.refund-amount .amount {
font-size: 18px;
color: #ff0000;
font-weight: bold;
}
</style>
</head>
<body>
<div class="header">退款申请</div>
<div class="feed-all">
<form id="refundForm">
<!-- 订单基础信息 -->
<div class="group">
<div class="form-item">
<div class="order-info">
<div class="order-info-row">
<span class="label">订单编号:</span>
<span class="value" id="orderNo">-</span>
</div>
<div class="order-info-row">
<span class="label">下单时间:</span>
<span class="value" id="orderTime">-</span>
</div>
<div class="order-info-row">
<span class="label">商品总金额:</span>
<span class="value price" id="goodsAmount">¥0.00</span>
</div>
<div class="order-info-row">
<span class="label">配送费:</span>
<span class="value price" id="deliveryFee">¥0.00</span>
</div>
<div class="order-info-row">
<span class="label">包装费:</span>
<span class="value price" id="packingFee">¥0.00</span>
</div>
<div class="order-info-row">
<span class="label">调度费:</span>
<span class="value price" id="dispatchFee">¥0.00</span>
</div>
</div>
</div>
</div>
<!-- 商品明细列表 -->
<div class="group">
<div class="form-item">
<div class="select-header">
<label class="checkbox-wrapper">
<input type="checkbox" id="selectAll" class="checkbox">
<span class="checkbox-label">全选</span>
</label>
</div>
</div>
<div id="goodsList" class="goods-list">
<!-- 商品列表将通过JS动态生成 -->
</div>
</div>
<!-- 退款金额 -->
<div class="group">
<div class="form-item">
<div class="refund-amount">
<span class="label">当前退款金额:</span>
<span class="amount" id="refundAmount">¥0.00</span>
</div>
</div>
</div>
<!-- 申请理由 -->
<div class="group">
<div class="form-item">
<label class="form-label required">申请理由</label>
<div class="textarea-wrapper">
<textarea
id="reason"
class="textarea-field"
placeholder="请输入申请理由"
maxlength="300"
></textarea>
<div class="word-limit">
<span id="wordCount">0</span>/300
</div>
</div>
<div id="reasonError" class="error-message" style="display: none;">请输入申请理由</div>
</div>
<div class="form-item">
<label class="form-label required">上传凭证</label>
<div class="upload-section">
<div class="upload-wrapper" id="uploadWrapper">
<div class="upload-btn" id="uploadBtn">
<div class="icon">+</div>
<div>上传图片</div>
</div>
</div>
<input
type="file"
id="fileInput"
class="upload-input"
accept="image/*"
multiple
>
</div>
<div id="uploadError" class="error-message" style="display: none;">请上传凭证</div>
</div>
</div>
</form>
</div>
<div class="footer">
<button class="submit-btn" id="submitBtn">提交</button>
</div>
<div id="toast" class="toast"></div>
<div id="loading" class="loading">
<div class="loading-content">
<div class="spinner"></div>
<div>申请中...</div>
</div>
</div>
<script>
// 获取URL参数
function getQueryParam(name) {
const urlParams = new URLSearchParams(window.location.search);
return urlParams.get(name);
}
// 订单编号从URL参数获取
const unitOrderNo = getQueryParam('unitOrderNo') || 'ORD202501160001';
// 文件列表
let fileList = [];
const MAX_FILES = 5;
// 模拟订单数据
const orderData = {
orderNo: unitOrderNo,
orderTime: '2025-01-16 09:23:15',
goodsAmount: 68.50,
deliveryFee: 6.00,
packingFee: 2.00,
dispatchFee: 1.50,
shops: [
{
shopId: '1',
shopName: '张记蔬菜店',
goods: [
{
goodsId: '101',
goodsName: '新鲜上海青',
goodsSpec: '500g/份',
goodsImage: '',
placeholder: '🥬',
price: 6.50,
quantity: 2
},
{
goodsId: '102',
goodsName: '有机西红柿',
goodsSpec: '1斤/份',
goodsImage: '',
placeholder: '🍅',
price: 8.00,
quantity: 1
},
{
goodsId: '103',
goodsName: '土豆',
goodsSpec: '1kg/份',
goodsImage: '',
placeholder: '🥔',
price: 5.50,
quantity: 1
}
]
},
{
shopId: '2',
shopName: '王家鲜肉铺',
goods: [
{
goodsId: '201',
goodsName: '五花肉',
goodsSpec: '1斤/份',
goodsImage: '',
placeholder: '🥓',
price: 18.00,
quantity: 2
},
{
goodsId: '202',
goodsName: '鸡腿',
goodsSpec: '500g/份',
goodsImage: '',
placeholder: '🍗',
price: 12.00,
quantity: 1
}
]
},
{
shopId: '3',
shopName: '李大妈水产',
goods: [
{
goodsId: '301',
goodsName: '草鱼',
goodsSpec: '2斤/条',
goodsImage: '',
placeholder: '🐟',
price: 16.00,
quantity: 1
}
]
}
]
};
// 已选商品列表
let selectedGoods = [];
// DOM元素
const reasonTextarea = document.getElementById('reason');
const wordCountSpan = document.getElementById('wordCount');
const uploadBtn = document.getElementById('uploadBtn');
const fileInput = document.getElementById('fileInput');
const uploadWrapper = document.getElementById('uploadWrapper');
const submitBtn = document.getElementById('submitBtn');
const reasonError = document.getElementById('reasonError');
const uploadError = document.getElementById('uploadError');
const toast = document.getElementById('toast');
const loading = document.getElementById('loading');
// 字数统计
reasonTextarea.addEventListener('input', function() {
const length = this.value.length;
wordCountSpan.textContent = length;
if (length > 0) {
reasonError.style.display = 'none';
}
});
// 点击上传按钮
uploadBtn.addEventListener('click', function() {
if (fileList.length < MAX_FILES) {
fileInput.click();
} else {
showToast('最多只能上传5张图片');
}
});
// 文件选择
fileInput.addEventListener('change', function(e) {
const files = Array.from(e.target.files);
if (fileList.length + files.length > MAX_FILES) {
showToast(`最多只能上传${MAX_FILES}张图片`);
return;
}
files.forEach(file => {
if (file.type.startsWith('image/')) {
const reader = new FileReader();
reader.onload = function(event) {
fileList.push({
file: file,
url: event.target.result
});
renderFiles();
uploadError.style.display = 'none';
};
reader.readAsDataURL(file);
}
});
// 清空input以便可以重复选择同一文件
fileInput.value = '';
});
// 渲染文件列表
function renderFiles() {
// 清空现有内容
uploadWrapper.innerHTML = '';
// 渲染已上传的图片
fileList.forEach((item, index) => {
const div = document.createElement('div');
div.className = 'upload-item';
div.innerHTML = `
<img src="${item.url}" alt="图片${index + 1}">
<button class="remove-btn" onclick="removeFile(${index})">×</button>
`;
uploadWrapper.appendChild(div);
});
// 如果未达到上限,显示上传按钮
if (fileList.length < MAX_FILES) {
const uploadBtnClone = document.createElement('div');
uploadBtnClone.className = 'upload-btn';
uploadBtnClone.id = 'uploadBtn';
uploadBtnClone.innerHTML = `
<div class="icon">+</div>
<div>上传图片</div>
`;
uploadBtnClone.addEventListener('click', function() {
fileInput.click();
});
uploadWrapper.appendChild(uploadBtnClone);
}
}
// 删除文件
window.removeFile = function(index) {
fileList.splice(index, 1);
renderFiles();
};
// 显示提示
function showToast(message, duration = 2000) {
toast.textContent = message;
toast.classList.add('show');
setTimeout(() => {
toast.classList.remove('show');
}, duration);
}
// 显示加载中
function showLoading() {
loading.classList.add('show');
}
// 隐藏加载中
function hideLoading() {
loading.classList.remove('show');
}
// 表单验证
function validateForm() {
let isValid = true;
// 验证是否选择了商品
if (selectedGoods.length === 0) {
showToast('请至少选择一个商品');
isValid = false;
return isValid;
}
// 验证申请理由
if (!reasonTextarea.value.trim()) {
reasonError.style.display = 'block';
showToast('请输入申请理由');
isValid = false;
} else {
reasonError.style.display = 'none';
}
// 验证上传凭证
if (fileList.length === 0) {
uploadError.style.display = 'block';
showToast('请上传凭证');
isValid = false;
} else {
uploadError.style.display = 'none';
}
return isValid;
}
// 模拟文件上传到服务器
async function uploadFilesToServer(files) {
// 这里应该是实际的上传逻辑返回文件URL列表
// 现在使用模拟数据
const urls = files.map((item, index) => {
return `https://example.com/uploads/refund_${Date.now()}_${index}.jpg`;
});
return urls;
}
// 提交表单
submitBtn.addEventListener('click', async function() {
if (!validateForm()) {
return;
}
showLoading();
try {
// 模拟上传文件并获取URL
const imageUrls = await uploadFilesToServer(fileList);
// 准备提交数据
const submitData = {
unitOrderNo: unitOrderNo,
reason: reasonTextarea.value.trim(),
img: imageUrls.join(','),
selectedGoods: selectedGoods,
refundAmount: document.getElementById('refundAmount').textContent.replace('¥', '')
};
console.log('提交数据:', submitData);
// 模拟API请求
// 实际项目中应该调用真实的API接口
await new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟成功
resolve();
// 模拟失败,取消下面的注释
// reject(new Error('提交失败'));
}, 1500);
});
hideLoading();
showToast('申请成功,等待审核中');
// 延迟后返回上一页
setTimeout(() => {
// 在实际项目中,这里应该返回到订单列表页面
// window.history.back();
console.log('返回上一页并刷新订单列表');
}, 1500);
} catch (error) {
hideLoading();
showToast('申请失败,请联系商家');
console.error('提交失败:', error);
}
});
// 初始化订单信息
function initOrderInfo() {
document.getElementById('orderNo').textContent = orderData.orderNo;
document.getElementById('orderTime').textContent = orderData.orderTime;
document.getElementById('goodsAmount').textContent = `¥${orderData.goodsAmount.toFixed(2)}`;
document.getElementById('deliveryFee').textContent = `¥${orderData.deliveryFee.toFixed(2)}`;
document.getElementById('packingFee').textContent = `¥${orderData.packingFee.toFixed(2)}`;
document.getElementById('dispatchFee').textContent = `¥${orderData.dispatchFee.toFixed(2)}`;
}
// 渲染商品列表
function renderGoodsList() {
const goodsList = document.getElementById('goodsList');
goodsList.innerHTML = '';
orderData.shops.forEach(shop => {
const shopGroup = document.createElement('div');
shopGroup.className = 'shop-group';
shopGroup.dataset.shopId = shop.shopId;
// 店铺头部
const shopHeader = document.createElement('div');
shopHeader.className = 'shop-header';
shopHeader.innerHTML = `
<label class="checkbox-wrapper">
<input type="checkbox" class="checkbox shop-checkbox" data-shop-id="${shop.shopId}">
<span class="shop-name">${shop.shopName}</span>
</label>
`;
shopGroup.appendChild(shopHeader);
// 商品列表
shop.goods.forEach(goods => {
const goodsItem = document.createElement('div');
goodsItem.className = 'goods-item';
goodsItem.dataset.goodsId = goods.goodsId;
goodsItem.dataset.shopId = shop.shopId;
goodsItem.innerHTML = `
<div class="goods-checkbox">
<input type="checkbox" class="checkbox goods-checkbox-input"
data-goods-id="${goods.goodsId}"
data-shop-id="${shop.shopId}"
data-price="${goods.price}"
data-quantity="${goods.quantity}">
</div>
<div class="goods-content">
<div class="goods-image">
<div class="placeholder">${goods.placeholder || '📦'}</div>
</div>
<div class="goods-info">
<div class="goods-name">${goods.goodsName}</div>
<div class="goods-spec">${goods.goodsSpec}</div>
<div class="goods-bottom">
<span class="goods-price">¥${goods.price.toFixed(2)}</span>
<span class="goods-quantity">x${goods.quantity}</span>
</div>
</div>
</div>
`;
shopGroup.appendChild(goodsItem);
});
goodsList.appendChild(shopGroup);
});
// 绑定事件
bindCheckboxEvents();
}
// 绑定复选框事件
function bindCheckboxEvents() {
// 全选按钮
const selectAll = document.getElementById('selectAll');
selectAll.addEventListener('change', function() {
const checkboxes = document.querySelectorAll('.goods-checkbox-input');
checkboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
const shopCheckboxes = document.querySelectorAll('.shop-checkbox');
shopCheckboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
updateSelectedGoods();
});
// 店铺复选框
const shopCheckboxes = document.querySelectorAll('.shop-checkbox');
shopCheckboxes.forEach(shopCheckbox => {
shopCheckbox.addEventListener('change', function() {
const shopId = this.dataset.shopId;
const goodsCheckboxes = document.querySelectorAll(`.goods-checkbox-input[data-shop-id="${shopId}"]`);
goodsCheckboxes.forEach(checkbox => {
checkbox.checked = this.checked;
});
updateSelectAllStatus();
updateSelectedGoods();
});
});
// 商品复选框
const goodsCheckboxes = document.querySelectorAll('.goods-checkbox-input');
goodsCheckboxes.forEach(goodsCheckbox => {
goodsCheckbox.addEventListener('change', function() {
const shopId = this.dataset.shopId;
updateShopCheckboxStatus(shopId);
updateSelectAllStatus();
updateSelectedGoods();
});
});
}
// 更新店铺复选框状态
function updateShopCheckboxStatus(shopId) {
const goodsCheckboxes = document.querySelectorAll(`.goods-checkbox-input[data-shop-id="${shopId}"]`);
const shopCheckbox = document.querySelector(`.shop-checkbox[data-shop-id="${shopId}"]`);
const allChecked = Array.from(goodsCheckboxes).every(checkbox => checkbox.checked);
shopCheckbox.checked = allChecked;
}
// 更新全选按钮状态
function updateSelectAllStatus() {
const selectAll = document.getElementById('selectAll');
const allGoodsCheckboxes = document.querySelectorAll('.goods-checkbox-input');
const allChecked = Array.from(allGoodsCheckboxes).every(checkbox => checkbox.checked);
selectAll.checked = allChecked;
}
// 更新已选商品和退款金额
function updateSelectedGoods() {
selectedGoods = [];
let totalAmount = 0;
const checkedBoxes = document.querySelectorAll('.goods-checkbox-input:checked');
checkedBoxes.forEach(checkbox => {
const goodsId = checkbox.dataset.goodsId;
const price = parseFloat(checkbox.dataset.price);
const quantity = parseInt(checkbox.dataset.quantity);
selectedGoods.push({
goodsId: goodsId,
price: price,
quantity: quantity
});
totalAmount += price * quantity;
});
// 如果全部商品都选中,加上配送费、包装费、调度费
const allGoodsCheckboxes = document.querySelectorAll('.goods-checkbox-input');
const allChecked = Array.from(allGoodsCheckboxes).every(checkbox => checkbox.checked);
if (allChecked && selectedGoods.length > 0) {
totalAmount += orderData.deliveryFee + orderData.packingFee + orderData.dispatchFee;
}
// 更新显示
document.getElementById('refundAmount').textContent = `¥${totalAmount.toFixed(2)}`;
}
// 页面加载完成
document.addEventListener('DOMContentLoaded', function() {
console.log('页面加载完成,订单编号:', unitOrderNo);
initOrderInfo();
renderGoodsList();
});
</script>
</body>
</html>