Files
OMS/app/wap/view/neworder/common.js
2026-01-04 19:08:31 +08:00

452 lines
13 KiB
JavaScript
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.
/**
* Copyright 2012-2026 ShopeX (https://www.shopex.cn)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class Modal {
constructor() {
this.modal = document.getElementById('commonModal')
this.mask = this.modal.querySelector('.modal-mask')
this.container = this.modal.querySelector('.modal-container')
this.closeBtn = this.modal.querySelector('.modal-close')
this.confirmBtn = this.modal.querySelector('.modal-confirm')
this.cancleBtn = this.modal.querySelector('.modal-cancle')
this.title = this.modal.querySelector('.modal-title')
this.tips = this.modal.querySelector('.modal-tips')
this.content = this.modal.querySelector('.modal-content')
this.bindEvents()
}
bindEvents() {
this.closeBtn.onclick = () => modal.hide()
this.mask.onclick = () => modal.hide()
}
show(options) {
const {
title = '',
tips = '',
fields = [], // 表单字段配置数组
onConfirm = () => {},
onCancel = () => {},
confirmText = '确定',
cancelText = '取消',
isShowCancleBtn = false
} = options
this.title.textContent = title
if (tips) {
this.tips.textContent = tips
}
// 生成表单内容
const formContent = fields
.map((field) => {
const {
type,
name,
label,
placeholder = '',
options = [],
required = false,
html = '',
className = ''
} = field
let inputHtml = ''
if (type === 'input') {
inputHtml = `
<input type="text"
class="modal-input"
name="${name}"
placeholder="${placeholder}"
${required ? 'required' : ''}>
`
return `
<div class="modal-form-item">
<div class="input-box flex-row align-center justify-between ${className}">
<label>${label}${required ? ' <span style="color: red">*</span>' : ''}</label>
${inputHtml}
</div>
</div>
`
} else if (type === 'textarea') {
inputHtml = `
<textarea class="modal-textarea"
name="${name}"
placeholder="${placeholder}"
${required ? 'required' : ''}></textarea>
`
return `
<div class="modal-form-item">
<div class="flex-col">
<label>${label}${required ? ' <span style="color: red">*</span>' : ''}</label>
${inputHtml}
</div>
</div>
`
} else if (type === 'select') {
const optionsHtml = options
.map((opt) => `<option value="${opt.value}">${opt.label}</option>`)
.join('')
inputHtml = `
<select class="modal-select"
name="${name}"
${required ? 'required' : ''}>
<option value="">请选择${label}</option>
${optionsHtml}
</select>
`
return `
<div class="modal-form-item">
<div class="input-box flex-row align-center justify-between">
<label>${label}${required ? ' <span style="color: red">*</span>' : ''}</label>
${inputHtml}
</div>
</div>
`
} else if (type === 'date') {
inputHtml = `
<input type="date"
class="date-input"
max="${new Date().toISOString().split('T')[0]}"
name="${name}"
placeholder="${placeholder}"
${required ? 'required' : ''}>
`
return `
<div class="modal-form-item">
<div class="input-box flex-row align-center justify-between">
<label>${label}${required ? ' <span style="color: red">*</span>' : ''}</label>
${inputHtml}
</div>
</div>
`
} else if (type === 'html') {
inputHtml = html
return `
<div class="modal-form-item">
${inputHtml}
</div>
`
} else if (type === 'image') {
return `
<div class="modal-form-item">
${field.label ? `<label class="modal-form-label">${field.label}${field.required ? ' <span class="required">*</span>' : ''}</label>` : ''}
<div class="modal-form-file-wrapper">
<input type="file"
class="modal-form-file"
name="${field.name}"
accept="image/*"
${field.required ? 'required' : ''}
multiple
>
<div class="file-preview-container"></div>
</div>
</div>
`;
}
})
.join('')
this.content.innerHTML = formContent
this.confirmBtn.innerHTML = confirmText
this.cancleBtn.innerHTML = cancelText
this.cancleBtn.style.display = isShowCancleBtn ? 'block' : 'none'
// 绑定确认按钮事件
const submitForm = async (cb) => {
const formData = {}
let isValid = true
for (const field of fields) {
if (field.type === 'image') {
const fileInput = this.modal.querySelector('input[type="file"]');
// 直接使用保存的 base64 列表
if (field.required && fileInput.uploadedBase64List.length === 0) {
showToast(`请上传${field.label}`)
isValid = false
return
}
if (field.required && fileInput.uploadedBase64List.length > 9) {
showToast(`图片最多上传9张`)
isValid = false
return
}
formData[field.name] = fileInput.uploadedBase64List || [];
} else if (field.type !== 'html') {
const element = this.content.querySelector(`[name="${field.name}"]`)
const value = element.value.trim()
if (field.required && !value) {
showToast(`${field.type === 'select' ? '选择' : '填写'}${field.label}`)
isValid = false
return
}
formData[field.name] = value
}
}
if (isValid && cb) {
cb(formData)
}
}
this.confirmBtn.onclick = () => {
submitForm(onConfirm)
}
this.cancleBtn.onclick = () => {
// 取消按钮事件
submitForm(onCancel)
}
this.modal.style.display = 'block'
// 初始化图片上传
this.initFileUpload();
}
hide() {
this.modal.style.display = 'none'
}
// 图片上传
initFileUpload() {
const fileInputs = this.modal.querySelectorAll('input[type="file"]');
if(fileInputs.length === 0) {
return;
}
fileInputs.forEach(input => {
const previewContainer = input.parentElement.querySelector('.file-preview-container');
const uploadBtn = document.createElement('label');
uploadBtn.className = 'upload-btn';
uploadBtn.setAttribute('for', `file-input-${Date.now()}`); // 生成唯一ID
uploadBtn.innerHTML = `<i class="upload-icon">+</i>`;
input.id = uploadBtn.getAttribute('for'); // 设置对应的ID
previewContainer.appendChild(uploadBtn);
input.style.display = 'none';
// 存储所有已上传文件的 base64 数据
input.uploadedBase64List = [];
// 存储已上传的文件对象
let uploadedFiles = [];
uploadBtn.addEventListener('click', (e) => {
e.preventDefault(); // 阻止事件冒泡
e.stopPropagation(); // 阻止事件传播
input.click();
});
input.addEventListener('change', (e) => {
const newFiles = Array.from(e.target.files);
newFiles.forEach(file => {
if (!file.type.startsWith('image/')) {
showToast('请上传图片文件');
return;
}
if (uploadedFiles.length >= 9) {
showToast('最多上传9张图片');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const base64Data = e.target.result;
// 保存 base64 数据
input.uploadedBase64List.push(base64Data);
const previewWrapper = document.createElement('div');
previewWrapper.className = 'file-preview-wrapper';
const preview = document.createElement('img');
preview.src = base64Data;
preview.className = 'file-preview';
const deleteBtn = document.createElement('span');
deleteBtn.className = 'file-delete-btn';
deleteBtn.innerHTML = '×';
deleteBtn.onclick = () => {
previewWrapper.remove();
// 从已上传文件中移除
const index = uploadedFiles.indexOf(file);
if (index > -1) {
uploadedFiles.splice(index, 1);
input.uploadedBase64List.splice(index, 1);
}
// 显示上传按钮
if(uploadedFiles.length < 9) {
uploadBtn.style.display = 'flex';
}
};
previewWrapper.appendChild(preview);
previewWrapper.appendChild(deleteBtn);
previewContainer.insertBefore(previewWrapper, uploadBtn);
// 添加到已上传文件
uploadedFiles.push(file);
// 隐藏上传按钮
if(uploadedFiles.length >= 9) {
uploadBtn.style.display = 'none';
}
};
reader.readAsDataURL(file);
});
});
});
}
}
// 请求配置和方法封装
const request = axios.create({
baseURL: '/api', // 设置基础URL
timeout: 10000, // 设置超时时间
headers: {
'Content-Type': 'multipart/form-data'
},
// 兼容对象数组
// transformRequest: [
// function (data) {
// // 将请求数据转换为FormData格式
// const formData = new FormData()
// const appendData = (key, value) => {
// if (value === null || value === undefined) {
// return
// }
// if (Array.isArray(value)) {
// // 处理数组
// value.forEach((item, index) => {
// if (typeof item === 'object' && item !== null) {
// // 处理对象数组,使用 key[index][property] 的格式
// Object.keys(item).forEach(prop => {
// formData.append(`${key}[${index}][${prop}]`, item[prop])
// })
// } else {
// // 处理普通数组
// formData.append(`${key}[${index}]`, item)
// }
// })
// } else if (typeof value === 'object' && value !== null) {
// // 处理对象
// Object.keys(value).forEach(prop => {
// appendData(`[${prop}][${key}]`, value[prop])
// })
// } else {
// // 处理基本类型
// formData.append(key, value)
// }
// }
// Object.keys(data).forEach(key => {
// appendData(key, data[key])
// })
// return formData
// }
// ]
})
// 请求拦截器
request.interceptors.request.use(
(config) => {
// 可以在这里添加loading状态
// 添加token等通用headers
return config
},
(error) => {
return Promise.reject(error)
}
)
// 响应拦截器
request.interceptors.response.use(
(response) => {
// 可以统一处理响应数据
if (response.data.rsp != 'fail') {
return response.data
}
// 处理业务错误
Toast.error(response.data.msg || '请求失败')
return Promise.reject(response.data)
},
(error) => {
// 处理网络错误
Toast.error('网络请求失败,请稍后重试')
return Promise.reject(error)
}
)
// Toast提示组件
const Toast = {
error(msg) {
// 这里可以替换成你项目中使用的提示组件
console.error(msg)
}
}
// 提示Toast封装
const showToast = (msg) => {
const toast = document.getElementById('toast')
toast.classList.add('show')
toast.textContent = msg
// 动画结束后移除类名和隐藏元素
setTimeout(() => {
toast.classList.remove('show')
}, 1500)
}
const showLoading = (msg) => {
const toast = document.getElementById('toast')
toast.classList.add('show')
toast.textContent = msg
}
const hideLoading = (msg) => {
const toast = document.getElementById('toast')
toast.classList.remove('show')
}
document.addEventListener('DOMContentLoaded', () => {
document.querySelector('.icon-back').onclick = () => {
history.back()
}
document.querySelector('.icon-refresh').onclick = () => {
location.reload()
}
})
// export { Modal, request, Toast, showToast }
// 如果你想让这些在全局可用,可以添加到 window 对象
window.Modal = Modal
window.request = request
window.Toast = Toast
window.showToast = showToast
window.showLoading = showLoading
window.hideLoading = hideLoading