新增商家页面基础完成

This commit is contained in:
aaron 2025-01-09 17:44:22 +08:00
parent d5f40e0dfd
commit 9b6e38edce
7 changed files with 747 additions and 257 deletions

View File

@ -1,15 +1,21 @@
<!DOCTYPE html>
<html lang="zh-CN">
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>蜂快到家后台管理系统</title>
<title><%= htmlWebpackPlugin.options.title %></title>
<script>
window._AMapSecurityConfig = {
securityJsCode: '6c7f5a402a13bf2dee1a4bbe0d8023c7'
}
</script>
<script src="https://webapi.amap.com/maps?v=2.0&key=fd47f3d4f54b675693c7d59dcd2a6c5f&plugin=AMap.Geocoder,AMap.AutoComplete,AMap.PlaceSearch"></script>
</head>
<body>
<noscript>
<strong>We're sorry but 蜂快到家后台管理系统 doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->

View File

@ -0,0 +1,39 @@
<template>
<a-select
v-bind="$attrs"
:style="selectStyle"
>
<slot></slot>
</a-select>
</template>
<script>
export default {
name: 'CommunitySelect',
setup() {
const selectStyle = {
height: '32px',
width: '100%'
}
return {
selectStyle
}
}
}
</script>
<style scoped>
:deep(.ant-select-selector) {
height: 32px !important;
line-height: 32px !important;
}
:deep(.ant-select-selection-search-input) {
height: 30px !important;
}
:deep(.ant-select-selection-item) {
line-height: 30px !important;
}
</style>

View File

@ -0,0 +1,232 @@
<template>
<div class="map-picker">
<a-form-item :label="label">
<a-auto-complete
v-model:value="searchAddress"
:options="searchOptions"
placeholder="输入地址搜索"
@change="handleSearch"
@select="handleSelect"
:loading="searchLoading"
allow-clear
/>
</a-form-item>
<a-form-item label="地图选点" required>
<div class="map-container">
<div :id="mapContainerId" style="height: 300px;"></div>
</div>
</a-form-item>
<a-form-item label="详细地址" required>
<a-input v-model:value="address" placeholder="请输入详细地址" />
</a-form-item>
<a-form-item label="经纬度" required>
<a-input-group compact>
<a-input-number
v-model:value="longitude"
:min="-180"
:max="180"
style="width: 50%"
placeholder="经度"
disabled
/>
<a-input-number
v-model:value="latitude"
:min="-90"
:max="90"
style="width: 50%"
placeholder="纬度"
disabled
/>
</a-input-group>
</a-form-item>
</div>
</template>
<script>
import { ref, onMounted, nextTick } from 'vue'
import { message } from 'ant-design-vue'
import { loadAMap, createMap, createAutoComplete, createGeocoder } from '@/utils/amap.js'
export default {
name: 'MapPicker',
props: {
label: {
type: String,
default: '地址搜索'
},
modelValue: {
type: Object,
default: () => ({
address: '',
longitude: null,
latitude: null
})
}
},
emits: ['update:modelValue'],
setup(props, { emit }) {
const mapContainerId = `map-container-${Date.now()}`
const map = ref(null)
const marker = ref(null)
const searchAddress = ref('')
const searchOptions = ref([])
const searchLoading = ref(false)
//
const address = ref(props.modelValue.address)
const longitude = ref(props.modelValue.longitude)
const latitude = ref(props.modelValue.latitude)
//
const updateValue = () => {
emit('update:modelValue', {
address: address.value,
longitude: longitude.value,
latitude: latitude.value
})
}
//
const updateMarkerPosition = (lng, lat) => {
longitude.value = lng
latitude.value = lat
if (marker.value) {
marker.value.setPosition([lng, lat])
} else {
marker.value = new window.AMap.Marker({
position: [lng, lat],
map: map.value
})
}
updateValue()
}
//
const initMap = async () => {
try {
await loadAMap()
if (!map.value) {
map.value = createMap(mapContainerId, {
zoom: 13,
viewMode: '2D'
})
//
map.value.on('click', async (e) => {
const { lng, lat } = e.lnglat
updateMarkerPosition(lng, lat)
//
const geocoder = createGeocoder()
geocoder.getAddress([lng, lat], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
address.value = result.regeocode.formattedAddress
updateValue()
} else {
message.error('获取地址信息失败')
}
})
})
//
if (props.modelValue.longitude && props.modelValue.latitude) {
updateMarkerPosition(props.modelValue.longitude, props.modelValue.latitude)
map.value.setCenter([props.modelValue.longitude, props.modelValue.latitude])
}
}
} catch (error) {
console.error('初始化地图失败:', error)
message.error('初始化地图失败')
}
}
//
const handleSearch = async (value) => {
if (!value || value.length < 2) {
searchOptions.value = []
return
}
try {
searchLoading.value = true
await loadAMap()
const autoComplete = createAutoComplete()
await new Promise((resolve, reject) => {
autoComplete.search(value, (status, result) => {
if (status === 'complete') {
searchOptions.value = result.tips.map(tip => ({
value: tip.name,
label: `${tip.name} (${tip.district})`,
location: tip.location
}))
resolve(result)
} else {
reject(new Error(status))
}
searchLoading.value = false
})
})
} catch (error) {
console.error('搜索地址失败:', error)
searchLoading.value = false
message.error('搜索地址失败')
}
}
//
const handleSelect = (value, option) => {
const selected = searchOptions.value.find(opt => opt.value === value)
if (selected && selected.location) {
const { lng, lat } = selected.location
updateMarkerPosition(lng, lat)
map.value.setCenter([lng, lat])
// 使
const geocoder = createGeocoder()
geocoder.getAddress([lng, lat], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
address.value = result.regeocode.formattedAddress
updateValue()
} else {
address.value = value
updateValue()
}
})
}
}
onMounted(() => {
nextTick(() => {
initMap()
})
})
return {
mapContainerId,
searchAddress,
searchOptions,
searchLoading,
address,
longitude,
latitude,
handleSearch,
handleSelect
}
}
}
</script>
<style scoped>
.map-container {
border: 1px solid #d9d9d9;
border-radius: 2px;
overflow: hidden;
margin-bottom: 16px;
}
</style>

4
src/config/map.js Normal file
View File

@ -0,0 +1,4 @@
export const MAP_CONFIG = {
key: 'fd47f3d4f54b675693c7d59dcd2a6c5f',
securityJsCode: '6c7f5a402a13bf2dee1a4bbe0d8023c7'
}

View File

@ -1,6 +1,20 @@
export function initAMap() {
// 高德地图配置
const MAP_CONFIG = {
key: 'fd47f3d4f54b675693c7d59dcd2a6c5f',
securityJsCode: '93527b49270ba2142f47f0407da7c0d6'
}
// 设置安全密钥
window._AMapSecurityConfig = {
securityJsCode: MAP_CONFIG.securityJsCode
}
let isMapLoaded = false
// 加载高德地图及其插件
export const loadAMap = () => {
return new Promise((resolve, reject) => {
if (window.AMap) {
if (isMapLoaded && window.AMap) {
resolve(window.AMap)
return
}
@ -8,26 +22,41 @@ export function initAMap() {
const script = document.createElement('script')
script.type = 'text/javascript'
script.async = true
script.src = `https://webapi.amap.com/maps?v=2.0&key=fd47f3d4f54b675693c7d59dcd2a6c5f&plugin=AMap.PlaceSearch,AMap.AutoComplete,AMap.Geocoder&callback=initAMapCallback`
script.src = `https://webapi.amap.com/maps?v=2.0&key=${MAP_CONFIG.key}&plugin=AMap.Geocoder,AMap.AutoComplete,AMap.PlaceSearch`
window.initAMapCallback = () => {
script.onload = () => {
isMapLoaded = true
resolve(window.AMap)
}
script.onerror = reject
script.onerror = () => {
reject(new Error('加载高德地图失败'))
}
document.head.appendChild(script)
})
}
// 创建地图实例
export function createMap(container, options = {}) {
export const createMap = (container, options = {}) => {
const defaultOptions = {
zoom: 15,
viewMode: '2D'
viewMode: '2D',
center: [116.397428, 39.90923] // 默认中心点
}
return new window.AMap.Map(container, { ...defaultOptions, ...options })
}
return new window.AMap.Map(container, {
...defaultOptions,
// 创建地址搜索服务
export const createAutoComplete = (options = {}) => {
return new window.AMap.AutoComplete({
city: '全国',
citylimit: true,
...options
})
}
// 创建地理编码服务
export const createGeocoder = () => {
return new window.AMap.Geocoder()
}

View File

@ -73,58 +73,14 @@
:wrapper-col="{ span: 16 }"
class="community-form"
>
<a-form-item label="地址搜索">
<a-auto-complete
v-model:value="searchAddress"
:options="searchOptions"
placeholder="输入地址搜索"
@change="handleSearch"
@select="handleSelect"
:loading="searchLoading"
allow-clear
>
<template #option="{ value: val, label }">
<div>{{ label }}</div>
</template>
</a-auto-complete>
</a-form-item>
<a-form-item label=" " :colon="false">
<div class="map-container">
<div id="add-map-container" style="height: 240px;"></div>
</div>
</a-form-item>
<a-form-item label="小区名称" name="name" required>
<a-input v-model:value="formState.name" placeholder="请输入小区名称" />
</a-form-item>
<a-form-item label="详细地址" name="address" required>
<a-input v-model:value="formState.address" placeholder="请输入详细地址" />
</a-form-item>
<a-form-item label="经纬度" required>
<a-input-group compact>
<a-input-number
v-model:value="formState.longitude"
:min="-180"
:max="180"
:controls="false"
disabled
style="width: 50%"
placeholder="经度"
<map-picker
v-model="formState.location"
label="地址搜索"
/>
<a-input-number
v-model:value="formState.latitude"
:min="-90"
:max="90"
:controls="false"
disabled
style="width: 50%"
placeholder="纬度"
/>
</a-input-group>
</a-form-item>
</a-form>
</a-modal>
</div>
@ -133,18 +89,23 @@
<script>
import { defineComponent, ref, onMounted, nextTick } from 'vue'
import { message, Tag } from 'ant-design-vue'
import { message, Tag, Menu, Dropdown } from 'ant-design-vue'
import { getCommunityList, createCommunity, updateCommunityStatus } from '@/api/community'
import { initAMap, createMap } from '@/utils/amap'
import { loadAMap, createMap } from '@/utils/amap.js'
import dayjs from 'dayjs'
import PageContainer from '@/components/PageContainer.vue'
import { DownOutlined } from '@ant-design/icons-vue'
import MapPicker from '@/components/MapPicker/index.vue'
export default defineComponent({
components: {
PageContainer,
[Tag.name]: Tag,
DownOutlined
ATag: Tag,
ADropdown: Dropdown,
AMenu: Menu,
AMenuItem: Menu.Item,
DownOutlined,
MapPicker
},
setup() {
const loading = ref(false)
@ -255,31 +216,26 @@ export default defineComponent({
const showMap = async (record) => {
mapVisible.value = true
// DOM
await nextTick()
try {
const AMap = await initAMap()
await loadAMap()
//
if (!currentMap.value) {
currentMap.value = new AMap.Map('map-container', {
currentMap.value = createMap('map-container', {
zoom: 15,
viewMode: '3D'
})
}
//
const position = [record.longitude, record.latitude]
currentMap.value.setCenter(position)
//
if (currentMarker.value) {
currentMap.value.remove(currentMarker.value)
}
//
currentMarker.value = new AMap.Marker({
currentMarker.value = new window.AMap.Marker({
position,
title: record.name
})
@ -308,191 +264,38 @@ export default defineComponent({
const formState = ref({
name: '',
location: {
address: '',
longitude: null,
latitude: null
}
})
const rules = {
name: [{ required: true, message: '请输入小区名称' }],
address: [{ required: true, message: '请输入详细地址' }],
longitude: [{ required: true, message: '请选择位置获取经度' }],
latitude: [{ required: true, message: '请选择位置获取纬度' }],
'location.address': [{ required: true, message: '请选择地址' }],
'location.longitude': [{ required: true, message: '请在地图上选择位置' }],
'location.latitude': [{ required: true, message: '请在地图上选择位置' }]
}
//
const showAddModal = async () => {
const showAddModal = () => {
addModalVisible.value = true
await nextTick()
initAddMap()
}
//
const updateMarkerPosition = (lng, lat, name = '') => {
if (!addMap.value) return
formState.value.longitude = lng
formState.value.latitude = lat
//
if (name && !formState.value.name) {
formState.value.name = name
}
if (addMarker.value) {
addMarker.value.setPosition([lng, lat])
} else {
const AMap = window.AMap
addMarker.value = new AMap.Marker({
position: [lng, lat],
map: addMap.value
})
}
}
//
const initAddMap = async () => {
try {
const AMap = await initAMap();
if (!addMap.value) {
addMap.value = createMap('add-map-container', {
center: [116.397428, 39.90923]
});
//
addMap.value.on('click', (e) => {
const { lng, lat } = e.lnglat;
// 使
const geocoder = new AMap.Geocoder({
radius: 100, //
extensions: 'all' //
});
geocoder.getAddress([lng, lat], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const address = result.regeocode;
formState.value.address = address.formattedAddress;
// 使 POI
if (address.pois && address.pois.length > 0) {
// """""" POI
const residentialPoi = address.pois.find(poi =>
poi.type.includes('住宅区') ||
poi.type.includes('小区') ||
poi.type.includes('住宅小区')
);
if (residentialPoi) {
formState.value.name = residentialPoi.name;
} else {
// POI使 POI
formState.value.name = address.pois[0].name;
}
} else if (address.aois && address.aois.length > 0) {
// POI使 AOI
formState.value.name = address.aois[0].name;
} else {
// POI AOI使
const township = address.addressComponent.township || '';
const street = address.addressComponent.street || '';
formState.value.name = `${township}${street}`;
}
//
updateMarkerPosition(lng, lat);
}
});
});
}
} catch (error) {
console.error('初始化地图失败:', error);
message.error('初始化地图失败,请刷新重试');
}
};
//
const handleSearch = async (value) => {
if (!value || value.length < 2) {
searchOptions.value = [];
return;
}
try {
searchLoading.value = true;
await initAMap(); //
const autoComplete = new window.AMap.AutoComplete({
city: '全国'
});
const result = await new Promise((resolve, reject) => {
autoComplete.search(value, (status, result) => {
if (status === 'complete') {
resolve(result);
} else {
reject(new Error('搜索失败'));
}
});
});
searchOptions.value = result.tips.map(tip => ({
value: tip.name,
label: `${tip.name} (${tip.district})`,
location: tip.location,
address: tip.address || tip.district
}));
} catch (error) {
console.error('搜索地址失败:', error);
searchOptions.value = [];
} finally {
searchLoading.value = false;
}
};
//
const handleSelect = (value, option) => {
const selectedOption = searchOptions.value.find(opt => opt.value === value);
if (selectedOption) {
//
formState.value.address = selectedOption.address;
formState.value.name = selectedOption.value; // 使
if (selectedOption.location) {
updateMarkerPosition(
selectedOption.location.lng,
selectedOption.location.lat,
selectedOption.value
);
addMap.value.setCenter([selectedOption.location.lng, selectedOption.location.lat]);
} else {
// 使 PlaceSearch
const placeSearch = new window.AMap.PlaceSearch({
city: '全国'
});
placeSearch.search(value, (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const poi = result.poiList.pois[0];
formState.value.address = poi.address;
updateMarkerPosition(
poi.location.lng,
poi.location.lat,
poi.name
);
addMap.value.setCenter([poi.location.lng, poi.location.lat]);
}
});
}
}
};
//
const handleAdd = () => {
formRef.value.validate().then(async () => {
try {
confirmLoading.value = true
const res = await createCommunity(formState.value)
const params = {
name: formState.value.name,
address: formState.value.location.address,
longitude: formState.value.location.longitude,
latitude: formState.value.location.latitude
}
const res = await createCommunity(params)
if (res.code === 200) {
message.success('添加成功')
addModalVisible.value = false
@ -513,11 +316,6 @@ export default defineComponent({
const handleCancel = () => {
formRef.value?.resetFields()
addModalVisible.value = false
searchAddress.value = ''
if (addMarker.value) {
addMarker.value.setMap(null)
addMarker.value = null
}
}
//
@ -558,17 +356,12 @@ export default defineComponent({
formatDateTime,
addModalVisible,
confirmLoading,
searchLoading,
searchAddress,
formState,
formRef,
rules,
showAddModal,
handleAdd,
handleCancel,
handleSearch,
searchOptions,
handleSelect,
handleStatusChange
}
}
@ -862,4 +655,11 @@ export default defineComponent({
:deep(.ant-select-selection-placeholder) {
line-height: 30px !important;
}
/* 调整地图容器样式 */
:deep(.map-container) {
border: 1px solid #d9d9d9;
border-radius: 2px;
overflow: hidden;
}
</style>

View File

@ -3,6 +3,7 @@
<div class="merchant-list">
<div class="table-header">
<h1>商家列表</h1>
<a-button type="primary" @click="showAddModal">添加商家</a-button>
</div>
<a-table
@ -46,6 +47,112 @@
>
<!-- 这里添加图片管理的具体实现 -->
</a-modal>
<!-- 添加商家模态框 -->
<a-modal
v-model:visible="addModalVisible"
title="添加商家"
:confirmLoading="confirmLoading"
width="800px"
@cancel="handleCancel"
>
<template #footer>
<a-space>
<a-button @click="handleCancel">取消</a-button>
<a-button type="primary" :loading="confirmLoading" @click="handleAdd">
保存
</a-button>
</a-space>
</template>
<a-form
ref="formRef"
:model="formState"
:rules="rules"
:label-col="{ span: 6 }"
:wrapper-col="{ span: 16 }"
>
<a-form-item label="商家名称" name="name" required>
<a-input v-model:value="formState.name" placeholder="请输入商家名称" />
</a-form-item>
<a-form-item label="营业时间" name="business_hours" required>
<a-input v-model:value="formState.business_hours" placeholder="例如10:30~20:30" />
</a-form-item>
<a-form-item label="联系电话" name="phone" required>
<a-input v-model:value="formState.phone" placeholder="请输入联系电话" />
</a-form-item>
<a-form-item label="地址搜索">
<a-auto-complete
v-model:value="searchAddress"
:options="searchOptions"
placeholder="输入地址搜索"
@change="handleSearch"
@select="handleSelect"
:loading="searchLoading"
allow-clear
/>
</a-form-item>
<a-form-item label="地图选点" required>
<div class="map-container">
<div id="add-map-container" style="height: 300px;"></div>
</div>
</a-form-item>
<a-form-item label="详细地址" name="address" required>
<a-input v-model:value="formState.address" placeholder="请输入详细地址" />
</a-form-item>
<a-form-item label="经纬度" required>
<a-input-group compact>
<a-input-number
v-model:value="formState.longitude"
:min="-180"
:max="180"
style="width: 50%"
placeholder="经度"
disabled
/>
<a-input-number
v-model:value="formState.latitude"
:min="-90"
:max="90"
style="width: 50%"
placeholder="纬度"
disabled
/>
</a-input-group>
</a-form-item>
<a-form-item label="商家图片" name="images">
<a-upload
v-model:fileList="fileList"
:customRequest="handleUpload"
list-type="picture-card"
:multiple="true"
@preview="handlePreview"
@remove="handleRemove"
>
<div v-if="fileList.length < 8">
<plus-outlined />
<div style="margin-top: 8px">上传</div>
</div>
</a-upload>
</a-form-item>
</a-form>
</a-modal>
<!-- 图片预览模态框 -->
<a-modal
v-model:visible="previewVisible"
:title="previewTitle"
:footer="null"
>
<img :src="previewImage" style="width: 100%" />
</a-modal>
</div>
</page-container>
</template>
@ -53,13 +160,15 @@
<script>
import { defineComponent, ref, onMounted, nextTick } from 'vue'
import { message } from 'ant-design-vue'
import { PlusOutlined } from '@ant-design/icons-vue'
import dayjs from 'dayjs'
import PageContainer from '@/components/PageContainer.vue'
import { initAMap } from '@/utils/amap'
import { loadAMap, createMap, createAutoComplete, createGeocoder } from '@/utils/amap.js'
export default defineComponent({
components: {
PageContainer
PageContainer,
PlusOutlined
},
setup() {
const loading = ref(false)
@ -214,6 +323,239 @@ export default defineComponent({
//
}
//
const addModalVisible = ref(false)
const confirmLoading = ref(false)
const formRef = ref(null)
const searchAddress = ref('')
const searchOptions = ref([])
const searchLoading = ref(false)
const addMap = ref(null)
const addMarker = ref(null)
const fileList = ref([])
const previewVisible = ref(false)
const previewImage = ref('')
const previewTitle = ref('')
const formState = ref({
name: '',
business_hours: '',
address: '',
longitude: null,
latitude: null,
phone: '',
images: []
})
const rules = {
name: [{ required: true, message: '请输入商家名称' }],
business_hours: [{ required: true, message: '请输入营业时间' }],
address: [{ required: true, message: '请输入详细地址' }],
phone: [{ required: true, message: '请输入联系电话' }],
}
//
const showAddModal = async () => {
addModalVisible.value = true
await nextTick()
initAddMap()
}
//
const initAddMap = async () => {
try {
await loadAMap()
if (!addMap.value) {
addMap.value = createMap('add-map-container', {
zoom: 13,
viewMode: '2D'
})
//
addMap.value.on('click', async (e) => {
const { lng, lat } = e.lnglat
updateMarkerPosition(lng, lat)
//
const geocoder = createGeocoder()
geocoder.getAddress([lng, lat], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const address = result.regeocode
//
formState.value.address = address.formattedAddress
formState.value.longitude = lng
formState.value.latitude = lat
} else {
message.error('获取地址信息失败')
}
})
})
}
} catch (error) {
console.error('初始化地图失败:', error)
message.error('初始化地图失败')
}
}
//
const updateMarkerPosition = (lng, lat) => {
if (addMarker.value) {
addMarker.value.setPosition([lng, lat])
} else {
const AMap = window.AMap
addMarker.value = new AMap.Marker({
position: [lng, lat],
map: addMap.value
})
}
}
//
const handleSearch = async (value) => {
if (!value || value.length < 2) {
searchOptions.value = []
return
}
try {
searchLoading.value = true
await loadAMap()
const autoComplete = createAutoComplete()
await new Promise((resolve, reject) => {
autoComplete.search(value, (status, result) => {
if (status === 'complete') {
searchOptions.value = result.tips.map(tip => ({
value: tip.name,
label: `${tip.name} (${tip.district})`,
location: tip.location
}))
resolve(result)
} else {
reject(new Error(status))
}
searchLoading.value = false
})
})
} catch (error) {
console.error('搜索地址失败:', error)
searchLoading.value = false
message.error('搜索地址失败')
}
}
//
const handleSelect = (value, option) => {
const selected = searchOptions.value.find(opt => opt.value === value)
if (selected && selected.location) {
const { lng, lat } = selected.location
updateMarkerPosition(lng, lat)
addMap.value.setCenter([lng, lat])
// 使
const geocoder = createGeocoder()
geocoder.getAddress([lng, lat], (status, result) => {
if (status === 'complete' && result.info === 'OK') {
const address = result.regeocode
//
formState.value.address = address.formattedAddress
formState.value.longitude = lng
formState.value.latitude = lat
} else {
// 使
formState.value.address = value
formState.value.longitude = lng
formState.value.latitude = lat
}
})
}
}
//
const handleUpload = async ({ file, onSuccess, onError }) => {
const formData = new FormData()
formData.append('files', file)
try {
const response = await fetch('/api/upload/images', {
method: 'POST',
body: formData
})
const result = await response.json()
if (result.code === 200) {
const imageUrl = result.data[0] // URL
formState.value.images.push(imageUrl)
onSuccess(result)
} else {
onError()
message.error('上传失败')
}
} catch (error) {
console.error('上传图片失败:', error)
onError()
message.error('上传失败')
}
}
//
const handlePreview = (file) => {
previewImage.value = file.url || file.preview
previewVisible.value = true
previewTitle.value = file.name || file.url.substring(file.url.lastIndexOf('/') + 1)
}
//
const handleRemove = (file) => {
const index = formState.value.images.indexOf(file.url)
if (index > -1) {
formState.value.images.splice(index, 1)
}
}
//
const handleAdd = () => {
formRef.value.validate().then(async () => {
try {
confirmLoading.value = true
const res = await fetch('/api/merchant', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(formState.value)
})
const result = await res.json()
if (result.code === 200) {
message.success('添加成功')
addModalVisible.value = false
fetchData() //
} else {
message.error(result.message || '添加失败')
}
} catch (error) {
console.error('添加商家失败:', error)
message.error('添加失败')
} finally {
confirmLoading.value = false
}
})
}
//
const handleCancel = () => {
formRef.value?.resetFields()
fileList.value = []
formState.value.images = []
addModalVisible.value = false
if (addMarker.value) {
addMarker.value.setMap(null)
addMarker.value = null
}
}
onMounted(() => {
fetchData()
})
@ -229,7 +571,27 @@ export default defineComponent({
showMap,
closeMap,
formatDateTime,
handleManageImages
handleManageImages,
addModalVisible,
confirmLoading,
formRef,
formState,
rules,
searchAddress,
searchOptions,
searchLoading,
fileList,
previewVisible,
previewImage,
previewTitle,
showAddModal,
handleSearch,
handleSelect,
handleUpload,
handlePreview,
handleRemove,
handleAdd,
handleCancel
}
}
})
@ -254,4 +616,22 @@ export default defineComponent({
:deep(.ant-table-content) {
overflow-x: auto;
}
.map-container {
border: 1px solid #d9d9d9;
border-radius: 2px;
overflow: hidden;
}
:deep(.ant-upload-list-picture-card-container) {
width: 104px;
height: 104px;
margin: 0 8px 8px 0;
}
:deep(.ant-upload.ant-upload-select-picture-card) {
width: 104px;
height: 104px;
margin: 0 8px 8px 0;
}
</style>