Commit 07703c77 authored by 法拉51246's avatar 法拉51246

前端腾讯地图api对接

客户信息页面省市区级联+选择地点功能+存储腾讯地图静态地图URL
-----
后端增加产品的导入
改变app-api的会员身份校验(都改为管理员身份userType)
parent 20a8a2b4
......@@ -113,7 +113,7 @@ public class WebFrameworkUtils {
return UserTypeEnum.ADMIN.getValue();
}
if (request.getServletPath().startsWith(properties.getAppApi().getPrefix())) {
return UserTypeEnum.MEMBER.getValue();
return UserTypeEnum.ADMIN.getValue();//暂时去掉会员的用户类型,全部都是管理员用户类型
}
return null;
}
......
......@@ -147,10 +147,10 @@ public class UserController {
public void importTemplate(HttpServletResponse response) throws IOException {
// 手动创建导出 demo
List<UserImportExcelVO> list = Arrays.asList(
UserImportExcelVO.builder().username("yunai").deptId(1L).email("yunai@iocoder.cn").mobile("15601691300")
.nickname("芋道").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(),
UserImportExcelVO.builder().username("yuanma").deptId(2L).email("yuanma@iocoder.cn").mobile("15601701300")
.nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build()
UserImportExcelVO.builder().username("falao").deptId(1L).email("666666@qq.com").mobile("15601691300")
.nickname("模板用户1").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(),
UserImportExcelVO.builder().username("fala").deptId(2L).email("666666@qq.com").mobile("15601701300")
.nickname("模板用户2").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build()
);
// 输出
ExcelUtils.write(response, "用户导入模板.xls", "用户列表", UserImportExcelVO.class, list);
......
......@@ -41,6 +41,10 @@
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-excel</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-biz-data-permission</artifactId>
</dependency>
</dependencies>
</project>
......@@ -40,6 +40,29 @@ public class CustomerInfoRespVO {
@ExcelProperty("所在地区")
private String regionFullName;
@Schema(description = "详细地址", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("详细地址")
private String locationText;
@Schema(description = "经度", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal longitude;
@Schema(description = "纬度", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal latitude;
@Schema(description = "产品ID", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("产品ID")
private String productIds;
@Schema(description = "部门", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("部门")
@DictFormat("customer_dept")
private String department;
@Schema(description = "静态图", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("静态图")
private String locationImage;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("创建时间")
private LocalDateTime createTime;
......
package cn.iocoder.yudao.module.visit.controller.admin.product;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.module.system.enums.common.SexEnum;
import io.swagger.v3.oas.annotations.Parameters;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
......@@ -28,6 +31,7 @@ import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.*;
import cn.iocoder.yudao.module.visit.controller.admin.product.vo.*;
import cn.iocoder.yudao.module.visit.dal.dataobject.product.ProductDO;
import cn.iocoder.yudao.module.visit.service.product.ProductService;
import org.springframework.web.multipart.MultipartFile;
@Tag(name = "管理后台 - 产品")
@RestController
......@@ -38,9 +42,6 @@ public class ProductController {
@Resource
private ProductService productService;
public ProductController(){
System.out.println("生效");
}
@PostMapping("/create")
@Operation(summary = "创建产品")
......@@ -96,4 +97,33 @@ public class ProductController {
BeanUtils.toBean(list, ProductRespVO.class));
}
@GetMapping("/get-import-template")
@Operation(summary = "获得导入品种资料模板")
public void importTemplate(HttpServletResponse response) throws IOException {
// 手动创建导出 demo
List<ProductImportExcelVO> list = Arrays.asList(
ProductImportExcelVO.builder().productName("阿莫西林胶囊").specification("20x20mg").packageName("盒装")
.status(CommonStatusEnum.ENABLE.getStatus()).build(),
ProductImportExcelVO.builder().productName("阿莫西林胶囊").specification("10x50mg").packageName("袋装")
.status(CommonStatusEnum.DISABLE.getStatus()).build()
);
// 输出
ExcelUtils.write(response, "品种资料导入模板.xls", "品种资料列表", ProductImportExcelVO.class, list);
}
@PostMapping("/import")
@Operation(summary = "导入品种资料")
@Parameters({
@Parameter(name = "file", description = "Excel 文件", required = true),
@Parameter(name = "updateSupport", description = "是否支持更新,默认为 false", example = "true")
})
@PreAuthorize("@ss.hasPermission('system:product:import')")
public CommonResult<ProductImportRespVO> importExcel(@RequestParam("file") MultipartFile file,
@RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
List<ProductImportExcelVO> list = ExcelUtils.read(file, ProductImportExcelVO.class);
return success(productService.importProductList(list, updateSupport));
}
}
\ No newline at end of file
package cn.iocoder.yudao.module.visit.controller.admin.product.vo;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import cn.iocoder.yudao.module.system.enums.DictTypeConstants;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
/**
* 用户 Excel 导入 VO
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = false) // 设置 chain = false,避免用户导入有问题
public class ProductImportExcelVO {
@ExcelProperty("品种名称")
private String productName;
@ExcelProperty("规格")
private String specification;
@ExcelProperty("包装信息")
private String packageName;
@ExcelProperty(value = "状态", converter = DictConvert.class)
@DictFormat(DictTypeConstants.COMMON_STATUS)
private Integer status;
}
package cn.iocoder.yudao.module.visit.controller.admin.product.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - 用户导入 Response VO")
@Data
@Builder
public class ProductImportRespVO {
@Schema(description = "创建成功的品种名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> createProductNames;
@Schema(description = "更新成功的品种名称数组", requiredMode = Schema.RequiredMode.REQUIRED)
private List<String> updateProductNames;
@Schema(description = "导入失败的品种名称用户集合,key 为品种名称,value 为失败原因", requiredMode = Schema.RequiredMode.REQUIRED)
private Map<String, String> failureProductNames;
}
......@@ -17,6 +17,10 @@ import cn.iocoder.yudao.module.visit.controller.admin.product.vo.*;
@Mapper
public interface ProductMapper extends BaseMapperX<ProductDO> {
default ProductDO selectByProductParam(String productName, String specification, String packageName) {
return selectOne(ProductDO::getProductName, productName, ProductDO::getSpecification, specification, ProductDO::getPackageName, packageName);
}
default PageResult<ProductDO> selectPage(ProductPageReqVO reqVO) {
return selectPage(reqVO, new LambdaQueryWrapperX<ProductDO>()
.likeIfPresent(ProductDO::getProductName, reqVO.getProductName())
......
......@@ -11,6 +11,8 @@ public interface ErrorCodeConstants {
// ========== 产品 ==========
ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1001000001, "产品不存在");
ErrorCode PRODUCT_IMPORT_LIST_IS_EMPTY = new ErrorCode(1001000002, "导入产品数据不能为空!");
ErrorCode PRODUCT_PRODUCT_EXISTS = new ErrorCode(1001000002, "导入产品已存在!");
// ========== 客户信息 ==========
ErrorCode CUSTOMER_INFO_NOT_EXISTS = new ErrorCode(1002000001, "客户信息不存在");
......
......@@ -52,4 +52,11 @@ public interface ProductService {
*/
PageResult<ProductDO> getProductPage(ProductPageReqVO pageReqVO);
/**
* 导入产品信息
*
* @param list 分页查询
* @return 产品分页
*/
ProductImportRespVO importProductList(List<ProductImportExcelVO> list, Boolean updateSupport);
}
\ No newline at end of file
package cn.iocoder.yudao.module.visit.service.product;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.exception.ServiceException;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.validation.ValidationUtils;
import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import javax.validation.ConstraintViolationException;
import org.springframework.validation.annotation.Validated;
import org.springframework.transaction.annotation.Transactional;
......@@ -15,6 +23,7 @@ import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.visit.dal.mysql.product.ProductMapper;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.module.system.enums.ErrorCodeConstants.*;
import static cn.iocoder.yudao.module.visit.enums.ErrorCodeConstants.*;
/**
......@@ -71,4 +80,47 @@ public class ProductServiceImpl implements ProductService {
return productMapper.selectPage(pageReqVO);
}
@Override
@Transactional(rollbackFor = Exception.class) // 添加事务,异常则回滚所有导入
public ProductImportRespVO importProductList(List<ProductImportExcelVO> importProducts, Boolean isUpdateSupport) {
// 1 参数校验
if (CollUtil.isEmpty(importProducts)) {
throw exception(PRODUCT_IMPORT_LIST_IS_EMPTY);
}
// 2. 遍历,逐个创建 or 更新
ProductImportRespVO respVO = ProductImportRespVO.builder().createProductNames(new ArrayList<>())
.updateProductNames(new ArrayList<>()).failureProductNames(new LinkedHashMap<>()).build();
importProducts.forEach(importProduct -> {
// 2.1.1 校验,判断是否有不符合的原因
try {
validateProductUnique(importProduct.getProductName(), importProduct.getSpecification(), importProduct.getPackageName());
} catch (ServiceException ex) {
respVO.getFailureProductNames().put(importProduct.getProductName()+importProduct.getSpecification()+importProduct.getPackageName(), ex.getMessage());
return;
}
// 2.2.1 判断如果不存在,在进行插入
ProductDO existProduct = productMapper.selectByProductParam(importProduct.getProductName(),importProduct.getSpecification(), importProduct.getPackageName());
if (existProduct == null) {
productMapper.insert(BeanUtils.toBean(importProduct, ProductDO.class));
respVO.getCreateProductNames().add(importProduct.getProductName()+importProduct.getSpecification()+importProduct.getPackageName());
}
});
return respVO;
}
private void validateProductUnique(String productName, String specification, String packageName) {
ProductPageReqVO productPageReqVO = new ProductPageReqVO();
productPageReqVO.setProductName(productName);
productPageReqVO.setSpecification(specification);
productPageReqVO.setPackageName(packageName);
PageResult<ProductDO> productDOPageResult = productMapper.selectPage(productPageReqVO);
if (productDOPageResult.getTotal() > 0) {
throw exception(PRODUCT_PRODUCT_EXISTS);
}
}
}
\ No newline at end of file
......@@ -31,6 +31,7 @@
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-china-area-data": "^6.1.0",
"element-plus": "2.9.1",
"fast-xml-parser": "^4.3.2",
"highlight.js": "^11.9.0",
......@@ -7943,6 +7944,11 @@
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/china-division": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/china-division/-/china-division-2.7.0.tgz",
"integrity": "sha512-4uUPAT+1WfqDh5jytq7omdCmHNk3j+k76zEG/2IqaGcYB90c2SwcixttcypdsZ3T/9tN1TTpBDoeZn+Yw/qBEA=="
},
"node_modules/chokidar": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
......@@ -9423,6 +9429,14 @@
"dev": true,
"license": "ISC"
},
"node_modules/element-china-area-data": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/element-china-area-data/-/element-china-area-data-6.1.0.tgz",
"integrity": "sha512-IkpcjwQv2A/2AxFiSoaISZ+oMw1rZCPUSOg5sOCwT5jKc96TaawmKZeY81xfxXsO0QbKxU5LLc6AirhG52hUmg==",
"dependencies": {
"china-division": "^2.7.0"
}
},
"node_modules/element-plus": {
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.9.1.tgz",
......
......@@ -47,6 +47,7 @@
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
"echarts-wordcloud": "^2.1.0",
"element-china-area-data": "^6.1.0",
"element-plus": "2.9.1",
"fast-xml-parser": "^4.3.2",
"highlight.js": "^11.9.0",
......
......@@ -40,4 +40,9 @@ export const ProductApi = {
exportProduct: async (params) => {
return await request.download({ url: `/visit/product/export-excel`, params })
},
// 下载产品信息导入模板
importProductTemplate: async () => {
return await request.download({ url: '/visit/product/get-import-template' })
}
}
......@@ -70,14 +70,14 @@
>
<Icon icon="ep:plus" /> 新增
</el-button>
<!-- <el-button-->
<!-- type="warning"-->
<!-- plain-->
<!-- @click="handleImport"-->
<!-- v-hasPermi="['system:user:import']"-->
<!-- >-->
<!-- <Icon icon="ep:upload" /> 导入-->
<!-- </el-button>-->
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['system:user:import']"
>
<Icon icon="ep:upload" /> 导入
</el-button>
<!-- <el-button-->
<!-- type="success"-->
<!-- plain-->
......
......@@ -26,30 +26,49 @@
/>
</el-select>
</el-form-item>
<el-form-item label="省名称" prop="provinceName">
<el-input v-model="formData.provinceName" placeholder="请输入省名称" />
</el-form-item>
<el-form-item label="市名称" prop="cityName">
<el-input v-model="formData.cityName" placeholder="请输入市名称" />
</el-form-item>
<el-form-item label="区名称" prop="areaName">
<el-input v-model="formData.areaName" placeholder="请输入区名称" />
</el-form-item>
<el-form-item label="所在地区" prop="regionFullName">
<el-input v-model="formData.regionFullName" placeholder="请输入所在地区" />
<el-cascader
placeholder="请选择地区"
size="default"
:options="pcaTextArr"
v-model="selectedOptions"
@change="handleChange"
/>
</el-form-item>
<!-- <el-form-item label="市名称" prop="cityName">-->
<!-- <el-input v-model="formData.cityName" placeholder="请输入市名称" />-->
<!-- </el-form-item>-->
<!-- -->
<!-- <el-form-item label="区名称" prop="areaName">-->
<!-- <el-input v-model="formData.areaName" placeholder="请输入区名称" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="所在地区" prop="regionFullName">-->
<!-- <el-input v-model="formData.regionFullName" placeholder="请输入所在地区" />-->
<!-- </el-form-item>-->
<el-form-item label="详细地址" prop="locationText">
<el-input v-model="formData.locationText" placeholder="请输入详细地址" />
</el-form-item>
<el-form-item label="经度" prop="longitude">
<el-input v-model="formData.longitude" placeholder="请输入经度" />
</el-form-item>
<el-form-item label="纬度" prop="latitude">
<el-input v-model="formData.latitude" placeholder="请输入纬度" />
</el-form-item>
<el-form-item label="定位静态图 URL" prop="locationImage">
<UploadImg v-model="formData.locationImage" />
<el-input
v-model="formData.locationText"
placeholder="请点击右侧选择"
readonly
>
<template #append>
<el-link type="primary" @click="openMapPicker">选择地址</el-link>
</template>
</el-input>
</el-form-item>
<MapPicker ref="mapPickerRef" @update-location="handleLocation" />
<!-- <el-form-item label="详细地址" prop="locationText">-->
<!-- <el-input v-model="formData.locationText" placeholder="请输入详细地址" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="经度" prop="longitude">-->
<!-- <el-input v-model="formData.longitude" placeholder="请输入经度" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="纬度" prop="latitude">-->
<!-- <el-input v-model="formData.latitude" placeholder="请输入纬度" />-->
<!-- </el-form-item>-->
<!-- <el-form-item label="定位静态图 URL" prop="locationImage">-->
<!-- <UploadImg v-model="formData.locationImage" />-->
<!-- </el-form-item>-->
<el-form-item label="产品信息" prop="productIds">
<el-input v-model="formData.productIds" placeholder="请输入产品信息" />
</el-form-item>
......@@ -73,7 +92,7 @@
<script setup lang="ts">
import { getIntDictOptions, getStrDictOptions, DICT_TYPE } from '@/utils/dict'
import { CustomerInfoApi, CustomerInfoVO } from '@/api/visit/customerinfo'
import { pcaTextArr } from 'element-china-area-data'
/** 客户信息 表单 */
defineOptions({ name: 'CustomerInfoForm' })
......@@ -84,7 +103,25 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const formData = ref({
const selectedOptions = ref([])
interface CustomerFormData {
id?: number;
customerName?: string;
contact?: string;
companyName?: string;
customerType?: string;
provinceName?: string;
cityName?: string;
areaName?: string;
regionFullName?: string;
locationText?: string;
longitude?: number;
latitude?: number;
locationImage?: string;
productIds?: number[];
department?: string;
}
const formData = ref<CustomerFormData>({
id: undefined,
customerName: undefined,
contact: undefined,
......@@ -117,6 +154,26 @@ const formRules = reactive({
})
const formRef = ref() // 表单 Ref
//地图相关操作
const mapPickerRef = ref()
const openMapPicker = () => {
mapPickerRef.value.open()
}
const handleLocation = ({ lat, lng, address }) => {
console.log('lat:', lat, 'lng:', lng, 'address:', address)
//经纬度和地址名称
formData.value.latitude = lat
formData.value.longitude = lng
formData.value.locationText = address
//静态图
const mapKey = 'KHXBZ-OVYYZ-N4NXF-7JCZ2-PR4FT-RYF4E'
const mapUrl = `https://apis.map.qq.com/ws/staticmap/v2/?center=${lat},${lng}&zoom=13&size=600*300&maptype=roadmap&markers=size:large|color:0xFFCCFF|label:k|${lat},${lng}&key=${mapKey}`
formData.value.locationImage = mapUrl
}
/** 打开弹窗 */
const open = async (type: string, id?: number) => {
dialogVisible.value = true
......@@ -133,6 +190,21 @@ const open = async (type: string, id?: number) => {
}
}
}
const handleChange = (value: string[]) => {
//给省市区和所在地区赋值
const [province, city, area] = value
//正常赋值
formData.value.provinceName = province
formData.value.cityName = city
formData.value.areaName = area
formData.value.regionFullName = province + city + area
//直辖市需要将city赋值为province一样的值
if(province.match(/(北京|上海|天津|重庆)/)){
formData.value.cityName = province
formData.value.regionFullName = province + province + area
}
console.log(formData.value.regionFullName)
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
......@@ -180,4 +252,4 @@ const resetForm = () => {
}
formRef.value?.resetFields()
}
</script>
\ No newline at end of file
</script>
<template>
<Dialog v-model="dialogVisible" title="产品导入" width="400">
<el-upload
ref="uploadRef"
v-model:file-list="fileList"
:action="importUrl + '?updateSupport=' + updateSupport"
:auto-upload="false"
:disabled="formLoading"
:headers="uploadHeaders"
:limit="1"
:on-error="submitFormError"
:on-exceed="handleExceed"
:on-success="submitFormSuccess"
accept=".xlsx, .xls"
drag
>
<Icon icon="ep:upload" />
<div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div>
<template #tip>
<div class="el-upload__tip text-center">
<span>仅允许导入 xls、xlsx 格式文件。</span>
<el-link
:underline="false"
style="font-size: 12px; vertical-align: baseline"
type="primary"
@click="importTemplate"
>
下载模板
</el-link>
</div>
</template>
</el-upload>
<template #footer>
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
</template>
<script lang="ts" setup>
import {getAccessToken, getTenantId} from '@/utils/auth'
import download from '@/utils/download'
import {ProductApi} from "@/api/visit/product";
defineOptions({ name: 'SystemProductImportForm' })
const message = useMessage() // 消息弹窗
const dialogVisible = ref(false) // 弹窗的是否展示
const formLoading = ref(false) // 表单的加载中
const uploadRef = ref()
const importUrl =
import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/visit/product/import'
const uploadHeaders = ref() // 上传 Header 头
const fileList = ref([]) // 文件列表
const updateSupport = ref(0) // 是否更新已经存在的用户数据
/** 打开弹窗 */
const open = () => {
dialogVisible.value = true
updateSupport.value = 0
fileList.value = []
resetForm()
}
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
/** 提交表单 */
const submitForm = async () => {
if (fileList.value.length == 0) {
message.error('请上传文件')
return
}
// 提交请求
uploadHeaders.value = {
Authorization: 'Bearer ' + getAccessToken(),
'tenant-id': getTenantId()
}
formLoading.value = true
uploadRef.value!.submit()
}
/** 文件上传成功 */
const emits = defineEmits(['success'])
const submitFormSuccess = (response: any) => {
if (response.code !== 0) {
message.error(response.msg)
formLoading.value = false
return
}
// 拼接提示语
const data = response.data
let text = '上传成功数量:' + data.createProductNames.length + ';'
for (let productName of data.createProductNames) {
text += '< ' + productName + ' >'
}
// text += '更新成功数量:' + data.updateProductNames.length + ';'
// for (const productName of data.updateProductNames) {
// text += '< ' + productName + ' >'
// }
text += '上传失败数量:' + Object.keys(data.failureProductNames).length + ';'
for (const productName in data.failureProductNames) {
text += '< ' + productName + ': ' + data.failureProductNames[productName] + ' >'
}
message.alert(text)
formLoading.value = false
dialogVisible.value = false
// 发送操作成功的事件
emits('success')
}
/** 上传错误提示 */
const submitFormError = (): void => {
message.error('上传失败,请您重新上传!')
formLoading.value = false
}
/** 重置表单 */
const resetForm = async (): Promise<void> => {
// 重置上传状态和文件
formLoading.value = false
await nextTick()
uploadRef.value?.clearFiles()
}
/** 文件数超出提示 */
const handleExceed = (): void => {
message.error('最多只能上传一个文件!')
}
/** 下载模板操作 */
const importTemplate = async () => {
const res = await ProductApi.importProductTemplate()
download.excel(res, '产品信息导入模版.xls')
}
</script>
......@@ -72,6 +72,14 @@
>
<Icon icon="ep:plus" class="mr-5px" /> 新增
</el-button>
<el-button
type="warning"
plain
@click="handleImport"
v-hasPermi="['visit:product:import']"
>
<Icon icon="ep:upload" /> 导入
</el-button>
<el-button
type="success"
plain
......@@ -136,6 +144,8 @@
<!-- 表单弹窗:添加/修改 -->
<ProductForm ref="formRef" @success="getList" />
<!-- 产品信息导入对话框 -->
<ProductImportForm ref="importFormRef" @success="getList" />
</template>
<script setup lang="ts">
......@@ -144,6 +154,7 @@ import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { ProductApi, ProductVO } from '@/api/visit/product'
import ProductForm from './ProductForm.vue'
import ProductImportForm from './ProductImportForm.vue'
/** 产品 列表 */
defineOptions({ name: 'Product' })
......@@ -190,6 +201,12 @@ const resetQuery = () => {
handleQuery()
}
/** 产品导入 */
const importFormRef = ref()
const handleImport = () => {
importFormRef.value.open()
}
/** 添加/修改操作 */
const formRef = ref()
const openForm = (type: string, id?: number) => {
......
<template>
<el-dialog
v-model="visible"
title="选择地址"
width="80%"
top="5vh"
:close-on-click-modal="false"
append-to-body
>
<iframe
id="tencentMapPicker"
:src="pickerUrl"
width="100%"
height="600px"
frameborder="0"
allow="geolocation"
style="border: 0"
></iframe>
</el-dialog>
</template>
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
// ✅ 地图弹窗显隐
const visible = ref(false)
// ✅ 腾讯地图 locationPicker 地址,替换成你的 key & 应用名
const pickerUrl = `https://apis.map.qq.com/tools/locpicker?search=1&type=1&key=KHXBZ-OVYYZ-N4NXF-7JCZ2-PR4FT-RYF4E&referer=VISITKEY`
// ✅ 定义暴露 open() 方法,父组件通过 ref 调用
const open = () => {
visible.value = true
}
defineExpose({ open })
// ✅ 向父组件发回选中的地址和经纬度
const emit = defineEmits<{
(e: 'update-location', payload: { lat: number; lng: number; address: string }): void
}>()
// ✅ 监听 locationPicker 的 postMessage
const handleMessage = (event: MessageEvent) => {
const loc = event.data
if (loc && loc.module === 'locationPicker') {
const lat = loc.latlng.lat
const lng = loc.latlng.lng
const address = loc.poiaddress || loc.poiname || loc.address
emit('update-location', { lat, lng, address })
visible.value = false // 选完后自动关闭弹窗
}
}
onMounted(() => {
window.addEventListener('message', handleMessage)
})
onUnmounted(() => {
window.removeEventListener('message', handleMessage)
})
</script>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment