Commit db94161b authored by 法拉51246's avatar 法拉51246

三个基本功能基本完成

parent 07703c77
package cn.iocoder.yudao.module.visit.controller.admin.customerinfo;
import cn.iocoder.yudao.module.visit.controller.admin.product.vo.ProductSimpleReqVO;
import cn.iocoder.yudao.module.visit.service.product.ProductService;
import com.fhs.common.utils.StringUtil;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import org.springframework.validation.annotation.Validated;
......@@ -37,6 +40,8 @@ public class CustomerInfoController {
@Resource
private CustomerInfoService customerInfoService;
@Resource
private ProductService productService;
@PostMapping("/create")
@Operation(summary = "创建客户信息")
......@@ -68,7 +73,12 @@ public class CustomerInfoController {
@PreAuthorize("@ss.hasPermission('visit:customer-info:query')")
public CommonResult<CustomerInfoRespVO> getCustomerInfo(@RequestParam("id") Long id) {
CustomerInfoDO customerInfo = customerInfoService.getCustomerInfo(id);
return success(BeanUtils.toBean(customerInfo, CustomerInfoRespVO.class));
CustomerInfoRespVO bean = BeanUtils.toBean(customerInfo, CustomerInfoRespVO.class);
// if (!StringUtil.isEmpty(customerInfo.getProductIds())){
// List<ProductSimpleReqVO> productSimpleReqVO = productService.getProductByIds(customerInfo.getProductIds());
// bean.setProductList(productSimpleReqVO);
// }
return success(bean);
}
@GetMapping("/page")
......
package cn.iocoder.yudao.module.visit.controller.admin.customerinfo.vo;
import cn.iocoder.yudao.module.visit.controller.admin.product.vo.ProductSimpleReqVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import java.util.*;
......@@ -36,6 +37,16 @@ public class CustomerInfoRespVO {
@DictFormat("customer_type") // TODO 代码优化:建议设置到对应的 DictTypeConstants 枚举类中
private Integer customerType;
//省名称
@Schema(description = "省名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String provinceName;
//市名称
@Schema(description = "市名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String cityName;
//区名称
@Schema(description = "区名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String areaName;
@Schema(description = "所在地区", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("所在地区")
private String regionFullName;
......@@ -54,8 +65,8 @@ public class CustomerInfoRespVO {
@ExcelProperty("产品ID")
private String productIds;
@Schema(description = "部门", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("部门")
@Schema(description = "客户所属部门", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("客户所属部门")
@DictFormat("customer_dept")
private String department;
......@@ -67,4 +78,7 @@ public class CustomerInfoRespVO {
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@Schema(description = "产品信息List对象")
private List<ProductSimpleReqVO> productList;
}
\ No newline at end of file
......@@ -33,7 +33,7 @@ import cn.iocoder.yudao.module.visit.service.info.InfoService;
@RestController
@RequestMapping("/visit/info")
@Validated
public class InfoController {
public class InfoController {
@Resource
private InfoService infoService;
......
......@@ -9,6 +9,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageParam;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 客户拜访记录分页 Request VO")
......@@ -26,8 +27,11 @@ public class InfoPageReqVO extends PageParam {
@Schema(description = "客户公司名称")
private String companyName;
@Schema(description = "所在地区")
private String regionFullName;
@Schema(description = "拜访日期")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
private LocalDate[] visitDate;
@Schema(description = "性质等级", example = "1")
......
package cn.iocoder.yudao.module.visit.controller.admin.info.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
......@@ -12,6 +13,9 @@ import com.alibaba.excel.annotation.*;
import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
@Schema(description = "管理后台 - 客户拜访记录 Response VO")
@Data
@ExcelIgnoreUnannotated
......@@ -33,12 +37,39 @@ public class InfoRespVO {
@ExcelProperty("客户公司名称")
private String companyName;
@Schema(description = "所在地区", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("所在地区")
private String regionFullName;
@Schema(description = "省名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String provinceName;
@Schema(description = "市名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String cityName;
@Schema(description = "区名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String areaName;
@Schema(description = "定位地址文字描述", requiredMode = Schema.RequiredMode.REQUIRED)
private String locationText;
@Schema(description = "经度", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal longitude;
@Schema(description = "纬度", requiredMode = Schema.RequiredMode.REQUIRED)
private BigDecimal latitude;
@Schema(description = "定位静态图URL", requiredMode = Schema.RequiredMode.REQUIRED)
private String locationImage;
@Schema(description = "拜访日期", requiredMode = Schema.RequiredMode.REQUIRED)
@ExcelProperty("拜访日期")
@JsonFormat(pattern = "yyyy-MM-dd")
private LocalDate visitDate;
@Schema(description = "性质等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@ExcelProperty("性质等级")
@ExcelProperty(value = "性质等级", converter = DictConvert.class)
@DictFormat("customer_type")
private Integer customerStatus;
@Schema(description = "拜访品种(多选,逗号分隔)")
......@@ -59,4 +90,18 @@ public class InfoRespVO {
@ExcelProperty("创建时间")
private LocalDateTime createTime;
@Schema(description = "服务内容")
@ExcelProperty("服务内容")
private String serviceContent;
@Schema(description = "客户反馈")
@ExcelProperty("客户反馈")
private String customerFeedback;
@Schema(description = "服务记录图片URL列表(JSON数组)")
@ExcelProperty("服务记录图片URL列表(JSON数组)")
private String serviceImages;
}
\ No newline at end of file
......@@ -39,6 +39,9 @@ public class InfoSaveReqVO {
@NotEmpty(message = "区名称不能为空")
private String areaName;
@Schema(description = "所在地区")
private String regionFullName;
@Schema(description = "定位地址文字描述", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "定位地址文字描述不能为空")
private String locationText;
......
......@@ -76,6 +76,15 @@ public class ProductController {
return success(BeanUtils.toBean(product, ProductRespVO.class));
}
@GetMapping("/getListByIds")
@Operation(summary = "获得产品")
@Parameter(name = "ids", description = "编号", required = true, example = "1024,1023")
@PreAuthorize("@ss.hasPermission('visit:product:query')")
public CommonResult<List<ProductSimpleReqVO>> getProduct(@RequestParam("ids") String ids) {
List<ProductSimpleReqVO> productByIds = productService.getProductByIds(ids);
return success(productByIds);
}
@GetMapping("/page")
@Operation(summary = "获得产品分页")
@PreAuthorize("@ss.hasPermission('visit:product:query')")
......
package cn.iocoder.yudao.module.visit.controller.admin.product.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import com.alibaba.excel.annotation.ExcelProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - 产品弹窗 Request VO")
@Data
@ToString(callSuper = true)
public class ProductSimpleReqVO {
@Schema(description = "编号", example = "1")
private Long id;
@Schema(description = "品种名称", example = "阿莫西林胶囊")
private String productName;
@Schema(description = "包装信息", example = "盒装")
private String packageName;
@Schema(description = "规格", example = "10x20mg")
private String specification;
}
\ No newline at end of file
......@@ -67,6 +67,10 @@ public class InfoDO extends BaseDO {
* 区名称
*/
private String areaName;
/**
* 所在地区
*/
private String regionFullName;
/**
* 定位地址文字描述
*/
......
......@@ -59,4 +59,6 @@ public interface ProductService {
* @return 产品分页
*/
ProductImportRespVO importProductList(List<ProductImportExcelVO> list, Boolean updateSupport);
List<ProductSimpleReqVO> getProductByIds(String productIds);
}
\ No newline at end of file
......@@ -111,6 +111,22 @@ public class ProductServiceImpl implements ProductService {
return respVO;
}
@Override
public List<ProductSimpleReqVO> getProductByIds(String productIds) {
//1.判断productIds是否为空
if (StrUtil.isEmpty(productIds)) {
return new ArrayList<>();
}
//2.按逗号切分成数组
String[] ids = productIds.split(",");
//3.判断数组长度
if (ids.length > 0) {
List<ProductDO> productDOS = productMapper.selectByIds(Arrays.asList(ids));
return BeanUtils.toBean(productDOS, ProductSimpleReqVO.class);
}
return new ArrayList<>();
}
private void validateProductUnique(String productName, String specification, String packageName) {
ProductPageReqVO productPageReqVO = new ProductPageReqVO();
......
......@@ -26,7 +26,7 @@
"camunda-bpmn-moddle": "^7.0.1",
"cropperjs": "^1.6.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"dayjs": "^1.11.13",
"diagram-js": "^12.8.0",
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
......@@ -9042,8 +9042,7 @@
"node_modules/dayjs": {
"version": "1.11.13",
"resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz",
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==",
"license": "MIT"
"integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg=="
},
"node_modules/de-indent": {
"version": "1.0.2",
......
......@@ -42,7 +42,7 @@
"camunda-bpmn-moddle": "^7.0.1",
"cropperjs": "^1.6.1",
"crypto-js": "^4.2.0",
"dayjs": "^1.11.10",
"dayjs": "^1.11.13",
"diagram-js": "^12.8.0",
"driver.js": "^1.3.1",
"echarts": "^5.5.0",
......
import request from '@/config/axios'
// 客户信息 VO
export interface CustomerInfoVO {
id: number // 编号
customerName: string // 客户姓名
contact: string // 联系方式
companyName: string // 公司名称
customerType: number // 性质等级
provinceName: string // 省名称
cityName: string // 市名称
areaName: string // 区名称
regionFullName: string // 所在地区
locationText: string // 详细地址
longitude: number // 经度
latitude: number // 纬度
locationImage: string // 定位静态图 URL
productIds: string // 产品信息
department: string // 客户部门
}
// 客户信息 API
export const CustomerInfoApi = {
// 查询客户信息分页
getCustomerInfoPage: async (params: any) => {
return await request.get({ url: `/visit/customer-info/page`, params })
},
// 查询客户信息详情
getCustomerInfo: async (id: number) => {
return await request.get({ url: `/visit/customer-info/get?id=` + id })
},
// 新增客户信息
createCustomerInfo: async (data: CustomerInfoVO) => {
return await request.post({ url: `/visit/customer-info/create`, data })
},
// 修改客户信息
updateCustomerInfo: async (data: CustomerInfoVO) => {
return await request.put({ url: `/visit/customer-info/update`, data })
},
// 删除客户信息
deleteCustomerInfo: async (id: number) => {
return await request.delete({ url: `/visit/customer-info/delete?id=` + id })
},
// 导出客户信息 Excel
exportCustomerInfo: async (params) => {
return await request.download({ url: `/visit/customer-info/export-excel`, params })
},
}
\ No newline at end of file
import request from '@/config/axios'
// 客户信息 VO
export interface CustomerInfoVO {
id: number // 编号
customerName: string // 客户姓名
contact: string // 联系方式
companyName: string // 公司名称
customerType: number // 性质等级
provinceName: string // 省名称
cityName: string // 市名称
areaName: string // 区名称
regionFullName: string // 所在地区
locationText: string // 详细地址
longitude: number // 经度
latitude: number // 纬度
locationImage: string // 定位静态图 URL
productIds: string // 产品信息
department: string // 客户部门
}
// 客户信息 API
export const CustomerInfoApi = {
// 查询客户信息分页
getCustomerInfoPage: async (params: any) => {
return await request.get({ url: `/visit/customer-info/page`, params })
},
// 查询客户信息详情
getCustomerInfo: async (id: number) => {
return await request.get({ url: `/visit/customer-info/get?id=` + id })
},
// 查询客户信息中拜访品种详情
getProductSimpleList: async (id: number) => {
return await request.get({ url: `/visit/customer-info/getProductSimpleList?id=` + id })
},
// 新增客户信息
createCustomerInfo: async (data: CustomerInfoVO) => {
return await request.post({ url: `/visit/customer-info/create`, data })
},
// 修改客户信息
updateCustomerInfo: async (data: CustomerInfoVO) => {
return await request.put({ url: `/visit/customer-info/update`, data })
},
// 删除客户信息
deleteCustomerInfo: async (id: number) => {
return await request.delete({ url: `/visit/customer-info/delete?id=` + id })
},
// 导出客户信息 Excel
exportCustomerInfo: async (params) => {
return await request.download({ url: `/visit/customer-info/export-excel`, params })
},
}
......@@ -21,6 +21,11 @@ export const ProductApi = {
return await request.get({ url: `/visit/product/get?id=` + id })
},
// 查询产品详情ByIds
getProductByIds: async (ids: string) => {
return await request.get({ url: `/visit/product/getListByIds?ids=` + ids })
},
// 新增产品
createProduct: async (data: ProductVO) => {
return await request.post({ url: `/visit/product/create`, data })
......
......@@ -136,19 +136,24 @@ const uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {
// 监听模型绑定值变动
watch(
() => props.modelValue,
(val: string | string[]) => {
if (!val) {
fileList.value = [] // fix:处理掉缓存,表单重置后上传组件的内容并没有重置
return
(val) => {
// 防止 val 为字符串、null、undefined 报错
let urls: string[] = []
if (Array.isArray(val)) {
urls = val
} else if (typeof val === 'string') {
urls = val.split(',').filter((url) => url.trim() !== '')
}
fileList.value = [] // 保障数据为空
fileList.value.push(
...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))
)
fileList.value = urls.map((url) => ({
name: url.substring(url.lastIndexOf('/') + 1),
url
}))
},
{ immediate: true, deep: true }
{ immediate: true }
)
// 发送图片链接列表更新
const emitUpdateModelValue = () => {
let result: string[] = fileList.value.map((file) => file.url!)
......
......@@ -70,7 +70,15 @@
<!-- <UploadImg v-model="formData.locationImage" />-->
<!-- </el-form-item>-->
<el-form-item label="产品信息" prop="productIds">
<el-input v-model="formData.productIds" placeholder="请输入产品信息" />
<el-input
v-model="formData.productIds"
placeholder="请点击右侧选择"
readonly
>
<template #append>
<el-link type="primary" @click="openProduct">选择产品</el-link>
</template>
</el-input>
</el-form-item>
<el-form-item label="客户部门" prop="department">
<el-select v-model="formData.department" placeholder="请选择客户部门">
......@@ -88,11 +96,14 @@
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
<productTable ref="productListRef" @update-product="handleUpdateProduct" :ids="formData.productIds"/>
</template>
<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'
import MapPicker from '../util/MapPicker.vue'
import productTable from './productTable.vue'
/** 客户信息 表单 */
defineOptions({ name: 'CustomerInfoForm' })
......@@ -103,7 +114,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const selectedOptions = ref([])
const selectedOptions = ref<string[]>([])
interface CustomerFormData {
id?: number;
customerName?: string;
......@@ -118,7 +129,7 @@ interface CustomerFormData {
longitude?: number;
latitude?: number;
locationImage?: string;
productIds?: number[];
productIds?: string;
department?: string;
}
const formData = ref<CustomerFormData>({
......@@ -154,7 +165,20 @@ const formRules = reactive({
})
const formRef = ref() // 表单 Ref
//地图相关操作
//===========================================选择产品相关操作=================================
const productListRef = ref<InstanceType<typeof productTable> | null>(null); // 产品列表 Ref
// 打开二级产品列表弹窗
const openProduct=()=>{
productListRef.value?.open(); // 调用子组件暴露的方法
}
const handleUpdateProduct = (ids: string) => {
console.log('来自子组件的产品ID:', ids);
//存入表单
formData.value.productIds = ids;
};
//===========================================地图相关操作=================================
const mapPickerRef = ref()
const openMapPicker = () => {
......@@ -184,12 +208,25 @@ const open = async (type: string, id?: number) => {
if (id) {
formLoading.value = true
try {
formData.value = await CustomerInfoApi.getCustomerInfo(id)
formData.value = await CustomerInfoApi.getCustomerInfo(id)//进入页面根据id获取数据
restoreSelectedOptionsFromForm();
} finally {
formLoading.value = false
}
}
}
function restoreSelectedOptionsFromForm() {
const province = formData.value.provinceName ?? ''
let city = formData.value.cityName ?? ''
const area = formData.value.areaName ?? ''
//如果是直辖市,cityName要重新赋值为市辖区,否则无法回显
if(province.match(/(北京|上海|天津|重庆)/)){
city = '市辖区'
}
selectedOptions.value = [province, city, area]
}
const handleChange = (value: string[]) => {
//给省市区和所在地区赋值
const [province, city, area] = value
......@@ -251,5 +288,7 @@ const resetForm = () => {
department: undefined,
}
formRef.value?.resetFields()
selectedOptions.value = [];
}
</script>
......@@ -105,6 +105,11 @@
:formatter="dateFormatter"
width="180px"
/>
<el-table-column label="产品信息" prop="productIds">
<template #default="scope">
<ProductList :ids=scope.row.productIds />
</template>
</el-table-column>
<el-table-column label="操作" align="center" min-width="120px">
<template #default="scope">
<el-button
......@@ -145,6 +150,7 @@ import { dateFormatter } from '@/utils/formatTime'
import download from '@/utils/download'
import { CustomerInfoApi, CustomerInfoVO } from '@/api/visit/customerinfo'
import CustomerInfoForm from './CustomerInfoForm.vue'
import ProductList from "@/views/visit/util/ProductList.vue";
/** 客户信息 列表 */
defineOptions({ name: 'CustomerInfo' })
......
<template>
<el-dialog v-model="dialogTableVisible" title="选择产品" width="800">
<ContentWrap>
<!-- 搜索工作栏 -->
<el-form
class="-mb-15px"
:model="queryParams"
ref="queryFormRef"
:inline="true"
label-width="68px"
>
<el-form-item label="品种名称" prop="productName">
<el-input
v-model="queryParams.productName"
placeholder="请输入品种名称"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="包装信息" prop="packageName">
<el-input
v-model="queryParams.packageName"
placeholder="请输入包装信息"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="规格" prop="specification">
<el-input
v-model="queryParams.specification"
placeholder="请输入规格"
clearable
@keyup.enter="handleQuery"
class="!w-240px"
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryParams.status"
placeholder="请选择状态"
clearable
class="!w-240px"
>
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="创建时间" prop="createTime">
<el-date-picker
v-model="queryParams.createTime"
value-format="YYYY-MM-DD HH:mm:ss"
type="daterange"
start-placeholder="开始日期"
end-placeholder="结束日期"
:default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]"
class="!w-220px"
/>
</el-form-item>
<el-form-item>
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
<el-button @click="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
<el-button @click="sendToParent" type="success" plain> 确认</el-button>
</el-form-item>
</el-form>
</ContentWrap>
<!-- 列表 -->
<ContentWrap>
<el-table :reserve-selection="true" ref="tableRef" v-loading="loading" row-key="id" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="品种名称" align="center" prop="productName" />
<el-table-column label="包装信息" align="center" prop="packageName" />
<el-table-column label="规格" align="center" prop="specification" />
<el-table-column label="状态" align="center" prop="status">
<template #default="scope">
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column
label="创建时间"
align="center"
prop="createTime"
:formatter="dateFormatter"
width="180px"
/>
</el-table>
<!-- 分页 -->
<Pagination
:total="total"
v-model:page="queryParams.pageNo"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</ContentWrap>
</el-dialog>
</template>
<script setup lang="ts">
import {DICT_TYPE, getIntDictOptions} from '@/utils/dict'
import {dateFormatter} from '@/utils/formatTime'
import {ProductApi, ProductVO} from '@/api/visit/product'
import {defineEmits} from 'vue';
const dialogTableVisible = ref(false)
/** 产品 列表 */
defineOptions({ name: 'Product' })
//接受参数ids
const props = defineProps({
ids: {
type: String,
required: false,
default: () => ''
}
})
const tableRef = ref();//表单
// 定义事件名和参数类型(可选)
const emit = defineEmits<{
(e: 'updateProduct', data: string): void;
}>();
const sendToParent = () => {
const joinedIds = multipleSelection.value.join(','); // ✅ 转为 "101,203,305"
emit('updateProduct', joinedIds); // ✅ 传 string 给父组件
dialogTableVisible.value = false; // ✅ 关闭弹窗
};
const loading = ref(true) // 列表的加载中
const list = ref<ProductVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
const queryParams = reactive({
pageNo: 1,
pageSize: 10,
productName: undefined,
packageName: undefined,
specification: undefined,
status: undefined,
createTime: [],
})
const queryFormRef = ref() // 搜索的表单
const multipleSelection = ref<any[]>([]) //存储多选内容
// 打开弹窗
const open = () => {
dialogTableVisible.value = true
//先清除勾选,再重新匹配勾选的反显
tableRef.value?.clearSelection();
if (props.ids && props.ids.trim() !== '') {
multipleSelection.value = props.ids.split(',').map(item => Number(item)) //将父组件传递过来的ids转为数组
}
getList().then(() => {
toggleDefaultSelection();
});
}
/** 多选操作**/
const handleSelectionChange = (val) => {
multipleSelection.value = val.map(item=>item.id)
console.log(val,multipleSelection)
}
/** 查询列表 */
const getList = async () => {
loading.value = true
try {
const data = await ProductApi.getProductPage(queryParams)
list.value = data.list
total.value = data.total
} finally {
loading.value = false
}
}
/** 回显已选择 */
const toggleDefaultSelection = () => {
nextTick(() => {
multipleSelection.value.forEach(id => {
const row = list.value.find(item => item.id === id);
if (row) {
tableRef.value?.toggleRowSelection(row, true);
}
});
});
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.pageNo = 1
getList()
}
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value.resetFields()
handleQuery()
}
/** 初始化 **/
onMounted(() => {
})
// 暴露函数
defineExpose({ open });
</script>
<style scoped lang="scss">
</style>
......@@ -13,30 +13,54 @@
<el-form-item label="联系方式" prop="contact">
<el-input v-model="formData.contact" placeholder="请输入联系方式" />
</el-form-item>
<el-form-item label="客户公司名称" prop="companyName">
<el-form-item label="公司名称" prop="companyName">
<el-input v-model="formData.companyName" placeholder="请输入客户公司名称" />
</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="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 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="areaName">-->
<!-- <el-input v-model="formData.regionFullName" placeholder="请输入区名称" />-->
<!-- </el-form-item>-->
<el-form-item label="所在地区" prop="regionFullName">
<el-cascader
placeholder="请选择地区"
size="default"
:options="pcaTextArr"
v-model="selectedOptions"
@change="handleChange"
/>
</el-form-item>
<el-form-item label="定位静态图URL" prop="locationImage">
<UploadImg v-model="formData.locationImage" />
<!-- <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="locationText">
<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="visitDate">
<el-date-picker
v-model="formData.visitDate"
......@@ -46,12 +70,25 @@
/>
</el-form-item>
<el-form-item label="性质等级" prop="customerStatus">
<el-radio-group v-model="formData.customerStatus">
<el-radio value="1">请选择字典生成</el-radio>
</el-radio-group>
<el-select v-model="formData.customerStatus" placeholder="请选择性质等级">
<el-option
v-for="dict in getIntDictOptions(DICT_TYPE.CUSTOMER_TYPE)"
:key="dict.value"
:label="dict.label"
:value="dict.value"
/>
</el-select>
</el-form-item>
<el-form-item label="拜访品种" prop="visitProductIds">
<el-input v-model="formData.visitProductIds" placeholder="请输入拜访品种" />
<el-input
v-model="formData.visitProductIds"
placeholder="请点击右侧选择"
readonly
>
<template #append>
<el-link type="primary" @click="openProduct">选择产品</el-link>
</template>
</el-input>
</el-form-item>
<el-form-item label="拜访方式" prop="visitMethod">
<el-select v-model="formData.visitMethod" placeholder="请选择拜访方式">
......@@ -80,7 +117,7 @@
<el-input v-model="formData.customerFeedback" placeholder="请输入客户反馈" />
</el-form-item>
<el-form-item label="服务记录图片URL列表" prop="serviceImages">
<UploadImg v-model="formData.serviceImages" />
<UploadImgs v-model="formData.serviceImages" :limit="9"/>
</el-form-item>
</el-form>
<template #footer>
......@@ -88,10 +125,15 @@
<el-button @click="dialogVisible = false">取 消</el-button>
</template>
</Dialog>
<productTable ref="productListRef" @updateProduct="handleUpdateProduct" :ids="formData.visitProductIds"/>
</template>
<script setup lang="ts">
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
import { InfoApi, InfoVO } from '@/api/visit/info'
import productTable from "@/views/visit/customerinfo/productTable.vue";
import { pcaTextArr } from 'element-china-area-data'
import MapPicker from '../util/MapPicker.vue'
import dayjs from 'dayjs';
/** 客户拜访记录 表单 */
defineOptions({ name: 'InfoForm' })
......@@ -103,6 +145,7 @@ const dialogVisible = ref(false) // 弹窗的是否展示
const dialogTitle = ref('') // 弹窗的标题
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
const formType = ref('') // 表单的类型:create - 新增;update - 修改
const selectedOptions = ref<string[]>([])
const formData = ref({
id: undefined,
customerName: undefined,
......@@ -111,6 +154,7 @@ const formData = ref({
provinceName: undefined,
cityName: undefined,
areaName: undefined,
regionFullName: undefined,
locationText: undefined,
longitude: undefined,
latitude: undefined,
......@@ -122,7 +166,7 @@ const formData = ref({
visitType: undefined,
serviceContent: undefined,
customerFeedback: undefined,
serviceImages: undefined,
serviceImages: [],
})
const formRules = reactive({
customerName: [{ required: true, message: '拜访人姓名不能为空', trigger: 'blur' }],
......@@ -131,6 +175,7 @@ const formRules = reactive({
provinceName: [{ required: true, message: '省名称不能为空', trigger: 'blur' }],
cityName: [{ required: true, message: '市名称不能为空', trigger: 'blur' }],
areaName: [{ required: true, message: '区名称不能为空', trigger: 'blur' }],
regionFullName: [{ required: true, message: '所在地区不能为空', trigger: 'blur' }],
locationText: [{ required: true, message: '定位地址文字描述不能为空', trigger: 'blur' }],
longitude: [{ required: true, message: '经度不能为空', trigger: 'blur' }],
latitude: [{ required: true, message: '纬度不能为空', trigger: 'blur' }],
......@@ -140,6 +185,40 @@ const formRules = reactive({
})
const formRef = ref() // 表单 Ref
//===========================================选择产品相关操作=================================
const productListRef = ref<InstanceType<typeof productTable> | null>(null); // 产品列表 Ref
// 打开二级产品列表弹窗
const openProduct=()=>{
productListRef.value?.open(); // 调用子组件暴露的方法
}
const handleUpdateProduct = (ids: string) => {
console.log('来自子组件的产品ID:', ids);
//存入表单
formData.value.visitProductIds = ids;
};
//===========================================地图相关操作=================================
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
......@@ -151,11 +230,44 @@ const open = async (type: string, id?: number) => {
formLoading.value = true
try {
formData.value = await InfoApi.getInfo(id)
restoreSelectedOptionsFromForm();
//将图片url数组从字符串转为数组
formData.value.serviceImages = formData.value.serviceImages.split(',')
console.log(formData.value.serviceImages)
} finally {
formLoading.value = false
}
}
}
function restoreSelectedOptionsFromForm() {
const province = formData.value.provinceName ?? ''
let city = formData.value.cityName ?? ''
const area = formData.value.areaName ?? ''
//如果是直辖市,cityName要重新赋值为市辖区,否则无法回显
if(province.match(/(北京|上海|天津|重庆)/)){
city = '市辖区'
}
selectedOptions.value = [province, city, area]
}
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 方法,用于打开弹窗
/** 提交表单 */
......@@ -163,6 +275,13 @@ const emit = defineEmits(['success']) // 定义 success 事件,用于操作成
const submitForm = async () => {
// 校验表单
await formRef.value.validate()
//将图片url数组从数组变为string字符串
const raw = formData.value;
if (Array.isArray(raw.serviceImages)) {
raw.serviceImages = raw.serviceImages.join(',');
}
//时间戳转换成字符串
formData.value.visitDate = dayjs(formData.value.visitDate).format('YYYY-MM-DD');
// 提交请求
formLoading.value = true
try {
......@@ -192,6 +311,7 @@ const resetForm = () => {
provinceName: undefined,
cityName: undefined,
areaName: undefined,
regionFullName: undefined,
locationText: undefined,
longitude: undefined,
latitude: undefined,
......@@ -203,8 +323,9 @@ const resetForm = () => {
visitType: undefined,
serviceContent: undefined,
customerFeedback: undefined,
serviceImages: undefined,
serviceImages: []
}
formRef.value?.resetFields()
selectedOptions.value = [];
}
</script>
......@@ -137,8 +137,13 @@
<el-table-column label="拜访人姓名" align="center" prop="customerName" width="100px"/>
<el-table-column label="联系方式" align="center" prop="contact" />
<el-table-column label="客户公司名称" align="center" prop="companyName" width="120px" />
<el-table-column label="所在省市区" align="center" prop="regionFullName" width="100px" />
<el-table-column label="拜访日期" align="center" prop="visitDate" />
<el-table-column label="性质等级" align="center" prop="customerStatus" />
<el-table-column label="性质等级" align="center" prop="customerStatus">
<template #default="scope">
<dict-tag :type="DICT_TYPE.CUSTOMER_TYPE" :value="scope.row.customerStatus" />
</template>
</el-table-column>
<el-table-column label="拜访品种" align="center" prop="visitProductIds" />
<el-table-column label="拜访方式" align="center" prop="visitMethod">
<template #default="scope">
......
......@@ -95,7 +95,8 @@
<!-- 列表 -->
<ContentWrap>
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" />
<el-table-column label="编号" align="center" prop="id" />
<el-table-column label="品种名称" align="center" prop="productName" />
<el-table-column label="包装信息" align="center" prop="packageName" />
......@@ -161,7 +162,7 @@ defineOptions({ name: 'Product' })
const message = useMessage() // 消息弹窗
const { t } = useI18n() // 国际化
// const selectable = (row) => ![1, 2].includes(row.id)
const loading = ref(true) // 列表的加载中
const list = ref<ProductVO[]>([]) // 列表的数据
const total = ref(0) // 列表的总页数
......@@ -176,7 +177,13 @@ const queryParams = reactive({
})
const queryFormRef = ref() // 搜索的表单
const exportLoading = ref(false) // 导出的加载中
const multipleSelection = ref([]) //存储多选内容
/** 多选操作**/
const handleSelectionChange = (val) => {
multipleSelection.value = val.map(item=>item.id)
console.log(val,multipleSelection)
}
/** 查询列表 */
const getList = async () => {
loading.value = true
......
<template>
<el-popover placement="right" width="500" trigger="click">
<template #reference>
<el-button link type="primary" @click="getHandler">点击查看</el-button>
</template>
<el-table :data="gridData">
<el-table-column width="150" property="productName" label="品种名称" />
<el-table-column width="150" property="packageName" label="包装信息" />
<el-table-column width="200" property="specification" label="规格" />
</el-table>
</el-popover>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
import {ProductApi} from "@/api/visit/product";
const props = defineProps({
ids: {
type: String,
required: true
}
})
interface Product {
productName: string
packageName: string
specification: string
}
const gridData = ref<Product[]>([])
// 修正5:添加函数返回值类型
const getHandler = async (): Promise<void> => {
try {
gridData.value = await ProductApi.getProductByIds(props.ids) // 显式使用props
console.log(gridData);
} catch (error) {
console.error('获取产品失败:', error)
gridData.value = []
}
}
</script>
......@@ -4,7 +4,7 @@
"useDefineForClassFields": true,
"module": "esnext",
"moduleResolution": "node",
"strict": true,
"strict": false,
"jsx": "preserve",
"sourceMap": true,
"resolveJsonModule": true,
......
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