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>
<template v-if="column.key === 'action'"> <template v-if="column.key === 'action'">
<a-space> <a-space>
<a-button type="link" @click="handleManageImages(record)">管理图片</a-button>
<a-button type="link" @click="handleEdit(record)">修改</a-button> <a-button type="link" @click="handleEdit(record)">修改</a-button>
</a-space> </a-space>
</template> </template>
@ -65,6 +64,26 @@
:label-col="{ span: 6 }" :label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }" :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-form-item label="选择用户" name="user_id" required>
<a-select <a-select
v-model:value="formState.user_id" v-model:value="formState.user_id"
@ -152,42 +171,17 @@
</a-form> </a-form>
</a-modal> </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 <a-modal
:visible="previewVisible" :visible="previewVisible"
:title="previewTitle" :title="previewTitle"
:footer="null" :footer="null"
width="800px"
@cancel="handlePreviewCancel" @cancel="handlePreviewCancel"
> >
<img :alt="previewTitle" style="width: 100%" :src="previewImage" /> <div class="preview-image-wrapper">
<img :alt="previewTitle" :src="previewImage" />
</div>
</a-modal> </a-modal>
<!-- 添加修改商家模态框 --> <!-- 添加修改商家模态框 -->
@ -214,6 +208,26 @@
:label-col="{ span: 6 }" :label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }" :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-form-item label="选择用户" name="user_id" required>
<a-select <a-select
v-model:value="editFormState.user_id" v-model:value="editFormState.user_id"
@ -305,18 +319,19 @@
</template> </template>
<script> <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 { message, Upload, Modal } from 'ant-design-vue'
import dayjs from 'dayjs' import dayjs from 'dayjs'
import PageContainer from '@/components/PageContainer.vue' import PageContainer from '@/components/PageContainer.vue'
import { loadAMap, createMap, createAutoComplete, createGeocoder } from '@/utils/amap.js' import { loadAMap, createMap, createAutoComplete, createGeocoder } from '@/utils/amap.js'
import request from '@/utils/request' import request from '@/utils/request'
import { PlusOutlined } from '@ant-design/icons-vue' import { PlusOutlined, SearchOutlined } from '@ant-design/icons-vue'
export default defineComponent({ export default defineComponent({
components: { components: {
PageContainer, PageContainer,
PlusOutlined, PlusOutlined,
SearchOutlined,
AUpload: Upload, AUpload: Upload,
AModal: Modal AModal: Modal
}, },
@ -359,6 +374,26 @@ export default defineComponent({
key: 'category_name', key: 'category_name',
width: 120, 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: '商家名称', title: '商家名称',
dataIndex: 'name', dataIndex: 'name',
@ -499,10 +534,12 @@ export default defineComponent({
address: '', address: '',
longitude: null, longitude: null,
latitude: null, latitude: null,
phone: '' phone: '',
brand_image_url: ''
}) })
const rules = { const rules = {
brand_image_url: [{ required: true, message: '请上传品牌图片' }],
user_id: [{ required: true, message: '请选择用户' }], user_id: [{ required: true, message: '请选择用户' }],
category_id: [{ required: true, message: '请选择商家分类' }], category_id: [{ required: true, message: '请选择商家分类' }],
name: [{ required: true, message: '请输入商家名称' }], name: [{ required: true, message: '请输入商家名称' }],
@ -673,7 +710,8 @@ export default defineComponent({
address: '', address: '',
longitude: null, longitude: null,
latitude: null, latitude: null,
phone: '' phone: '',
brand_image_url: ''
} }
searchAddress.value = '' searchAddress.value = ''
searchOptions.value = [] searchOptions.value = []
@ -683,143 +721,38 @@ export default defineComponent({
addMarker.value = null addMarker.value = null
} }
addModalVisible.value = false 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 previewVisible = ref(false)
const previewImage = ref('') const previewImage = ref('')
const previewTitle = ref('') const previewTitle = ref('')
// const handlePreview = (file) => {
const handleManageImages = (record) => { let url = ''
currentMerchant.value = record if (typeof file === 'string') {
imageModalVisible.value = true 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
}
// previewImage.value = url
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 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
previewVisible.value = true previewVisible.value = true
previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1) previewTitle.value = '品牌图片'
} }
// //
const handlePreviewCancel = () => { 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 previewVisible.value = false
previewImage.value = '' previewImage.value = ''
previewTitle.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 editModalVisible = ref(false)
const editLoading = ref(false) const editLoading = ref(false)
@ -832,7 +765,8 @@ export default defineComponent({
address: '', address: '',
longitude: null, longitude: null,
latitude: null, latitude: null,
phone: '' phone: '',
brand_image_url: ''
}) })
const editSearchAddress = ref('') const editSearchAddress = ref('')
const editMap = ref(null) const editMap = ref(null)
@ -843,14 +777,8 @@ export default defineComponent({
const handleEdit = async (record) => { const handleEdit = async (record) => {
currentEditId.value = record.id currentEditId.value = record.id
editFormState.value = { editFormState.value = {
user_id: record.user_id, ...record,
name: record.name, user_id: record.user_id
category_id: record.category_id,
business_hours: record.business_hours,
address: record.address,
longitude: record.longitude,
latitude: record.latitude,
phone: record.phone
} }
// //
@ -870,6 +798,14 @@ export default defineComponent({
editModalVisible.value = true editModalVisible.value = true
await nextTick() await nextTick()
initEditMap(record.longitude, record.latitude) 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: '', address: '',
longitude: null, longitude: null,
latitude: null, latitude: null,
phone: '' phone: '',
brand_image_url: ''
} }
editSearchAddress.value = '' editSearchAddress.value = ''
userOptions.value = [] userOptions.value = []
@ -996,6 +933,7 @@ export default defineComponent({
editMarker.value = null editMarker.value = null
} }
editModalVisible.value = false 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(() => { onMounted(() => {
fetchData() fetchData()
}) })
@ -1052,19 +1056,11 @@ export default defineComponent({
handleSelect, handleSelect,
handleAdd, handleAdd,
handleCancel, handleCancel,
imageModalVisible,
imagesSaving,
fileList,
previewVisible, previewVisible,
previewImage, previewImage,
previewTitle, previewTitle,
handleManageImages,
handleUpload,
handlePreview, handlePreview,
handlePreviewCancel, handlePreviewCancel,
handleRemove,
handleSaveImages,
handleCancelImages,
categories, categories,
editModalVisible, editModalVisible,
editLoading, editLoading,
@ -1077,14 +1073,17 @@ export default defineComponent({
handleEditSelect, handleEditSelect,
userLoading, userLoading,
userOptions, userOptions,
handleUserSearch handleUserSearch,
brandImageList,
editBrandImageList,
handleBrandImageUpload,
handleBrandImageRemove
} }
} }
}) })
</script> </script>
<style scoped> <style scoped>
.table-header { .table-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -1106,11 +1105,12 @@ export default defineComponent({
overflow: hidden; overflow: hidden;
} }
.image-wall { .image-container {
padding: 24px; padding: 24px;
background: #fafafa; background: #fafafa;
border-radius: 2px; border-radius: 2px;
min-height: 200px; min-height: 200px;
position: relative;
} }
:deep(.ant-upload-select-picture-card) { :deep(.ant-upload-select-picture-card) {
@ -1152,4 +1152,107 @@ export default defineComponent({
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; 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> </style>