This commit is contained in:
aaron 2025-02-01 17:49:09 +08:00
parent ebad62338b
commit 7800662267

View File

@ -23,7 +23,6 @@
</template>
<template v-if="column.key === 'action'">
<a-space>
<a-button type="link" @click="handleManageImages(record)">管理图片</a-button>
<a-button type="link" @click="handleEdit(record)">修改</a-button>
</a-space>
</template>
@ -65,6 +64,26 @@
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<a-form-item label="品牌图片" name="brand_image_url" required>
<div class="upload-wrapper">
<a-upload
v-model:fileList="brandImageList"
:customRequest="handleBrandImageUpload"
list-type="picture-card"
:maxCount="1"
@preview="handlePreview"
@remove="handleBrandImageRemove"
accept="image/*"
>
<div v-if="!formState.brand_image_url">
<plus-outlined />
<div style="margin-top: 8px">上传图片</div>
</div>
</a-upload>
</div>
<div class="upload-tip">建议尺寸200x200px支持jpgpng格式</div>
</a-form-item>
<a-form-item label="选择用户" name="user_id" required>
<a-select
v-model:value="formState.user_id"
@ -152,42 +171,17 @@
</a-form>
</a-modal>
<!-- 添加图片管理模态框 -->
<a-modal
v-model:visible="imageModalVisible"
title="商家图片管理"
width="800px"
@ok="handleSaveImages"
@cancel="handleCancelImages"
:confirmLoading="imagesSaving"
okText="保存"
>
<div class="image-wall">
<a-upload
v-model:fileList="fileList"
:customRequest="handleUpload"
list-type="picture-card"
:multiple="true"
@preview="handlePreview"
@remove="handleRemove"
accept="image/*"
>
<div v-if="fileList.length < 8">
<plus-outlined />
<div style="margin-top: 8px">上传图片</div>
</div>
</a-upload>
</div>
</a-modal>
<!-- 图片预览模态框 -->
<a-modal
:visible="previewVisible"
:title="previewTitle"
:footer="null"
width="800px"
@cancel="handlePreviewCancel"
>
<img :alt="previewTitle" style="width: 100%" :src="previewImage" />
<div class="preview-image-wrapper">
<img :alt="previewTitle" :src="previewImage" />
</div>
</a-modal>
<!-- 添加修改商家模态框 -->
@ -214,6 +208,26 @@
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<a-form-item label="品牌图片" name="brand_image_url" required>
<div class="upload-wrapper">
<a-upload
v-model:fileList="editBrandImageList"
:customRequest="handleBrandImageUpload"
list-type="picture-card"
:maxCount="1"
@preview="handlePreview"
@remove="handleBrandImageRemove"
accept="image/*"
>
<div v-if="!editFormState.brand_image_url">
<plus-outlined />
<div style="margin-top: 8px">上传图片</div>
</div>
</a-upload>
</div>
<div class="upload-tip">建议尺寸200x200px支持jpgpng格式</div>
</a-form-item>
<a-form-item label="选择用户" name="user_id" required>
<a-select
v-model:value="editFormState.user_id"
@ -305,18 +319,19 @@
</template>
<script>
import { defineComponent, ref, onMounted, nextTick } from 'vue'
import { defineComponent, ref, onMounted, nextTick, h } from 'vue'
import { message, Upload, Modal } from 'ant-design-vue'
import dayjs from 'dayjs'
import PageContainer from '@/components/PageContainer.vue'
import { loadAMap, createMap, createAutoComplete, createGeocoder } from '@/utils/amap.js'
import request from '@/utils/request'
import { PlusOutlined } from '@ant-design/icons-vue'
import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'
export default defineComponent({
components: {
PageContainer,
PlusOutlined,
SearchOutlined,
AUpload: Upload,
AModal: Modal
},
@ -359,6 +374,26 @@ export default defineComponent({
key: 'category_name',
width: 120,
},
{
title: '品牌图片',
dataIndex: 'brand_image_url',
key: 'brand_image_url',
width: 100,
align: 'center',
customRender: ({ text }) => {
if (!text) return '-'
return h('img', {
src: text,
alt: '品牌图片',
style: {
width: '60px',
height: '60px',
objectFit: 'cover',
borderRadius: '4px'
}
})
}
},
{
title: '商家名称',
dataIndex: 'name',
@ -499,10 +534,12 @@ export default defineComponent({
address: '',
longitude: null,
latitude: null,
phone: ''
phone: '',
brand_image_url: ''
})
const rules = {
brand_image_url: [{ required: true, message: '请上传品牌图片' }],
user_id: [{ required: true, message: '请选择用户' }],
category_id: [{ required: true, message: '请选择商家分类' }],
name: [{ required: true, message: '请输入商家名称' }],
@ -673,7 +710,8 @@ export default defineComponent({
address: '',
longitude: null,
latitude: null,
phone: ''
phone: '',
brand_image_url: ''
}
searchAddress.value = ''
searchOptions.value = []
@ -683,143 +721,38 @@ export default defineComponent({
addMarker.value = null
}
addModalVisible.value = false
brandImageList.value = []
}
//
const imageModalVisible = ref(false)
const imagesSaving = ref(false)
const currentMerchant = ref(null)
const fileList = ref([])
//
const previewVisible = ref(false)
const previewImage = ref('')
const previewTitle = ref('')
//
const handleManageImages = (record) => {
currentMerchant.value = record
imageModalVisible.value = true
//
if (record.images && record.images.length > 0) {
fileList.value = record.images.map((item, index) => ({
uid: `-${index}`,
name: item.image_url.substring(item.image_url.lastIndexOf('/') + 1),
status: 'done',
url: item.image_url,
image_url: item.image_url,
sort: item.sort
}))
} else {
fileList.value = []
const handlePreview = (file) => {
let url = ''
if (typeof file === 'string') {
url = file
} else if (file.url) {
url = file.url
} else if (file.preview) {
url = file.preview
} else if (file.response && file.response.url) {
url = file.response.url
}
}
//
const handleUpload = async ({ file, onSuccess, onError, onProgress }) => {
const formData = new FormData()
formData.append('files', file)
try {
onProgress({ percent: 50 })
const res = await request.post('/api/upload/images', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
if (res.code === 200 && res.data && res.data.urls && res.data.urls.length > 0) {
const url = res.data.urls[0]
const fileInfo = {
uid: `new-${Date.now()}`,
name: url.substring(url.lastIndexOf('/') + 1),
status: 'done',
response: res.data, //
url: url, //
sort: fileList.value.length
}
//
fileList.value = [...fileList.value, fileInfo]
onSuccess(res.data) //
message.success('上传成功')
} else {
throw new Error('上传失败')
}
} catch (error) {
console.error('上传图片失败:', error)
onError()
message.error('上传失败')
}
}
//
const handlePreview = async (file) => {
previewImage.value = file.url || file.preview
previewImage.value = url
previewVisible.value = true
previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1)
previewTitle.value = '品牌图片'
}
//
const handlePreviewCancel = () => {
previewVisible.value = false
previewTitle.value = ''
}
//
const handleRemove = async (file) => {
//
fileList.value = fileList.value.filter(f => f.uid !== file.uid)
return true
}
//
const resetImageManager = () => {
fileList.value = []
currentMerchant.value = null
imageModalVisible.value = false
previewVisible.value = false
previewImage.value = ''
previewTitle.value = ''
}
//
const handleSaveImages = async () => {
try {
imagesSaving.value = true
const data = {
images: fileList.value.map((file, index) => {
const imageUrl = file.response?.urls?.[0] || file.url
return {
image_url: imageUrl,
sort: index
}
})
}
console.log('Saving data:', data)
const res = await request.put(`/api/merchant/${currentMerchant.value.id}`, data)
if (res.code === 200) {
message.success('保存成功')
resetImageManager() // 使
fetchData()
} else {
throw new Error(res.message || '保存失败')
}
} catch (error) {
console.error('保存失败:', error)
message.error(error.message || '保存失败')
} finally {
imagesSaving.value = false
}
}
//
const handleCancelImages = () => {
resetImageManager() // 使
}
//
const editModalVisible = ref(false)
const editLoading = ref(false)
@ -832,7 +765,8 @@ export default defineComponent({
address: '',
longitude: null,
latitude: null,
phone: ''
phone: '',
brand_image_url: ''
})
const editSearchAddress = ref('')
const editMap = ref(null)
@ -843,14 +777,8 @@ export default defineComponent({
const handleEdit = async (record) => {
currentEditId.value = record.id
editFormState.value = {
user_id: record.user_id,
name: record.name,
category_id: record.category_id,
business_hours: record.business_hours,
address: record.address,
longitude: record.longitude,
latitude: record.latitude,
phone: record.phone
...record,
user_id: record.user_id
}
//
@ -870,6 +798,14 @@ export default defineComponent({
editModalVisible.value = true
await nextTick()
initEditMap(record.longitude, record.latitude)
if (record.brand_image_url) {
editBrandImageList.value = [{
uid: '-1',
name: 'brand-image.png',
status: 'done',
url: record.brand_image_url
}]
}
}
//
@ -987,7 +923,8 @@ export default defineComponent({
address: '',
longitude: null,
latitude: null,
phone: ''
phone: '',
brand_image_url: ''
}
editSearchAddress.value = ''
userOptions.value = []
@ -996,6 +933,7 @@ export default defineComponent({
editMarker.value = null
}
editModalVisible.value = false
editBrandImageList.value = []
}
//
@ -1025,6 +963,72 @@ export default defineComponent({
}
}
//
const brandImageList = ref([])
const editBrandImageList = ref([])
//
const handleBrandImageUpload = async ({ file, onSuccess, onError, onProgress }) => {
const formData = new FormData()
formData.append('files', file)
try {
const res = await request.post('/api/upload/images', formData, {
headers: {
'Content-Type': 'multipart/form-data'
},
onUploadProgress: (e) => {
onProgress({ percent: Math.round((e.loaded * 100) / e.total) })
}
})
if (res.code === 200) {
const url = res.data.urls[0]
//
if (editModalVisible.value) {
editFormState.value.brand_image_url = url
//
editBrandImageList.value = [{
uid: file.uid,
name: file.name,
status: 'done',
url: url,
thumbUrl: url
}]
} else {
formState.value.brand_image_url = url
//
brandImageList.value = [{
uid: file.uid,
name: file.name,
status: 'done',
url: url,
thumbUrl: url
}]
}
onSuccess()
} else {
onError()
message.error('上传失败')
}
} catch (error) {
console.error('上传失败:', error)
onError()
message.error('上传失败')
}
}
//
const handleBrandImageRemove = () => {
if (editModalVisible.value) {
editFormState.value.brand_image_url = ''
editBrandImageList.value = []
} else {
formState.value.brand_image_url = ''
brandImageList.value = []
}
}
onMounted(() => {
fetchData()
})
@ -1052,19 +1056,11 @@ export default defineComponent({
handleSelect,
handleAdd,
handleCancel,
imageModalVisible,
imagesSaving,
fileList,
previewVisible,
previewImage,
previewTitle,
handleManageImages,
handleUpload,
handlePreview,
handlePreviewCancel,
handleRemove,
handleSaveImages,
handleCancelImages,
categories,
editModalVisible,
editLoading,
@ -1077,14 +1073,17 @@ export default defineComponent({
handleEditSelect,
userLoading,
userOptions,
handleUserSearch
handleUserSearch,
brandImageList,
editBrandImageList,
handleBrandImageUpload,
handleBrandImageRemove
}
}
})
</script>
<style scoped>
.table-header {
display: flex;
justify-content: space-between;
@ -1106,11 +1105,12 @@ export default defineComponent({
overflow: hidden;
}
.image-wall {
.image-container {
padding: 24px;
background: #fafafa;
border-radius: 2px;
min-height: 200px;
position: relative;
}
:deep(.ant-upload-select-picture-card) {
@ -1152,4 +1152,107 @@ export default defineComponent({
overflow: hidden;
text-overflow: ellipsis;
}
.image-action-list {
display: flex;
flex-wrap: wrap;
padding: 0 24px;
}
.image-action-item {
width: 104px;
margin: 0 8px 16px 0;
text-align: center;
}
.image-action-item .ant-btn {
color: #1890ff;
padding: 0;
height: 24px;
line-height: 24px;
}
.image-action-item .ant-btn[disabled] {
color: rgba(0, 0, 0, 0.25);
}
:deep(.ant-upload-list) {
position: relative;
z-index: 1;
}
.upload-wrapper {
margin-bottom: 16px;
}
.upload-tip {
font-size: 12px;
color: #999;
}
.upload-wrapper {
display: flex;
align-items: center;
}
.upload-tip {
color: #999;
font-size: 12px;
margin-top: 4px;
}
:deep(.ant-upload-select-picture-card) {
width: 100px !important;
height: 100px !important;
margin: 0;
}
:deep(.ant-upload-list-picture-card-container) {
width: 100px;
height: 100px;
margin: 0 8px 0 0;
}
.preview-image-wrapper {
display: flex;
justify-content: center;
align-items: center;
min-height: 200px;
background: #f0f0f0;
border-radius: 8px;
overflow: hidden;
}
.preview-image-wrapper img {
max-width: 100%;
max-height: 80vh;
object-fit: contain;
}
:deep(.ant-modal-body) {
padding: 0;
overflow: hidden;
}
:deep(.ant-modal-close) {
color: #fff;
top: -30px;
right: -30px;
}
:deep(.ant-modal-header) {
display: none;
}
:deep(.ant-modal-wrap) {
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
}
:deep(.ant-modal-content) {
background: transparent;
box-shadow: none;
}
</style>