Compare commits
25 Commits
master
...
dev-common
| Author | SHA1 | Date | |
|---|---|---|---|
| 481f4cdd84 | |||
| f023251e30 | |||
| 30570713bf | |||
| 44e2e40e24 | |||
| 4546481f48 | |||
| 5a1923d9ca | |||
| cb13784141 | |||
| dbc35688e7 | |||
| 20990a419c | |||
| 41d7bd6c86 | |||
| 0432e2430a | |||
| 52f1a1cda2 | |||
| 1a4f106ed7 | |||
| a6e263dd70 | |||
| 87430999e9 | |||
| 21abd429e5 | |||
| bf281ea13e | |||
| 41befdf4da | |||
| 1db391a2f0 | |||
| 78fb7b8eca | |||
| d89670e94a | |||
| 2080f3921c | |||
| 6d706e8961 | |||
| 956834103e | |||
| 1b3863bd7e |
8
.env
8
.env
@@ -1,5 +1,5 @@
|
|||||||
# 标题
|
# 标题
|
||||||
VITE_APP_TITLE=P4数通宝
|
VITE_APP_TITLE=P4数通宝+
|
||||||
|
|
||||||
# 项目本地运行端口号
|
# 项目本地运行端口号
|
||||||
VITE_PORT=6188
|
VITE_PORT=6188
|
||||||
@@ -20,6 +20,6 @@ VITE_APP_DOCALERT_ENABLE=false
|
|||||||
VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc
|
VITE_APP_BAIDU_CODE = a1ff8825baa73c3a78eb96aa40325abc
|
||||||
|
|
||||||
# 默认账户密码
|
# 默认账户密码
|
||||||
VITE_APP_DEFAULT_LOGIN_TENANT = 武汉亚为电子科技有限公司
|
VITE_APP_DEFAULT_LOGIN_TENANT = YAVII
|
||||||
VITE_APP_DEFAULT_LOGIN_USERNAME = admin
|
VITE_APP_DEFAULT_LOGIN_USERNAME = YAVII
|
||||||
VITE_APP_DEFAULT_LOGIN_PASSWORD = 123456
|
VITE_APP_DEFAULT_LOGIN_PASSWORD = yavii123
|
||||||
|
|||||||
58
src/api/aftersale/aftersaleanalysis/index.ts
Normal file
58
src/api/aftersale/aftersaleanalysis/index.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
export interface AnalysisQueryParams {
|
||||||
|
registerCreateTime?: string[]
|
||||||
|
processFinishTime?: string[]
|
||||||
|
visitCreateTime?: string[]
|
||||||
|
pageSize?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DistItem {
|
||||||
|
name: string
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegisterSummary {
|
||||||
|
total: number
|
||||||
|
pending: number
|
||||||
|
approved: number
|
||||||
|
rejected: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProcessSummary {
|
||||||
|
total: number
|
||||||
|
processing: number
|
||||||
|
completed: number
|
||||||
|
failed: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VisitSummary {
|
||||||
|
total: number
|
||||||
|
avgRating: number
|
||||||
|
fiveStar: number
|
||||||
|
highRepurchase: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RegisterAnalysisResponse {
|
||||||
|
registerSummary?: RegisterSummary
|
||||||
|
registerTypeDist?: DistItem[]
|
||||||
|
registerStatusDist?: DistItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProcessAnalysisResponse {
|
||||||
|
processSummary?: ProcessSummary
|
||||||
|
processTypeDist?: DistItem[]
|
||||||
|
processStatusDist?: DistItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface VisitAnalysisResponse {
|
||||||
|
visitSummary?: VisitSummary
|
||||||
|
visitRatingDist?: DistItem[]
|
||||||
|
visitRepurchaseDist?: DistItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AfterSaleAnalysisApi = {
|
||||||
|
getAnalysis: async (params: AnalysisQueryParams) => {
|
||||||
|
return await request.post({ url: '/erp/after-sale/analysis', data: params })
|
||||||
|
}
|
||||||
|
}
|
||||||
57
src/api/aftersale/aftersaleprocess/index.ts
Normal file
57
src/api/aftersale/aftersaleprocess/index.ts
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
/** ERP 售后处理信息 */
|
||||||
|
export interface AfterSaleProcess {
|
||||||
|
id: number; // 处理记录编号
|
||||||
|
afterSaleId?: number; // 关联售后登记编号
|
||||||
|
processType?: number; // 处理类型:1-退货入库,2-换货出库,3-维修处理,4-退款处理,5-取消处理,6-其他
|
||||||
|
processCount?: number; // 处理数量(与售后类型对应:退货数量/换货数量/维修数量等)
|
||||||
|
processResult?: string; // 处理结果描述
|
||||||
|
processEvidence: string; // 处理凭证(图片/文件URL,多个用逗号分隔)
|
||||||
|
relatedOrderId: number; // 关联业务订单编号(如换货对应的新销售订单号、退款对应的财务订单号)
|
||||||
|
processStatus?: number; // 处理状态:1-处理中,2-处理完成,3-处理失败
|
||||||
|
processFailReason: string; // 处理失败原因
|
||||||
|
processUser?: string; // 处理人
|
||||||
|
processTime?: string | Dayjs; // 处理时间
|
||||||
|
finishTime: string | Dayjs; // 处理完成时间
|
||||||
|
userRating?: number; // 用户评价评分(0-10)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ERP 售后处理 API
|
||||||
|
export const AfterSaleProcessApi = {
|
||||||
|
// 查询ERP 售后处理分页
|
||||||
|
getAfterSaleProcessPage: async (params: any) => {
|
||||||
|
return await request.get({ url: `/erp/after-sale-process/page`, params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查询ERP 售后处理详情
|
||||||
|
getAfterSaleProcess: async (id: number) => {
|
||||||
|
return await request.get({ url: `/erp/after-sale-process/get?id=` + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增ERP 售后处理
|
||||||
|
createAfterSaleProcess: async (data: AfterSaleProcess) => {
|
||||||
|
return await request.post({ url: `/erp/after-sale-process/create`, data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改ERP 售后处理
|
||||||
|
updateAfterSaleProcess: async (data: AfterSaleProcess) => {
|
||||||
|
return await request.put({ url: `/erp/after-sale-process/update`, data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除ERP 售后处理
|
||||||
|
deleteAfterSaleProcess: async (id: number) => {
|
||||||
|
return await request.delete({ url: `/erp/after-sale-process/delete?id=` + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 批量删除ERP 售后处理 */
|
||||||
|
deleteAfterSaleProcessList: async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: `/erp/after-sale-process/delete-list?ids=${ids.join(',')}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出ERP 售后处理 Excel
|
||||||
|
exportAfterSaleProcess: async (params) => {
|
||||||
|
return await request.download({ url: `/erp/after-sale-process/export-excel`, params })
|
||||||
|
}
|
||||||
|
}
|
||||||
56
src/api/aftersale/aftersaleregister/index.ts
Normal file
56
src/api/aftersale/aftersaleregister/index.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
/** ERP 售后登记信息 */
|
||||||
|
export interface AfterSaleRegister {
|
||||||
|
id: number; // 售后登记编号
|
||||||
|
orderId?: number; // 关联销售订单编号
|
||||||
|
orderItemId?: number; // 关联销售订单项编号
|
||||||
|
afterSaleType?: number; // 售后类型:1-退货,2-换货,3-维修,4-退款
|
||||||
|
applyReason?: string; // 申请原因
|
||||||
|
contactName?: string; // 联系人
|
||||||
|
contactPhone?: string; // 联系电话
|
||||||
|
applyStatus?: number; // 申请状态:1-待审核,2-审核通过,3-审核驳回,4-已取消
|
||||||
|
rejectReason: string; // 驳回原因(审核驳回时填写)
|
||||||
|
applicant?: string; // 申请人
|
||||||
|
auditUser: string; // 审核人
|
||||||
|
auditTime: string | Dayjs; // 审核时间
|
||||||
|
}
|
||||||
|
|
||||||
|
// ERP 售后登记 API
|
||||||
|
export const AfterSaleRegisterApi = {
|
||||||
|
// 查询ERP 售后登记分页
|
||||||
|
getAfterSaleRegisterPage: async (params: any) => {
|
||||||
|
return await request.get({ url: `/erp/after-sale-register/page`, params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查询ERP 售后登记详情
|
||||||
|
getAfterSaleRegister: async (id: number) => {
|
||||||
|
return await request.get({ url: `/erp/after-sale-register/get?id=` + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增ERP 售后登记
|
||||||
|
createAfterSaleRegister: async (data: AfterSaleRegister) => {
|
||||||
|
return await request.post({ url: `/erp/after-sale-register/create`, data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改ERP 售后登记
|
||||||
|
updateAfterSaleRegister: async (data: AfterSaleRegister) => {
|
||||||
|
return await request.put({ url: `/erp/after-sale-register/update`, data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除ERP 售后登记
|
||||||
|
deleteAfterSaleRegister: async (id: number) => {
|
||||||
|
return await request.delete({ url: `/erp/after-sale-register/delete?id=` + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 批量删除ERP 售后登记 */
|
||||||
|
deleteAfterSaleRegisterList: async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: `/erp/after-sale-register/delete-list?ids=${ids.join(',')}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出ERP 售后登记 Excel
|
||||||
|
exportAfterSaleRegister: async (params) => {
|
||||||
|
return await request.download({ url: `/erp/after-sale-register/export-excel`, params })
|
||||||
|
}
|
||||||
|
}
|
||||||
53
src/api/aftersale/aftersalesvisit/index.ts
Normal file
53
src/api/aftersale/aftersalesvisit/index.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
import type { Dayjs } from 'dayjs';
|
||||||
|
|
||||||
|
/** 售后回访信息 */
|
||||||
|
export interface AfterSalesVisit {
|
||||||
|
id: number; // 主键ID
|
||||||
|
customerName?: string; // 客户名称
|
||||||
|
contactInfo?: string; // 联系方式
|
||||||
|
customerUsage: string; // 客户用途
|
||||||
|
productName?: string; // 产品名称
|
||||||
|
productEvaluation: string; // 产品评价
|
||||||
|
customerType?: string; // 客户类型:大型食品厂/中小型加工厂/经销商/餐饮连锁/机关单位/其他
|
||||||
|
repurchaseIntention?: string; // 重复采购意愿:一定会/应该会/不确定/可能不会/肯定不会
|
||||||
|
serviceRating?: number; // 服务评分(0~5星)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 售后回访 API
|
||||||
|
export const AfterSalesVisitApi = {
|
||||||
|
// 查询售后回访分页
|
||||||
|
getAfterSalesVisitPage: async (params: any) => {
|
||||||
|
return await request.get({ url: `/erp/after-sales-visit/page`, params })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查询售后回访详情
|
||||||
|
getAfterSalesVisit: async (id: number) => {
|
||||||
|
return await request.get({ url: `/erp/after-sales-visit/get?id=` + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 新增售后回访
|
||||||
|
createAfterSalesVisit: async (data: AfterSalesVisit) => {
|
||||||
|
return await request.post({ url: `/erp/after-sales-visit/create`, data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 修改售后回访
|
||||||
|
updateAfterSalesVisit: async (data: AfterSalesVisit) => {
|
||||||
|
return await request.put({ url: `/erp/after-sales-visit/update`, data })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 删除售后回访
|
||||||
|
deleteAfterSalesVisit: async (id: number) => {
|
||||||
|
return await request.delete({ url: `/erp/after-sales-visit/delete?id=` + id })
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 批量删除售后回访 */
|
||||||
|
deleteAfterSalesVisitList: async (ids: number[]) => {
|
||||||
|
return await request.delete({ url: `/erp/after-sales-visit/delete-list?ids=${ids.join(',')}` })
|
||||||
|
},
|
||||||
|
|
||||||
|
// 导出售后回访 Excel
|
||||||
|
exportAfterSalesVisit: async (params) => {
|
||||||
|
return await request.download({ url: `/erp/after-sales-visit/export-excel`, params })
|
||||||
|
}
|
||||||
|
}
|
||||||
102
src/api/erp/sale/exchange/index.ts
Normal file
102
src/api/erp/sale/exchange/index.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import request from '@/config/axios'
|
||||||
|
|
||||||
|
// ERP 销售换货 VO
|
||||||
|
export interface SaleExchangeVO {
|
||||||
|
id: number // 换货编号
|
||||||
|
no: string // 换货单号
|
||||||
|
exchangeTime: Date // 换货时间
|
||||||
|
outId: number // 关联出库单编号
|
||||||
|
outNo: string // 关联出库单号
|
||||||
|
customerId: number // 客户编号
|
||||||
|
customerName: string // 客户名称
|
||||||
|
saleUserId: number // 销售人员编号
|
||||||
|
saleUserName: string // 销售人员名称
|
||||||
|
accountId: number // 结算账户编号
|
||||||
|
totalOutCount: number // 换出商品总数
|
||||||
|
totalInCount: number // 换入商品总数
|
||||||
|
totalPrice: number // 差价金额,单位:元
|
||||||
|
otherPrice: number // 其它费用,单位:元
|
||||||
|
status: number // 状态
|
||||||
|
remark: string // 备注
|
||||||
|
fileUrl: string // 附件地址
|
||||||
|
outItems: SaleExchangeOutItemVO[] // 换出商品明细
|
||||||
|
inItems: SaleExchangeInItemVO[] // 换入商品明细
|
||||||
|
}
|
||||||
|
|
||||||
|
// ERP 销售换货换出商品明细 VO
|
||||||
|
export interface SaleExchangeOutItemVO {
|
||||||
|
id: number // 明细编号
|
||||||
|
exchangeId: number // 换货编号
|
||||||
|
productId: number // 产品编号
|
||||||
|
productName: string // 产品名称
|
||||||
|
productBarCode: string // 产品条码
|
||||||
|
productUnitId: number // 产品单位编号
|
||||||
|
productUnitName: string // 产品单位名称
|
||||||
|
warehouseId: number // 仓库编号
|
||||||
|
warehouseName: string // 仓库名称
|
||||||
|
count: number // 换出数量
|
||||||
|
productPrice: number // 产品单价,单位:元
|
||||||
|
totalPrice: number // 合计金额,单位:元
|
||||||
|
}
|
||||||
|
|
||||||
|
// ERP 销售换货换入商品明细 VO
|
||||||
|
export interface SaleExchangeInItemVO {
|
||||||
|
id: number // 明细编号
|
||||||
|
exchangeId: number // 换货编号
|
||||||
|
productId: number // 产品编号
|
||||||
|
productName: string // 产品名称
|
||||||
|
productBarCode: string // 产品条码
|
||||||
|
productUnitId: number // 产品单位编号
|
||||||
|
productUnitName: string // 产品单位名称
|
||||||
|
warehouseId: number // 仓库编号
|
||||||
|
warehouseName: string // 仓库名称
|
||||||
|
count: number // 换入数量
|
||||||
|
productPrice: number // 产品单价,单位:元
|
||||||
|
totalPrice: number // 合计金额,单位:元
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询销售换货分页
|
||||||
|
export const getSaleExchangePage = async (params: any) => {
|
||||||
|
return await request.get({ url: `/erp/sale-exchange/page`, params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询销售换货详情
|
||||||
|
export const getSaleExchange = async (id: number) => {
|
||||||
|
return await request.get({ url: `/erp/sale-exchange/get?id=` + id })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增销售换货
|
||||||
|
export const createSaleExchange = async (data: SaleExchangeVO) => {
|
||||||
|
return await request.post({ url: `/erp/sale-exchange/create`, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改销售换货
|
||||||
|
export const updateSaleExchange = async (data: SaleExchangeVO) => {
|
||||||
|
return await request.put({ url: `/erp/sale-exchange/update`, data })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新销售换货的状态
|
||||||
|
export const updateSaleExchangeStatus = async (id: number, status: number) => {
|
||||||
|
return await request.put({
|
||||||
|
url: `/erp/sale-exchange/update-status`,
|
||||||
|
params: {
|
||||||
|
id,
|
||||||
|
status
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除销售换货
|
||||||
|
export const deleteSaleExchange = async (ids: number[]) => {
|
||||||
|
return await request.delete({
|
||||||
|
url: `/erp/sale-exchange/delete`,
|
||||||
|
params: {
|
||||||
|
ids: ids.join(',')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出销售换货 Excel
|
||||||
|
export const exportSaleExchange = async (params: any) => {
|
||||||
|
return await request.download({ url: `/erp/sale-exchange/export-excel`, params })
|
||||||
|
}
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!-- 移动端布局 -->
|
||||||
<el-drawer
|
<el-drawer
|
||||||
|
v-if="isMobile"
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="审批流程"
|
title="审批流程"
|
||||||
direction="rtl"
|
direction="rtl"
|
||||||
@@ -65,23 +67,48 @@
|
|||||||
<el-button @click="dialogVisible = false">关闭</el-button>
|
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<el-dialog v-else v-model="dialogVisible" title="审批流程" width="700px" :close-on-press-escape="true" :destroy-on-close="true">
|
||||||
|
<el-table v-loading="loading" :data="approvalRecords" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
<el-table-column label="审批层级" align="center" width="100"><template #default="scope">{{ getLevelText(scope.row.approvalLevel) }}</template></el-table-column>
|
||||||
|
<el-table-column label="申请人" align="center" prop="applicantName" width="100"><template #default="scope">{{ scope.row.applicantName || scope.row.applicant }}</template></el-table-column>
|
||||||
|
<el-table-column label="审批人" align="center" prop="approverName" width="100"><template #default="scope">{{ scope.row.approverName || scope.row.approver }}</template></el-table-column>
|
||||||
|
<el-table-column label="审批结果" align="center" width="90"><template #default="scope"><el-tag :type="getStatusTagType(scope.row.approvalResult)" size="small">{{ getApprovalResultText(scope.row.approvalResult) }}</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="流程状态" align="center" width="90"><template #default="scope"><el-tag :type="getProcessStatusTagType(scope.row.processStatus)" size="small">{{ getProcessStatusText(scope.row.processStatus) }}</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="审批时间" align="center" prop="approvalTime" width="160"><template #default="scope">{{ scope.row.approvalTime ? formatDate(scope.row.approvalTime) : '-' }}</template></el-table-column>
|
||||||
|
<el-table-column label="审批意见" align="center" prop="comment" min-width="150" />
|
||||||
|
</el-table>
|
||||||
|
<el-empty v-if="approvalRecords.length === 0" description="暂无审批记录" />
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { ApprovalRecordApi } from '@/api/erp/approval'
|
import { ApprovalRecordApi } from '@/api/erp/approval'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
|
const loading = ref(false)
|
||||||
const approvalRecords = ref<any[]>([])
|
const approvalRecords = ref<any[]>([])
|
||||||
|
|
||||||
const open = async (bizId: string, bizTableName: string) => {
|
const open = async (bizId: string, bizTableName: string) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const data = await ApprovalRecordApi.getApprovalRecordListByBiz(bizId, bizTableName)
|
const data = await ApprovalRecordApi.getApprovalRecordListByBiz(bizId, bizTableName)
|
||||||
approvalRecords.value = data
|
approvalRecords.value = data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取审批记录失败', error)
|
console.error('获取审批记录失败', error)
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!-- 移动端布局 -->
|
||||||
<el-drawer
|
<el-drawer
|
||||||
|
v-if="isMobile"
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="处理审批"
|
title="处理审批"
|
||||||
direction="rtl"
|
direction="rtl"
|
||||||
@@ -113,6 +115,52 @@
|
|||||||
<el-button type="primary" @click="submitForm" :loading="loading">确定</el-button>
|
<el-button type="primary" @click="submitForm" :loading="loading">确定</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<el-dialog v-else v-model="dialogVisible" title="处理审批" width="600px" :close-on-press-escape="true" :destroy-on-close="true">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
|
||||||
|
<el-form-item label="审批记录" prop="approvalRecordId">
|
||||||
|
<el-select v-model="formData.approvalRecordId" placeholder="请选择待处理的审批记录" filterable clearable style="width: 100%" @change="handleRecordChange">
|
||||||
|
<el-option v-for="record in pendingRecords" :key="record.id" :label="`第${record.approvalLevel}级审批 - ${record.applicantName || record.applicant}`" :value="record.id">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center"><span>第{{ record.approvalLevel }}级审批</span><el-tag size="small" type="warning">待处理</el-tag></div>
|
||||||
|
</el-option>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="审批结果" prop="approvalResult">
|
||||||
|
<el-radio-group v-model="formData.approvalResult">
|
||||||
|
<el-radio :label="1">通过</el-radio>
|
||||||
|
<el-radio :label="2">驳回</el-radio>
|
||||||
|
<el-radio :label="3">转审</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="下一级审批人" prop="nextApprover" v-if="formData.approvalResult === 1 || formData.approvalResult === 3">
|
||||||
|
<el-select v-model="formData.nextApprover" placeholder="请选择下一级审批人" filterable clearable style="width: 100%">
|
||||||
|
<el-option v-for="user in userList" :key="user.id" :label="user.nickname" :value="String(user.id)" />
|
||||||
|
</el-select>
|
||||||
|
<div style="color: #909399; font-size: 12px; margin-top: 4px">{{ formData.approvalResult === 1 ? '通过时可指定下一级审批人,不指定则流程结束' : '转审时必须指定审批人' }}</div>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="指定原因" prop="assignReason" v-if="formData.approvalResult === 3">
|
||||||
|
<el-input v-model="formData.assignReason" type="textarea" :rows="2" placeholder="请输入转审原因" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="审批意见" prop="comment">
|
||||||
|
<el-input v-model="formData.comment" type="textarea" :rows="3" placeholder="请输入审批意见" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<!-- 审批信息卡片 -->
|
||||||
|
<div class="pc-process-info" v-if="selectedRecord">
|
||||||
|
<div class="pc-process-info__title">审批信息</div>
|
||||||
|
<el-descriptions :column="2" border size="small">
|
||||||
|
<el-descriptions-item label="申请人">{{ selectedRecord.applicantName || selectedRecord.applicant }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="当前审批人">{{ selectedRecord.approverName || selectedRecord.approver }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="审批层级">第{{ selectedRecord.approvalLevel }}级</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="提交时间">{{ formatDate(selectedRecord.createTime) }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm" :loading="loading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
@@ -122,6 +170,10 @@ import * as UserApi from '@/api/system/user'
|
|||||||
import type { UserVO } from '@/api/system/user'
|
import type { UserVO } from '@/api/system/user'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@@ -299,4 +351,17 @@ defineExpose({ open })
|
|||||||
height: auto;
|
height: auto;
|
||||||
padding: 8px 20px;
|
padding: 8px 20px;
|
||||||
}
|
}
|
||||||
|
.pc-process-info {
|
||||||
|
background: #f0f9eb;
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 14px;
|
||||||
|
border: 1px solid #e1f3d8;
|
||||||
|
margin-top: 16px;
|
||||||
|
&__title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!-- 移动端布局 -->
|
||||||
<el-drawer
|
<el-drawer
|
||||||
|
v-if="isMobile"
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="提交审批"
|
title="提交审批"
|
||||||
direction="rtl"
|
direction="rtl"
|
||||||
@@ -40,13 +42,35 @@
|
|||||||
<el-button type="primary" @click="submitForm" :loading="loading">确定</el-button>
|
<el-button type="primary" @click="submitForm" :loading="loading">确定</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<el-dialog v-else v-model="dialogVisible" title="提交审批" width="500px" :close-on-press-escape="true" :destroy-on-close="true">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
|
||||||
|
<el-form-item label="下一级审批人" prop="nextApprover">
|
||||||
|
<el-select v-model="formData.nextApprover" placeholder="请选择下一级审批人" filterable clearable style="width: 100%">
|
||||||
|
<el-option v-for="user in userList" :key="user.id" :label="user.nickname" :value="String(user.id)" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input v-model="formData.remark" type="textarea" :rows="4" placeholder="请输入备注信息" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm" :loading="loading">确定</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive } from 'vue'
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { ApprovalRecordApi } from '@/api/erp/approval'
|
import { ApprovalRecordApi } from '@/api/erp/approval'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import type { UserVO } from '@/api/system/user'
|
import type { UserVO } from '@/api/system/user'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
<el-avatar :size="60">
|
<el-avatar :size="60">
|
||||||
<Icon icon="ep:avatar" :size="60" />
|
<Icon icon="ep:avatar" :size="60" />
|
||||||
</el-avatar>
|
</el-avatar>
|
||||||
<span class="text-18px font-bold">P4数通宝</span>
|
<span class="text-18px font-bold">P4数通宝+</span>
|
||||||
</div>
|
</div>
|
||||||
<Icon icon="tdesign:qrcode" :size="20" />
|
<Icon icon="tdesign:qrcode" :size="20" />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
258
src/components/DynamicForm/README.md
Normal file
258
src/components/DynamicForm/README.md
Normal file
@@ -0,0 +1,258 @@
|
|||||||
|
# DynamicForm 动态表单组件
|
||||||
|
|
||||||
|
一个通用的动态表单组件,支持多种输入类型和灵活的配置。
|
||||||
|
|
||||||
|
## 功能特性
|
||||||
|
|
||||||
|
- 🎯 **多种输入类型**:支持数字、文本、选择器、日期、开关、单选框、复选框、滑块等
|
||||||
|
- 🔧 **灵活配置**:支持自定义验证规则、占位符、禁用状态等
|
||||||
|
- 📱 **响应式布局**:支持自定义列数和间距
|
||||||
|
- 🎨 **自定义插槽**:支持自定义输入组件
|
||||||
|
- 📊 **数据绑定**:支持 v-model 双向绑定
|
||||||
|
- 🔍 **类型安全**:完整的 TypeScript 类型定义
|
||||||
|
|
||||||
|
## 基础用法
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<DynamicForm
|
||||||
|
v-model="formData"
|
||||||
|
:items="formItems"
|
||||||
|
title="用户信息"
|
||||||
|
@form-change="handleFormChange"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import DynamicForm, {
|
||||||
|
createNumberItem,
|
||||||
|
createTextItem,
|
||||||
|
createSelectItem
|
||||||
|
} from '@/components/DynamicForm'
|
||||||
|
|
||||||
|
const formData = ref({})
|
||||||
|
|
||||||
|
const formItems = [
|
||||||
|
createTextItem('name', '姓名', { required: true }),
|
||||||
|
createNumberItem('age', '年龄', { min: 0, max: 120 }),
|
||||||
|
createSelectItem('gender', '性别', [
|
||||||
|
{ label: '男', value: 'male' },
|
||||||
|
{ label: '女', value: 'female' }
|
||||||
|
])
|
||||||
|
]
|
||||||
|
|
||||||
|
const handleFormChange = (value) => {
|
||||||
|
console.log('表单数据变化:', value)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 支持的输入类型
|
||||||
|
|
||||||
|
### 1. 数字输入 (number)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
createNumberItem('price', '价格', {
|
||||||
|
min: 0,
|
||||||
|
max: 10000,
|
||||||
|
precision: 2,
|
||||||
|
step: 0.01,
|
||||||
|
placeholder: '请输入价格'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 文本输入 (text/textarea)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 单行文本
|
||||||
|
createTextItem('name', '姓名', {
|
||||||
|
placeholder: '请输入姓名',
|
||||||
|
required: true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 多行文本
|
||||||
|
createTextItem('description', '描述', {
|
||||||
|
rows: 4,
|
||||||
|
placeholder: '请输入描述'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 选择器 (select)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
createSelectItem('status', '状态', [
|
||||||
|
{ label: '启用', value: 'enabled' },
|
||||||
|
{ label: '禁用', value: 'disabled' }
|
||||||
|
], {
|
||||||
|
placeholder: '请选择状态',
|
||||||
|
filterable: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. 日期选择 (date)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
createDateItem('birthday', '生日', {
|
||||||
|
dateType: 'date',
|
||||||
|
format: 'YYYY-MM-DD',
|
||||||
|
valueFormat: 'YYYY-MM-DD'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. 开关 (switch)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
createSwitchItem('enabled', '启用状态', {
|
||||||
|
activeText: '启用',
|
||||||
|
inactiveText: '禁用'
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. 单选框组 (radio)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
createRadioItem('level', '等级', [
|
||||||
|
{ label: '初级', value: 'beginner' },
|
||||||
|
{ label: '中级', value: 'intermediate' },
|
||||||
|
{ label: '高级', value: 'advanced' }
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. 复选框组 (checkbox)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
createCheckboxItem('hobbies', '爱好', [
|
||||||
|
{ label: '阅读', value: 'reading' },
|
||||||
|
{ label: '运动', value: 'sports' },
|
||||||
|
{ label: '音乐', value: 'music' }
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
### 8. 滑块 (slider)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
createSliderItem('volume', '音量', {
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
step: 1,
|
||||||
|
showInput: true
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Props
|
||||||
|
|
||||||
|
| 参数 | 说明 | 类型 | 默认值 |
|
||||||
|
|------|------|------|--------|
|
||||||
|
| modelValue | 表单数据 | `Record<string, any>` | `{}` |
|
||||||
|
| items | 表单项配置 | `DynamicFormItem[]` | `[]` |
|
||||||
|
| title | 表单标题 | `string` | `''` |
|
||||||
|
| gutter | 列间距 | `number` | `16` |
|
||||||
|
| columnSpan | 列跨度 | `number` | `12` |
|
||||||
|
| formProp | 表单属性前缀 | `string` | `''` |
|
||||||
|
|
||||||
|
## Events
|
||||||
|
|
||||||
|
| 事件名 | 说明 | 参数 |
|
||||||
|
|--------|------|------|
|
||||||
|
| update:modelValue | 表单数据更新 | `(value: Record<string, any>)` |
|
||||||
|
| fieldChange | 单个字段变化 | `(key: string, value: any, item: DynamicFormItem)` |
|
||||||
|
| formChange | 整个表单变化 | `(value: Record<string, any>)` |
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
| 方法名 | 说明 | 参数 | 返回值 |
|
||||||
|
|--------|------|------|--------|
|
||||||
|
| getFormData | 获取表单数据 | - | `Record<string, any>` |
|
||||||
|
| setFormData | 设置表单数据 | `data: Record<string, any>` | - |
|
||||||
|
| resetForm | 重置表单 | - | - |
|
||||||
|
| validateForm | 验证表单 | - | `boolean` |
|
||||||
|
|
||||||
|
## 自定义插槽
|
||||||
|
|
||||||
|
```vue
|
||||||
|
<template>
|
||||||
|
<DynamicForm v-model="formData" :items="formItems">
|
||||||
|
<template #customField="{ item, value, onChange }">
|
||||||
|
<el-input
|
||||||
|
v-model="value"
|
||||||
|
@input="onChange"
|
||||||
|
:placeholder="item.placeholder"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</DynamicForm>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
const formItems = [
|
||||||
|
{
|
||||||
|
key: 'customField',
|
||||||
|
label: '自定义字段',
|
||||||
|
type: 'custom',
|
||||||
|
slotName: 'customField',
|
||||||
|
placeholder: '请输入自定义内容'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 高级用法
|
||||||
|
|
||||||
|
### 条件显示
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const formItems = computed(() => {
|
||||||
|
const items = [
|
||||||
|
createSelectItem('type', '类型', [
|
||||||
|
{ label: '类型A', value: 'typeA' },
|
||||||
|
{ label: '类型B', value: 'typeB' }
|
||||||
|
])
|
||||||
|
]
|
||||||
|
|
||||||
|
// 根据类型动态添加字段
|
||||||
|
if (formData.value.type === 'typeA') {
|
||||||
|
items.push(createTextItem('fieldA', '字段A'))
|
||||||
|
} else if (formData.value.type === 'typeB') {
|
||||||
|
items.push(createNumberItem('fieldB', '字段B'))
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 表单验证
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const formItems = [
|
||||||
|
createTextItem('email', '邮箱', {
|
||||||
|
required: true,
|
||||||
|
rules: [
|
||||||
|
{ required: true, message: '请输入邮箱', trigger: 'blur' },
|
||||||
|
{ type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 类型定义
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface DynamicFormItem {
|
||||||
|
key: string
|
||||||
|
label: string
|
||||||
|
type: 'number' | 'text' | 'textarea' | 'select' | 'date' | 'time' | 'switch' | 'radio' | 'checkbox' | 'slider' | 'custom'
|
||||||
|
placeholder?: string
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
clearable?: boolean
|
||||||
|
rules?: any[]
|
||||||
|
// ... 更多配置项
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. 每个表单项的 `key` 必须唯一
|
||||||
|
2. 选择器类型的表单项必须提供 `options` 配置
|
||||||
|
3. 自定义插槽类型需要指定 `slotName`
|
||||||
|
4. 表单数据会自动同步到 `v-model` 绑定的变量
|
||||||
|
5. 建议使用工具函数创建表单项,确保类型安全
|
||||||
48
src/components/DynamicForm/index.ts
Normal file
48
src/components/DynamicForm/index.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import DynamicForm from './index.vue'
|
||||||
|
import type {
|
||||||
|
DynamicFormItem,
|
||||||
|
DynamicFormConfig,
|
||||||
|
DynamicFormEvents,
|
||||||
|
DynamicFormMethods
|
||||||
|
} from './types'
|
||||||
|
import {
|
||||||
|
createNumberItem,
|
||||||
|
createTextItem,
|
||||||
|
createSelectItem,
|
||||||
|
createDateItem,
|
||||||
|
createSwitchItem,
|
||||||
|
createRadioItem,
|
||||||
|
createCheckboxItem,
|
||||||
|
createSliderItem,
|
||||||
|
createCustomItem,
|
||||||
|
createDynamicFormConfig,
|
||||||
|
validateFormItem,
|
||||||
|
validateFormConfig
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
// 导出组件
|
||||||
|
export default DynamicForm
|
||||||
|
|
||||||
|
// 导出类型
|
||||||
|
export type {
|
||||||
|
DynamicFormItem,
|
||||||
|
DynamicFormConfig,
|
||||||
|
DynamicFormEvents,
|
||||||
|
DynamicFormMethods
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出工具函数
|
||||||
|
export {
|
||||||
|
createNumberItem,
|
||||||
|
createTextItem,
|
||||||
|
createSelectItem,
|
||||||
|
createDateItem,
|
||||||
|
createSwitchItem,
|
||||||
|
createRadioItem,
|
||||||
|
createCheckboxItem,
|
||||||
|
createSliderItem,
|
||||||
|
createCustomItem,
|
||||||
|
createDynamicFormConfig,
|
||||||
|
validateFormItem,
|
||||||
|
validateFormConfig
|
||||||
|
}
|
||||||
322
src/components/DynamicForm/index.vue
Normal file
322
src/components/DynamicForm/index.vue
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
<template>
|
||||||
|
<div class="dynamic-form">
|
||||||
|
<!-- 表单标题 -->
|
||||||
|
<el-divider v-if="title" content-position="left">{{ title }}</el-divider>
|
||||||
|
|
||||||
|
<!-- 动态表单项目 -->
|
||||||
|
<el-row :gutter="gutter">
|
||||||
|
<el-col v-for="item in formItems" :key="item.key" :span="getColumnSpan(item)">
|
||||||
|
<el-form-item :label="item.label" :required="item.required">
|
||||||
|
<div class="form-field-container">
|
||||||
|
<!-- 输入控件区域 -->
|
||||||
|
<div class="form-field-input" :class="{ 'with-standard': item.showStandard && item.standard }">
|
||||||
|
<!-- 数字输入 -->
|
||||||
|
<el-input-number v-if="item.type === 'number'" v-model="formData[item.key]" :min="item.min"
|
||||||
|
:max="item.max" :precision="item.precision || 0" :step="item.step || 1" :placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled" :clearable="item.clearable !== false" style="width: 100%"
|
||||||
|
@change="handleFieldChange(item.key, $event)" />
|
||||||
|
|
||||||
|
<!-- 文本输入 -->
|
||||||
|
<el-input v-else-if="item.type === 'text'" v-model="formData[item.key]" :type="item.inputType || 'text'"
|
||||||
|
:placeholder="item.placeholder" :disabled="item.disabled" :clearable="item.clearable !== false"
|
||||||
|
:rows="item.rows || 1" :maxlength="item.maxlength" :show-word-limit="!!item.maxlength"
|
||||||
|
@input="handleFieldChange(item.key, $event)" />
|
||||||
|
|
||||||
|
<!-- 多行文本 -->
|
||||||
|
<el-input v-else-if="item.type === 'textarea'" v-model="formData[item.key]" type="textarea"
|
||||||
|
:placeholder="item.placeholder" :disabled="item.disabled" :clearable="item.clearable !== false"
|
||||||
|
:rows="item.rows || 3" @input="handleFieldChange(item.key, $event)" />
|
||||||
|
|
||||||
|
<!-- 选择器 -->
|
||||||
|
<el-select v-else-if="item.type === 'select'" v-model="formData[item.key]" :placeholder="item.placeholder"
|
||||||
|
:disabled="item.disabled" :clearable="item.clearable !== false" :filterable="item.filterable"
|
||||||
|
:multiple="item.multiple" style="width: 100%" @change="handleFieldChange(item.key, $event)">
|
||||||
|
<el-option v-for="option in item.options" :key="option.value" :label="option.label"
|
||||||
|
:value="option.value" :disabled="option.disabled" />
|
||||||
|
</el-select>
|
||||||
|
|
||||||
|
<!-- 日期选择 -->
|
||||||
|
<el-date-picker v-else-if="item.type === 'date'" v-model="formData[item.key]"
|
||||||
|
:type="item.dateType || 'date'" :placeholder="item.placeholder" :disabled="item.disabled"
|
||||||
|
:clearable="item.clearable !== false" :format="item.format" :value-format="item.valueFormat"
|
||||||
|
style="width: 100%" @change="handleFieldChange(item.key, $event)" />
|
||||||
|
|
||||||
|
<!-- 时间选择 -->
|
||||||
|
<el-time-picker v-else-if="item.type === 'time'" v-model="formData[item.key]"
|
||||||
|
:placeholder="item.placeholder" :disabled="item.disabled" :clearable="item.clearable !== false"
|
||||||
|
:format="item.format || 'HH:mm:ss'" :value-format="item.valueFormat || 'HH:mm:ss'" style="width: 100%"
|
||||||
|
@change="handleFieldChange(item.key, $event)" />
|
||||||
|
|
||||||
|
<!-- 开关 -->
|
||||||
|
<el-switch v-else-if="item.type === 'switch'" v-model="formData[item.key]" :disabled="item.disabled"
|
||||||
|
:active-text="item.activeText" :inactive-text="item.inactiveText"
|
||||||
|
@change="handleFieldChange(item.key, $event)" />
|
||||||
|
|
||||||
|
<!-- 单选框组 -->
|
||||||
|
<el-radio-group v-else-if="item.type === 'radio'" v-model="formData[item.key]" :disabled="item.disabled"
|
||||||
|
@change="handleFieldChange(item.key, $event)">
|
||||||
|
<el-radio v-for="option in item.options" :key="option.value" :value="option.value"
|
||||||
|
:disabled="option.disabled">
|
||||||
|
{{ option.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
|
||||||
|
<!-- 复选框组 -->
|
||||||
|
<el-checkbox-group v-else-if="item.type === 'checkbox'" v-model="formData[item.key]"
|
||||||
|
:disabled="item.disabled" @change="handleFieldChange(item.key, $event)">
|
||||||
|
<el-checkbox v-for="option in item.options" :key="option.value" :value="option.value"
|
||||||
|
:disabled="option.disabled">
|
||||||
|
{{ option.label }}
|
||||||
|
</el-checkbox>
|
||||||
|
</el-checkbox-group>
|
||||||
|
|
||||||
|
<!-- 滑块 -->
|
||||||
|
<el-slider v-else-if="item.type === 'slider'" v-model="formData[item.key]" :min="item.min || 0"
|
||||||
|
:max="item.max || 100" :step="item.step || 1" :disabled="item.disabled" :show-input="item.showInput"
|
||||||
|
:show-stops="item.showStops" :show-tooltip="item.showTooltip"
|
||||||
|
@change="handleFieldChange(item.key, $event)" />
|
||||||
|
|
||||||
|
<!-- 自定义插槽 -->
|
||||||
|
<slot v-else-if="item.type === 'custom'" :name="item.slotName || item.key" :item="item"
|
||||||
|
:value="formData[item.key]" :onChange="(value) => handleFieldChange(item.key, value)"></slot>
|
||||||
|
|
||||||
|
<!-- 未知类型提示 -->
|
||||||
|
<div v-else class="unknown-type">
|
||||||
|
<el-alert :title="`未知的表单类型: ${item.type}`" type="warning" :closable="false" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 质检标准显示区域 -->
|
||||||
|
<div
|
||||||
|
v-if="item.showStandard && (item.standard || item.standardValue || item.standardRange || item.standardOptions?.length)"
|
||||||
|
class="form-field-standard">
|
||||||
|
<el-tooltip :content="getStandardTooltip(item)" placement="top">
|
||||||
|
<div class="standard-display">
|
||||||
|
<Icon icon="ep:info-filled" class="standard-icon" />
|
||||||
|
<span class="standard-text">{{ getStandardDisplayText(item) }}</span>
|
||||||
|
</div>
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, computed } from 'vue'
|
||||||
|
import type { DynamicFormItem } from './types'
|
||||||
|
|
||||||
|
// 定义Props
|
||||||
|
interface Props {
|
||||||
|
modelValue?: Record<string, any>
|
||||||
|
items?: DynamicFormItem[]
|
||||||
|
title?: string
|
||||||
|
gutter?: number
|
||||||
|
columnSpan?: number
|
||||||
|
formProp?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义Emits
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: Record<string, any>): void
|
||||||
|
(e: 'fieldChange', key: string, value: any, item: DynamicFormItem): void
|
||||||
|
(e: 'formChange', value: Record<string, any>): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
modelValue: () => ({}),
|
||||||
|
items: () => [],
|
||||||
|
title: '',
|
||||||
|
gutter: 16,
|
||||||
|
columnSpan: 12,
|
||||||
|
formProp: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formData = ref<Record<string, any>>({ ...props.modelValue })
|
||||||
|
|
||||||
|
// 计算属性
|
||||||
|
const formItems = computed(() => props.items)
|
||||||
|
|
||||||
|
// 获取列跨度
|
||||||
|
const getColumnSpan = (item: DynamicFormItem) => {
|
||||||
|
if (item.fullWidth) return 24
|
||||||
|
return item.span || props.columnSpan
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 处理字段变化
|
||||||
|
const handleFieldChange = (key: string, value: any) => {
|
||||||
|
formData.value[key] = value
|
||||||
|
|
||||||
|
// 查找对应的表单项配置
|
||||||
|
const item = formItems.value.find(item => item.key === key)
|
||||||
|
|
||||||
|
// 触发事件
|
||||||
|
emit('fieldChange', key, value, item!)
|
||||||
|
emit('update:modelValue', { ...formData.value })
|
||||||
|
emit('formChange', { ...formData.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取标准显示文本(只显示标准值)
|
||||||
|
const getStandardDisplayText = (item: DynamicFormItem): string => {
|
||||||
|
// 如果没有标准信息,不显示
|
||||||
|
if (!item.standardValue && !item.standardRange && !item.standardOptions?.length) {
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据标准类型显示标准值
|
||||||
|
switch (item.standardType) {
|
||||||
|
case 'text':
|
||||||
|
return item.standardValue || ''
|
||||||
|
case 'number':
|
||||||
|
return item.standardValue !== undefined ? String(item.standardValue) : ''
|
||||||
|
case 'range':
|
||||||
|
if (item.standardRange) {
|
||||||
|
const { min, max } = item.standardRange
|
||||||
|
// 如果只有最小值(max为null、undefined或0)
|
||||||
|
if (min !== undefined && min !== null && (max === undefined || max === null || max === 0)) {
|
||||||
|
return `≥${min}`
|
||||||
|
}
|
||||||
|
// 如果只有最大值(min为null、undefined或0)
|
||||||
|
if ((min === undefined || min === null || min === 0) && max !== undefined && max !== null) {
|
||||||
|
return `≤${max}`
|
||||||
|
}
|
||||||
|
// 如果有范围(两个值都有且不为null/undefined/0)
|
||||||
|
if (min !== undefined && min !== null && max !== undefined && max !== null && min !== 0 && max !== 0) {
|
||||||
|
return `${min}~${max}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
case 'select':
|
||||||
|
return item.standardOptions?.length ? item.standardOptions.map(opt => opt.label).join(', ') : ''
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取标准提示内容(只显示标准描述)
|
||||||
|
const getStandardTooltip = (item: DynamicFormItem): string => {
|
||||||
|
// 只显示标准描述
|
||||||
|
return item.standard || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听外部数据变化
|
||||||
|
watch(() => props.modelValue, (newValue) => {
|
||||||
|
formData.value = { ...newValue }
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
// 暴露方法
|
||||||
|
const getFormData = () => {
|
||||||
|
return { ...formData.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFormData = (data: Record<string, any>) => {
|
||||||
|
formData.value = { ...data }
|
||||||
|
emit('update:modelValue', { ...formData.value })
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {}
|
||||||
|
emit('update:modelValue', {})
|
||||||
|
}
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
// 这里可以添加表单验证逻辑
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getFormData,
|
||||||
|
setFormData,
|
||||||
|
resetForm,
|
||||||
|
validateForm
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.dynamic-form {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.unknown-type {
|
||||||
|
margin: 8px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field-input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field-input.with-standard {
|
||||||
|
flex: 0 0 calc(100% - 200px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field-standard {
|
||||||
|
flex: 0 0 180px;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.standard-display {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
padding: 6px 12px;
|
||||||
|
background: #f0f9ff;
|
||||||
|
border: 1px solid #bae6fd;
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.standard-display:hover {
|
||||||
|
background: #e0f2fe;
|
||||||
|
border-color: #7dd3fc;
|
||||||
|
}
|
||||||
|
|
||||||
|
.standard-icon {
|
||||||
|
color: #0ea5e9;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.standard-text {
|
||||||
|
color: #0369a1;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.4;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式设计 */
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.form-field-container {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field-input.with-standard {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field-standard {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
83
src/components/DynamicForm/types.ts
Normal file
83
src/components/DynamicForm/types.ts
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// 动态表单项目接口
|
||||||
|
export interface DynamicFormItem {
|
||||||
|
key: string
|
||||||
|
label: string
|
||||||
|
type: 'number' | 'text' | 'textarea' | 'select' | 'date' | 'time' | 'switch' | 'radio' | 'checkbox' | 'slider' | 'custom'
|
||||||
|
placeholder?: string
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
clearable?: boolean
|
||||||
|
rules?: any[]
|
||||||
|
defaultValue?: any
|
||||||
|
|
||||||
|
// 质检标准相关
|
||||||
|
standard?: string
|
||||||
|
standardType?: 'text' | 'number' | 'range' | 'select'
|
||||||
|
standardValue?: any
|
||||||
|
standardRange?: { min: number; max: number }
|
||||||
|
standardOptions?: Array<{ label: string; value: any; disabled?: boolean }>
|
||||||
|
showStandard?: boolean
|
||||||
|
|
||||||
|
// 数字输入相关
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
precision?: number
|
||||||
|
step?: number
|
||||||
|
|
||||||
|
// 文本输入相关
|
||||||
|
inputType?: string
|
||||||
|
rows?: number
|
||||||
|
|
||||||
|
// 选择器相关
|
||||||
|
options?: Array<{
|
||||||
|
label: string
|
||||||
|
value: any
|
||||||
|
disabled?: boolean
|
||||||
|
}>
|
||||||
|
filterable?: boolean
|
||||||
|
multiple?: boolean
|
||||||
|
|
||||||
|
// 日期时间相关
|
||||||
|
dateType?: 'date' | 'datetime' | 'daterange' | 'datetimerange'
|
||||||
|
format?: string
|
||||||
|
valueFormat?: string
|
||||||
|
|
||||||
|
// 开关相关
|
||||||
|
activeText?: string
|
||||||
|
inactiveText?: string
|
||||||
|
|
||||||
|
// 滑块相关
|
||||||
|
showInput?: boolean
|
||||||
|
showStops?: boolean
|
||||||
|
showTooltip?: boolean
|
||||||
|
|
||||||
|
// 自定义插槽
|
||||||
|
slotName?: string
|
||||||
|
|
||||||
|
// 布局相关
|
||||||
|
span?: number
|
||||||
|
fullWidth?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态表单配置接口
|
||||||
|
export interface DynamicFormConfig {
|
||||||
|
title?: string
|
||||||
|
gutter?: number
|
||||||
|
columnSpan?: number
|
||||||
|
formProp?: string
|
||||||
|
items: DynamicFormItem[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态表单事件接口
|
||||||
|
export interface DynamicFormEvents {
|
||||||
|
fieldChange: (key: string, value: any, item: DynamicFormItem) => void
|
||||||
|
formChange: (value: Record<string, any>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
// 动态表单方法接口
|
||||||
|
export interface DynamicFormMethods {
|
||||||
|
getFormData: () => Record<string, any>
|
||||||
|
setFormData: (data: Record<string, any>) => void
|
||||||
|
resetForm: () => void
|
||||||
|
validateForm: () => boolean
|
||||||
|
}
|
||||||
322
src/components/DynamicForm/utils.ts
Normal file
322
src/components/DynamicForm/utils.ts
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
import type { DynamicFormItem, DynamicFormConfig } from './types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建数字输入表单项
|
||||||
|
*/
|
||||||
|
export const createNumberItem = (
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
options: {
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
precision?: number
|
||||||
|
step?: number
|
||||||
|
placeholder?: string
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
span?: number
|
||||||
|
fullWidth?: boolean
|
||||||
|
} = {}
|
||||||
|
): DynamicFormItem => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
type: 'number',
|
||||||
|
min: options.min,
|
||||||
|
max: options.max,
|
||||||
|
precision: options.precision || 0,
|
||||||
|
step: options.step || 1,
|
||||||
|
placeholder: options.placeholder || `请输入${label}`,
|
||||||
|
required: options.required,
|
||||||
|
disabled: options.disabled,
|
||||||
|
span: options.span,
|
||||||
|
fullWidth: options.fullWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建文本输入表单项
|
||||||
|
*/
|
||||||
|
export const createTextItem = (
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
options: {
|
||||||
|
inputType?: string
|
||||||
|
rows?: number
|
||||||
|
placeholder?: string
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
span?: number
|
||||||
|
fullWidth?: boolean
|
||||||
|
} = {}
|
||||||
|
): DynamicFormItem => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
type: options.rows && options.rows > 1 ? 'textarea' : 'text',
|
||||||
|
inputType: options.inputType || 'text',
|
||||||
|
rows: options.rows,
|
||||||
|
placeholder: options.placeholder || `请输入${label}`,
|
||||||
|
required: options.required,
|
||||||
|
disabled: options.disabled,
|
||||||
|
span: options.span,
|
||||||
|
fullWidth: options.fullWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建选择器表单项
|
||||||
|
*/
|
||||||
|
export const createSelectItem = (
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
options: Array<{ label: string; value: any; disabled?: boolean }>,
|
||||||
|
config: {
|
||||||
|
placeholder?: string
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
filterable?: boolean
|
||||||
|
multiple?: boolean
|
||||||
|
span?: number
|
||||||
|
fullWidth?: boolean
|
||||||
|
} = {}
|
||||||
|
): DynamicFormItem => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
type: 'select',
|
||||||
|
options,
|
||||||
|
placeholder: config.placeholder || `请选择${label}`,
|
||||||
|
required: config.required,
|
||||||
|
disabled: config.disabled,
|
||||||
|
filterable: config.filterable,
|
||||||
|
multiple: config.multiple,
|
||||||
|
span: config.span,
|
||||||
|
fullWidth: config.fullWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建日期选择表单项
|
||||||
|
*/
|
||||||
|
export const createDateItem = (
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
options: {
|
||||||
|
dateType?: 'date' | 'datetime' | 'daterange' | 'datetimerange'
|
||||||
|
format?: string
|
||||||
|
valueFormat?: string
|
||||||
|
placeholder?: string
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
span?: number
|
||||||
|
fullWidth?: boolean
|
||||||
|
} = {}
|
||||||
|
): DynamicFormItem => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
type: 'date',
|
||||||
|
dateType: options.dateType || 'date',
|
||||||
|
format: options.format,
|
||||||
|
valueFormat: options.valueFormat,
|
||||||
|
placeholder: options.placeholder || `请选择${label}`,
|
||||||
|
required: options.required,
|
||||||
|
disabled: options.disabled,
|
||||||
|
span: options.span,
|
||||||
|
fullWidth: options.fullWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建开关表单项
|
||||||
|
*/
|
||||||
|
export const createSwitchItem = (
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
options: {
|
||||||
|
activeText?: string
|
||||||
|
inactiveText?: string
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
span?: number
|
||||||
|
fullWidth?: boolean
|
||||||
|
} = {}
|
||||||
|
): DynamicFormItem => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
type: 'switch',
|
||||||
|
activeText: options.activeText,
|
||||||
|
inactiveText: options.inactiveText,
|
||||||
|
required: options.required,
|
||||||
|
disabled: options.disabled,
|
||||||
|
span: options.span,
|
||||||
|
fullWidth: options.fullWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建单选框组表单项
|
||||||
|
*/
|
||||||
|
export const createRadioItem = (
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
options: Array<{ label: string; value: any; disabled?: boolean }>,
|
||||||
|
config: {
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
span?: number
|
||||||
|
fullWidth?: boolean
|
||||||
|
} = {}
|
||||||
|
): DynamicFormItem => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
type: 'radio',
|
||||||
|
options,
|
||||||
|
required: config.required,
|
||||||
|
disabled: config.disabled,
|
||||||
|
span: config.span,
|
||||||
|
fullWidth: config.fullWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建复选框组表单项
|
||||||
|
*/
|
||||||
|
export const createCheckboxItem = (
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
options: Array<{ label: string; value: any; disabled?: boolean }>,
|
||||||
|
config: {
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
span?: number
|
||||||
|
fullWidth?: boolean
|
||||||
|
} = {}
|
||||||
|
): DynamicFormItem => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
type: 'checkbox',
|
||||||
|
options,
|
||||||
|
required: config.required,
|
||||||
|
disabled: config.disabled,
|
||||||
|
span: config.span,
|
||||||
|
fullWidth: config.fullWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建滑块表单项
|
||||||
|
*/
|
||||||
|
export const createSliderItem = (
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
options: {
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
step?: number
|
||||||
|
showInput?: boolean
|
||||||
|
showStops?: boolean
|
||||||
|
showTooltip?: boolean
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
span?: number
|
||||||
|
fullWidth?: boolean
|
||||||
|
} = {}
|
||||||
|
): DynamicFormItem => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
type: 'slider',
|
||||||
|
min: options.min || 0,
|
||||||
|
max: options.max || 100,
|
||||||
|
step: options.step || 1,
|
||||||
|
showInput: options.showInput,
|
||||||
|
showStops: options.showStops,
|
||||||
|
showTooltip: options.showTooltip,
|
||||||
|
required: options.required,
|
||||||
|
disabled: options.disabled,
|
||||||
|
span: options.span,
|
||||||
|
fullWidth: options.fullWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建自定义插槽表单项
|
||||||
|
*/
|
||||||
|
export const createCustomItem = (
|
||||||
|
key: string,
|
||||||
|
label: string,
|
||||||
|
slotName: string,
|
||||||
|
options: {
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
span?: number
|
||||||
|
fullWidth?: boolean
|
||||||
|
} = {}
|
||||||
|
): DynamicFormItem => {
|
||||||
|
return {
|
||||||
|
key,
|
||||||
|
label,
|
||||||
|
type: 'custom',
|
||||||
|
slotName,
|
||||||
|
required: options.required,
|
||||||
|
disabled: options.disabled,
|
||||||
|
span: options.span,
|
||||||
|
fullWidth: options.fullWidth
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建动态表单配置
|
||||||
|
*/
|
||||||
|
export const createDynamicFormConfig = (
|
||||||
|
items: DynamicFormItem[],
|
||||||
|
config: {
|
||||||
|
title?: string
|
||||||
|
gutter?: number
|
||||||
|
columnSpan?: number
|
||||||
|
formProp?: string
|
||||||
|
} = {}
|
||||||
|
): DynamicFormConfig => {
|
||||||
|
return {
|
||||||
|
title: config.title,
|
||||||
|
gutter: config.gutter || 16,
|
||||||
|
columnSpan: config.columnSpan || 12,
|
||||||
|
formProp: config.formProp,
|
||||||
|
items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证表单项配置
|
||||||
|
*/
|
||||||
|
export const validateFormItem = (item: DynamicFormItem): boolean => {
|
||||||
|
if (!item.key || !item.label || !item.type) {
|
||||||
|
console.error('表单项配置不完整:', item)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证选择器类型必须有选项
|
||||||
|
if (['select', 'radio', 'checkbox'].includes(item.type) && (!item.options || item.options.length === 0)) {
|
||||||
|
console.error('选择器类型表单项必须有选项:', item)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证表单配置
|
||||||
|
*/
|
||||||
|
export const validateFormConfig = (config: DynamicFormConfig): boolean => {
|
||||||
|
if (!config.items || config.items.length === 0) {
|
||||||
|
console.error('表单配置必须有表单项')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.items.every(validateFormItem)
|
||||||
|
}
|
||||||
45
src/components/FormBuilder/index.ts
Normal file
45
src/components/FormBuilder/index.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import FormBuilder from './index.vue'
|
||||||
|
import type {
|
||||||
|
FormBuilderItem,
|
||||||
|
FormBuilderConfig,
|
||||||
|
FormBuilderEvents,
|
||||||
|
FormBuilderMethods
|
||||||
|
} from './types'
|
||||||
|
import {
|
||||||
|
createFormBuilderItem,
|
||||||
|
createFormBuilderConfig,
|
||||||
|
validateFormBuilderItem,
|
||||||
|
validateFormBuilderConfig,
|
||||||
|
generateDefaultItems,
|
||||||
|
importFromJson,
|
||||||
|
exportToJson,
|
||||||
|
duplicateItem,
|
||||||
|
createBatchItems
|
||||||
|
} from './utils'
|
||||||
|
|
||||||
|
// 导出组件
|
||||||
|
export default FormBuilder
|
||||||
|
|
||||||
|
// 导出类型
|
||||||
|
export type {
|
||||||
|
FormBuilderItem,
|
||||||
|
FormBuilderConfig,
|
||||||
|
FormBuilderEvents,
|
||||||
|
FormBuilderMethods
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导出工具函数
|
||||||
|
export {
|
||||||
|
createFormBuilderItem,
|
||||||
|
createFormBuilderConfig,
|
||||||
|
validateFormBuilderItem,
|
||||||
|
validateFormBuilderConfig,
|
||||||
|
generateDefaultItems,
|
||||||
|
importFromJson,
|
||||||
|
exportToJson,
|
||||||
|
duplicateItem,
|
||||||
|
createBatchItems
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
706
src/components/FormBuilder/index.vue
Normal file
706
src/components/FormBuilder/index.vue
Normal file
@@ -0,0 +1,706 @@
|
|||||||
|
<template>
|
||||||
|
<div class="form-builder">
|
||||||
|
<!-- 表单构建器标题 -->
|
||||||
|
<div class="form-builder-header">
|
||||||
|
<h4>{{ title }}</h4>
|
||||||
|
<div class="header-actions">
|
||||||
|
<el-select v-model="selectedFieldType" placeholder="选择字段类型" style="width: 120px; margin-right: 8px;">
|
||||||
|
<el-option v-for="type in availableFieldTypes" :key="type" :label="getFieldTypeLabel(type)" :value="type" />
|
||||||
|
</el-select>
|
||||||
|
<el-button type="primary" @click="() => addItem(selectedFieldType)">
|
||||||
|
<Icon icon="ep:plus" />
|
||||||
|
添加项目
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 表单项列表 -->
|
||||||
|
<div class="form-items-list">
|
||||||
|
<div v-for="(item, index) in formItems" :key="item.id" class="form-item-card">
|
||||||
|
<div class="form-item-header">
|
||||||
|
<span class="item-index">第 {{ index + 1 }} 个质检项目</span>
|
||||||
|
<div class="item-actions">
|
||||||
|
<el-button type="primary" size="small" @click="moveUp(index)" :disabled="index === 0">
|
||||||
|
<Icon icon="ep:arrow-up" />
|
||||||
|
</el-button>
|
||||||
|
<el-button type="primary" size="small" @click="moveDown(index)" :disabled="index === formItems.length - 1">
|
||||||
|
<Icon icon="ep:arrow-down" />
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" size="small" @click="removeItem(index)">
|
||||||
|
<Icon icon="ep:delete" />
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-item-content">
|
||||||
|
<el-row :gutter="16">
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="项目名称" :prop="`items.${index}.label`">
|
||||||
|
<el-input v-model="item.label" placeholder="请输入项目标题" @input="handleItemChange" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item label="项目key(建议保持默认)" :prop="`items.${index}.key`">
|
||||||
|
<el-input v-model="item.key" placeholder="请输入项目键值" @input="handleItemChange" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-form-item label="输入类型" :prop="`items.${index}.type`">
|
||||||
|
<el-select v-model="item.type" placeholder="选择类型" @change="handleTypeChange(index, $event)">
|
||||||
|
<el-option v-for="type in availableFieldTypes" :key="type" :label="getFieldTypeLabel(type)"
|
||||||
|
:value="type" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="默认值" :prop="`items.${index}.defaultValue`">
|
||||||
|
<!-- 根据字段类型显示不同的输入控件 -->
|
||||||
|
<el-input v-if="item.type === 'text' || item.type === 'textarea'" v-model="item.defaultValue"
|
||||||
|
:type="item.type === 'textarea' ? 'textarea' : 'text'" :rows="item.type === 'textarea' ? 2 : 1"
|
||||||
|
placeholder="请输入默认值" @input="handleItemChange" />
|
||||||
|
<el-input-number v-else-if="item.type === 'number'" v-model="item.defaultValue" placeholder="请输入数字"
|
||||||
|
@change="handleItemChange" style="width: 100%" />
|
||||||
|
<el-select v-else-if="item.type === 'select'" v-model="item.defaultValue" placeholder="请选择默认值"
|
||||||
|
@change="handleItemChange" style="width: 100%">
|
||||||
|
<el-option label="选项1" value="option1" />
|
||||||
|
<el-option label="选项2" value="option2" />
|
||||||
|
</el-select>
|
||||||
|
<el-switch v-else-if="item.type === 'switch'" v-model="item.defaultValue" @change="handleItemChange" />
|
||||||
|
<el-date-picker v-else-if="item.type === 'date'" v-model="item.defaultValue" type="date"
|
||||||
|
placeholder="选择日期" @change="handleItemChange" style="width: 100%" />
|
||||||
|
<el-input v-else v-model="item.defaultValue" placeholder="请输入默认值" @input="handleItemChange" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- text类型特殊配置 -->
|
||||||
|
<el-row :gutter="16" v-if="item.type === 'text'" style="margin-top: 16px; padding: 16px; background: #f0f9ff; border-radius: 8px; border: 1px solid #e0f2fe;">
|
||||||
|
<el-col :span="24">
|
||||||
|
<div style="display: flex; align-items: center; margin-bottom: 12px;">
|
||||||
|
<Icon icon="ep:document-copy" style="color: #0ea5e9; margin-right: 8px;" />
|
||||||
|
<span style="font-weight: 600; color: #0f172a;">文本字段配置</span>
|
||||||
|
<el-tooltip content="为文本字段设置显示行数和最大长度限制" placement="top">
|
||||||
|
<Icon icon="ep:question-filled" style="color: #64748b; margin-left: 8px; cursor: help;" />
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="显示行数">
|
||||||
|
<el-input-number v-model="item.rows" :min="1" :max="10" placeholder="行数" @change="handleItemChange" style="width: 100%" />
|
||||||
|
<div style="font-size: 12px; color: #64748b; margin-top: 4px;">
|
||||||
|
设置文本框的显示行数(1-10行)
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="最大长度">
|
||||||
|
<el-input-number v-model="item.maxlength" :min="1" :max="10000" placeholder="字符数" @change="handleItemChange" style="width: 100%" />
|
||||||
|
<div style="font-size: 12px; color: #64748b; margin-top: 4px;">
|
||||||
|
设置允许输入的最大字符数
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 质检标准配置 -->
|
||||||
|
<el-row :gutter="16"
|
||||||
|
style="margin-top: 16px; padding: 16px; background: #f8f9fa; border-radius: 8px; border: 1px solid #e9ecef;">
|
||||||
|
<el-col :span="24">
|
||||||
|
<div style="display: flex; align-items: center; margin-bottom: 12px;">
|
||||||
|
<Icon icon="ep:info-filled" style="color: #409eff; margin-right: 8px;" />
|
||||||
|
<span style="font-weight: 600; color: #303133;">质检标准配置</span>
|
||||||
|
<el-tooltip content="为这个字段设置质检标准,帮助质检员了解检验要求" placement="top">
|
||||||
|
<Icon icon="ep:question-filled" style="color: #909399; margin-left: 8px; cursor: help;" />
|
||||||
|
</el-tooltip>
|
||||||
|
</div>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="8">
|
||||||
|
<el-form-item label="标准描述" :required="true">
|
||||||
|
<el-input v-model="item.standard" placeholder="例如:重量必须符合标准要求" @input="handleItemChange" />
|
||||||
|
<div style="font-size: 12px; color: #909399; margin-top: 4px;">
|
||||||
|
描述这个字段的质检标准要求
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="4">
|
||||||
|
<el-form-item label="标准类型">
|
||||||
|
<div style="padding: 8px 12px; background: #f5f7fa; border-radius: 4px; color: #606266; font-size: 14px;">
|
||||||
|
{{ getStandardTypeLabel(item.standardType) }}
|
||||||
|
</div>
|
||||||
|
<div style="font-size: 12px; color: #909399; margin-top: 4px;">
|
||||||
|
根据字段类型自动设置
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="6">
|
||||||
|
<el-form-item :label="getStandardValueLabel(item.standardType)">
|
||||||
|
<el-input v-if="item.standardType === 'text'" v-model="item.standardValue" placeholder="例如:合格"
|
||||||
|
@input="handleItemChange" />
|
||||||
|
<div v-else-if="item.standardType === 'range'" style="display: flex; gap: 8px;">
|
||||||
|
<el-input-number v-model="item.standardRange!.min" placeholder="最小值" @change="handleItemChange"
|
||||||
|
style="flex: 1" />
|
||||||
|
<span style="line-height: 32px;">~</span>
|
||||||
|
<el-input-number v-model="item.standardRange!.max" placeholder="最大值" @change="handleItemChange"
|
||||||
|
style="flex: 1" />
|
||||||
|
</div>
|
||||||
|
<el-button v-else-if="item.standardType === 'select'" type="primary" size="small" @click="editStandardOptions(index)">
|
||||||
|
<Icon icon="ep:setting" />
|
||||||
|
编辑选项
|
||||||
|
</el-button>
|
||||||
|
<div style="font-size: 12px; color: #909399; margin-top: 4px;">
|
||||||
|
{{ getStandardValueHint(item.standardType) }}
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<el-form-item label="显示标准">
|
||||||
|
<el-switch v-model="item.showStandard" @change="handleItemChange" />
|
||||||
|
<div style="font-size: 12px; color: #909399; margin-top: 4px;">
|
||||||
|
在表单中显示标准
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="3">
|
||||||
|
<el-form-item label="必填">
|
||||||
|
<el-switch v-model="item.required" @change="handleItemChange" />
|
||||||
|
<div style="font-size: 12px; color: #909399; margin-top: 4px;">
|
||||||
|
此字段是否必填
|
||||||
|
</div>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 空状态 -->
|
||||||
|
<div v-if="formItems.length === 0" class="empty-state">
|
||||||
|
<el-empty description="暂无表单项,点击上方按钮添加">
|
||||||
|
<el-button type="primary" @click="() => addItem()">
|
||||||
|
<Icon icon="ep:plus" />
|
||||||
|
添加第一个项目
|
||||||
|
</el-button>
|
||||||
|
</el-empty>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 预览区域 -->
|
||||||
|
<div v-if="formItems.length > 0" class="preview-section">
|
||||||
|
<el-divider content-position="left">预览效果</el-divider>
|
||||||
|
<div class="preview-content">
|
||||||
|
<DynamicForm v-model="previewData" :items="dynamicFormItems" :gutter="16" :column-span="12"
|
||||||
|
@form-change="handlePreviewChange" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, computed, watch } from 'vue'
|
||||||
|
import DynamicForm, { type DynamicFormItem } from '@/components/DynamicForm'
|
||||||
|
import type { FormBuilderItem, FormFieldType } from './types'
|
||||||
|
|
||||||
|
// 定义Props
|
||||||
|
interface Props {
|
||||||
|
modelValue?: FormBuilderItem[]
|
||||||
|
title?: string
|
||||||
|
mode?: 'simple' | 'advanced' | 'custom'
|
||||||
|
showPreview?: boolean
|
||||||
|
allowDrag?: boolean
|
||||||
|
customFieldTypes?: FormFieldType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 定义Emits
|
||||||
|
interface Emits {
|
||||||
|
(e: 'update:modelValue', value: FormBuilderItem[]): void
|
||||||
|
(e: 'change', value: FormBuilderItem[]): void
|
||||||
|
(e: 'preview-change', value: Record<string, any>): void
|
||||||
|
(e: 'itemAdd', item: FormBuilderItem): void
|
||||||
|
(e: 'itemRemove', item: FormBuilderItem): void
|
||||||
|
(e: 'itemUpdate', item: FormBuilderItem, index: number): void
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
|
modelValue: () => [],
|
||||||
|
title: '表单构建器',
|
||||||
|
mode: 'simple',
|
||||||
|
showPreview: true,
|
||||||
|
allowDrag: true,
|
||||||
|
customFieldTypes: () => ['text', 'number', 'select']
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<Emits>()
|
||||||
|
|
||||||
|
// 表单数据
|
||||||
|
const formItems = ref<FormBuilderItem[]>([...props.modelValue])
|
||||||
|
const previewData = ref<Record<string, any>>({})
|
||||||
|
const selectedFieldType = ref<FormFieldType>('text')
|
||||||
|
|
||||||
|
// 初始化时确保标准字段有默认值
|
||||||
|
formItems.value.forEach(item => {
|
||||||
|
if (!item.standard) item.standard = ''
|
||||||
|
if (!item.standardType) item.standardType = 'text'
|
||||||
|
if (!item.standardValue) item.standardValue = ''
|
||||||
|
if (!item.standardRange) item.standardRange = { min: 0, max: 100 }
|
||||||
|
if (!item.standardOptions) item.standardOptions = []
|
||||||
|
if (item.showStandard === undefined) item.showStandard = true
|
||||||
|
})
|
||||||
|
|
||||||
|
// 可用的字段类型
|
||||||
|
const availableFieldTypes = computed(() => {
|
||||||
|
return props.customFieldTypes || ['text', 'number', 'select']
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取字段类型标签
|
||||||
|
const getFieldTypeLabel = (type: FormFieldType): string => {
|
||||||
|
const labels: Record<FormFieldType, string> = {
|
||||||
|
text: '文本',
|
||||||
|
number: '数字',
|
||||||
|
select: '选择器',
|
||||||
|
textarea: '多行文本',
|
||||||
|
date: '日期',
|
||||||
|
switch: '开关',
|
||||||
|
radio: '单选框',
|
||||||
|
checkbox: '复选框',
|
||||||
|
custom: '自定义',
|
||||||
|
time: '时间',
|
||||||
|
slider: '滑块'
|
||||||
|
}
|
||||||
|
return labels[type] || type
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成唯一ID
|
||||||
|
const generateId = () => {
|
||||||
|
return `item_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 转换为DynamicFormItem格式
|
||||||
|
const dynamicFormItems = computed<DynamicFormItem[]>(() => {
|
||||||
|
return formItems.value.map(item => ({
|
||||||
|
key: item.key,
|
||||||
|
label: item.label,
|
||||||
|
type: item.type,
|
||||||
|
placeholder: item.placeholder || `请输入${item.label}`,
|
||||||
|
defaultValue: item.defaultValue,
|
||||||
|
required: item.required,
|
||||||
|
disabled: item.disabled,
|
||||||
|
// 传递质检标准相关属性
|
||||||
|
standard: item.standard,
|
||||||
|
standardType: item.standardType,
|
||||||
|
standardValue: item.standardValue,
|
||||||
|
standardRange: item.standardRange,
|
||||||
|
standardOptions: item.standardOptions,
|
||||||
|
showStandard: item.showStandard,
|
||||||
|
// 传递其他属性
|
||||||
|
...(item.type === 'number' && {
|
||||||
|
min: item.min,
|
||||||
|
max: item.max,
|
||||||
|
step: item.step,
|
||||||
|
precision: item.precision
|
||||||
|
}),
|
||||||
|
...(item.type === 'textarea' && {
|
||||||
|
rows: item.rows,
|
||||||
|
maxlength: item.maxlength
|
||||||
|
}),
|
||||||
|
...(item.type === 'select' && {
|
||||||
|
options: item.options || []
|
||||||
|
}),
|
||||||
|
...(item.type === 'date' && {
|
||||||
|
dateType: item.dateType || 'date',
|
||||||
|
format: item.format,
|
||||||
|
valueFormat: item.valueFormat
|
||||||
|
}),
|
||||||
|
...(item.type === 'switch' && {
|
||||||
|
activeText: item.activeText,
|
||||||
|
inactiveText: item.inactiveText
|
||||||
|
}),
|
||||||
|
rules: item.rules
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 添加表单项
|
||||||
|
const addItem = (type: FormFieldType = 'text') => {
|
||||||
|
// 根据字段类型自动设置标准类型
|
||||||
|
const standardType = getDefaultStandardType(type)
|
||||||
|
|
||||||
|
const newItem: FormBuilderItem = {
|
||||||
|
id: generateId(),
|
||||||
|
label: `项目${formItems.value.length + 1}`,
|
||||||
|
key: `item_${formItems.value.length + 1}`,
|
||||||
|
type,
|
||||||
|
defaultValue: getDefaultValueByType(type),
|
||||||
|
placeholder: `请输入${type === 'text' ? '文本' : type === 'number' ? '数字' : '内容'}`,
|
||||||
|
// text类型特有属性
|
||||||
|
...(type === 'text' && {
|
||||||
|
rows: 1,
|
||||||
|
maxlength: 100
|
||||||
|
}),
|
||||||
|
// 初始化标准字段(根据类型自动设置)
|
||||||
|
standard: '',
|
||||||
|
standardType,
|
||||||
|
standardValue: '',
|
||||||
|
standardRange: { min: 0, max: 100 },
|
||||||
|
standardOptions: [],
|
||||||
|
showStandard: true,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
formItems.value.push(newItem)
|
||||||
|
handleItemChange()
|
||||||
|
emit('itemAdd', newItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据字段类型获取默认标准类型
|
||||||
|
const getDefaultStandardType = (type: FormFieldType): string => {
|
||||||
|
switch (type) {
|
||||||
|
case 'text':
|
||||||
|
return 'text'
|
||||||
|
case 'number':
|
||||||
|
return 'range'
|
||||||
|
case 'select':
|
||||||
|
return 'select'
|
||||||
|
default:
|
||||||
|
return 'text'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取标准类型显示标签
|
||||||
|
const getStandardTypeLabel = (standardType: string): string => {
|
||||||
|
const labels: Record<string, string> = {
|
||||||
|
text: '📄 文本标准',
|
||||||
|
number: '🔢 数值标准',
|
||||||
|
range: '📊 范围标准',
|
||||||
|
select: '✅ 选择标准'
|
||||||
|
}
|
||||||
|
return labels[standardType] || standardType
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取标准值标签
|
||||||
|
const getStandardValueLabel = (standardType: string): string => {
|
||||||
|
switch (standardType) {
|
||||||
|
case 'text':
|
||||||
|
return '期望文本'
|
||||||
|
case 'range':
|
||||||
|
return '数值范围'
|
||||||
|
case 'select':
|
||||||
|
return '标准选项'
|
||||||
|
default:
|
||||||
|
return '标准值'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取标准值提示
|
||||||
|
const getStandardValueHint = (standardType: string): string => {
|
||||||
|
switch (standardType) {
|
||||||
|
case 'text':
|
||||||
|
return '输入期望的文本值'
|
||||||
|
case 'range':
|
||||||
|
return '设置允许的数值范围'
|
||||||
|
case 'select':
|
||||||
|
return '设置可选的标准选项'
|
||||||
|
default:
|
||||||
|
return '设置标准值'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据类型获取默认值
|
||||||
|
const getDefaultValueByType = (type: FormFieldType): any => {
|
||||||
|
switch (type) {
|
||||||
|
case 'number':
|
||||||
|
return 0
|
||||||
|
case 'switch':
|
||||||
|
return false
|
||||||
|
case 'select':
|
||||||
|
case 'radio':
|
||||||
|
case 'checkbox':
|
||||||
|
return []
|
||||||
|
case 'date':
|
||||||
|
return null
|
||||||
|
default:
|
||||||
|
return ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除表单项
|
||||||
|
const removeItem = (index: number) => {
|
||||||
|
formItems.value.splice(index, 1)
|
||||||
|
handleItemChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上移表单项
|
||||||
|
const moveUp = (index: number) => {
|
||||||
|
if (index > 0) {
|
||||||
|
const item = formItems.value.splice(index, 1)[0]
|
||||||
|
formItems.value.splice(index - 1, 0, item)
|
||||||
|
handleItemChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下移表单项
|
||||||
|
const moveDown = (index: number) => {
|
||||||
|
if (index < formItems.value.length - 1) {
|
||||||
|
const item = formItems.value.splice(index, 1)[0]
|
||||||
|
formItems.value.splice(index + 1, 0, item)
|
||||||
|
handleItemChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理字段类型变化
|
||||||
|
const handleTypeChange = (index: number, newType: FormFieldType) => {
|
||||||
|
const item = formItems.value[index]
|
||||||
|
if (item) {
|
||||||
|
// 根据新的字段类型自动更新标准类型
|
||||||
|
item.standardType = getDefaultStandardType(newType)
|
||||||
|
|
||||||
|
// 重置相关字段的值
|
||||||
|
item.standardValue = ''
|
||||||
|
item.standardRange = { min: 0, max: 100 }
|
||||||
|
item.standardOptions = []
|
||||||
|
|
||||||
|
// 为text类型添加默认属性
|
||||||
|
if (newType === 'text') {
|
||||||
|
item.rows = item.rows || 1
|
||||||
|
item.maxlength = item.maxlength || 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleItemChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理表单项变化
|
||||||
|
const handleItemChange = () => {
|
||||||
|
// 确保所有标准字段都有值,并根据字段类型自动设置标准类型
|
||||||
|
formItems.value.forEach(item => {
|
||||||
|
if (!item.standard) item.standard = ''
|
||||||
|
if (!item.standardType) {
|
||||||
|
// 如果没有标准类型,根据字段类型设置默认值
|
||||||
|
item.standardType = getDefaultStandardType(item.type)
|
||||||
|
}
|
||||||
|
if (!item.standardValue) item.standardValue = ''
|
||||||
|
if (!item.standardRange) item.standardRange = { min: 0, max: 100 }
|
||||||
|
if (!item.standardOptions) item.standardOptions = []
|
||||||
|
if (item.showStandard === undefined) item.showStandard = true
|
||||||
|
})
|
||||||
|
|
||||||
|
emit('update:modelValue', [...formItems.value])
|
||||||
|
emit('change', [...formItems.value])
|
||||||
|
|
||||||
|
// 更新预览数据
|
||||||
|
updatePreviewData()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新预览数据
|
||||||
|
const updatePreviewData = () => {
|
||||||
|
const newPreviewData: Record<string, any> = {}
|
||||||
|
formItems.value.forEach(item => {
|
||||||
|
if (item.defaultValue !== null && item.defaultValue !== undefined && item.defaultValue !== '') {
|
||||||
|
newPreviewData[item.key] = item.defaultValue
|
||||||
|
}
|
||||||
|
})
|
||||||
|
previewData.value = newPreviewData
|
||||||
|
emit('preview-change', newPreviewData)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理预览数据变化
|
||||||
|
const handlePreviewChange = (value: Record<string, any>) => {
|
||||||
|
console.log('FormBuilder预览数据变化:', value)
|
||||||
|
previewData.value = value
|
||||||
|
emit('preview-change', value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 监听外部数据变化
|
||||||
|
watch(() => props.modelValue, (newValue) => {
|
||||||
|
formItems.value = [...newValue]
|
||||||
|
// 确保标准字段有默认值
|
||||||
|
formItems.value.forEach(item => {
|
||||||
|
if (!item.standard) item.standard = ''
|
||||||
|
if (!item.standardType) item.standardType = 'text'
|
||||||
|
if (!item.standardValue) item.standardValue = ''
|
||||||
|
if (!item.standardRange) item.standardRange = { min: 0, max: 100 }
|
||||||
|
if (!item.standardOptions) item.standardOptions = []
|
||||||
|
if (item.showStandard === undefined) item.showStandard = true
|
||||||
|
})
|
||||||
|
updatePreviewData()
|
||||||
|
}, { deep: true })
|
||||||
|
|
||||||
|
// 暴露方法
|
||||||
|
const getFormItems = () => {
|
||||||
|
return [...formItems.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
const setFormItems = (items: FormBuilderItem[]) => {
|
||||||
|
formItems.value = [...items]
|
||||||
|
handleItemChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
const clearItems = () => {
|
||||||
|
formItems.value = []
|
||||||
|
handleItemChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
const addItemWithData = (label: string, key: string, type: FormFieldType = 'text', defaultValue: any = '') => {
|
||||||
|
const standardType = getDefaultStandardType(type)
|
||||||
|
|
||||||
|
const newItem: FormBuilderItem = {
|
||||||
|
id: generateId(),
|
||||||
|
label,
|
||||||
|
key,
|
||||||
|
type,
|
||||||
|
defaultValue,
|
||||||
|
// 初始化标准字段
|
||||||
|
standard: '',
|
||||||
|
standardType,
|
||||||
|
standardValue: '',
|
||||||
|
standardRange: { min: 0, max: 100 },
|
||||||
|
standardOptions: [],
|
||||||
|
showStandard: true,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
formItems.value.push(newItem)
|
||||||
|
handleItemChange()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑标准选项
|
||||||
|
const editStandardOptions = (index: number) => {
|
||||||
|
const item = formItems.value[index]
|
||||||
|
if (!item.standardOptions) {
|
||||||
|
item.standardOptions = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建当前选项的文本
|
||||||
|
const currentOptions = item.standardOptions.map(opt => opt.label).join(',')
|
||||||
|
|
||||||
|
// 使用更友好的提示
|
||||||
|
const newOptions = prompt(
|
||||||
|
`📋 编辑标准选项\n\n当前选项:${currentOptions || '无'}\n\n请输入新的标准选项,用逗号分隔:\n例如:合格,不合格,待定`,
|
||||||
|
currentOptions
|
||||||
|
)
|
||||||
|
|
||||||
|
if (newOptions !== null) {
|
||||||
|
if (newOptions.trim()) {
|
||||||
|
// 解析新选项
|
||||||
|
const options = newOptions.split(',').map((label, idx) => ({
|
||||||
|
label: label.trim(),
|
||||||
|
value: `option_${idx + 1}`,
|
||||||
|
disabled: false
|
||||||
|
})).filter(opt => opt.label) // 过滤空选项
|
||||||
|
|
||||||
|
item.standardOptions = options
|
||||||
|
handleItemChange()
|
||||||
|
|
||||||
|
// 显示成功提示
|
||||||
|
console.log(`已设置 ${options.length} 个标准选项:${options.map(opt => opt.label).join(',')}`)
|
||||||
|
} else {
|
||||||
|
// 清空选项
|
||||||
|
item.standardOptions = []
|
||||||
|
handleItemChange()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
getFormItems,
|
||||||
|
setFormItems,
|
||||||
|
clearItems,
|
||||||
|
addItemWithData
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.form-builder {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-builder-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
padding-bottom: 12px;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-builder-header h4 {
|
||||||
|
margin: 0;
|
||||||
|
color: #303133;
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-items-list {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-card {
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
background: #fff;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-card:hover {
|
||||||
|
border-color: #409eff;
|
||||||
|
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 12px 16px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
border-bottom: 1px solid #e4e7ed;
|
||||||
|
border-radius: 8px 8px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-index {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-item-content {
|
||||||
|
padding: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.empty-state {
|
||||||
|
text-align: center;
|
||||||
|
padding: 40px 20px;
|
||||||
|
background: #fafafa;
|
||||||
|
border: 2px dashed #d9d9d9;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-section {
|
||||||
|
margin-top: 24px;
|
||||||
|
padding-top: 24px;
|
||||||
|
border-top: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-content {
|
||||||
|
background: #f8f9fa;
|
||||||
|
padding: 20px;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid #e4e7ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
font-weight: 500;
|
||||||
|
color: #606266;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-button + .el-button) {
|
||||||
|
margin-left: 8px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
82
src/components/FormBuilder/types.ts
Normal file
82
src/components/FormBuilder/types.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// 表单字段类型
|
||||||
|
export type FormFieldType = 'text' | 'number' | 'select' | 'textarea' | 'date' | 'switch' | 'radio' | 'checkbox' | 'custom' | 'time' | 'slider'
|
||||||
|
|
||||||
|
// 表单字段选项
|
||||||
|
export interface FormFieldOption {
|
||||||
|
label: string
|
||||||
|
value: any
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单构建器项目接口
|
||||||
|
export interface FormBuilderItem {
|
||||||
|
id: string
|
||||||
|
label: string
|
||||||
|
key: string
|
||||||
|
type: FormFieldType
|
||||||
|
defaultValue: any
|
||||||
|
placeholder?: string
|
||||||
|
required?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
// 质检标准相关
|
||||||
|
standard?: string // 质检标准描述
|
||||||
|
standardType?: 'text' | 'number' | 'range' | 'select' // 标准类型
|
||||||
|
standardValue?: any // 标准值
|
||||||
|
standardRange?: { min: number; max: number } // 标准范围
|
||||||
|
standardOptions?: FormFieldOption[] // 标准选项
|
||||||
|
showStandard?: boolean // 是否显示标准
|
||||||
|
// 选择器相关
|
||||||
|
options?: FormFieldOption[]
|
||||||
|
// 数字输入相关
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
step?: number
|
||||||
|
precision?: number
|
||||||
|
// 文本输入相关
|
||||||
|
rows?: number
|
||||||
|
maxlength?: number
|
||||||
|
// 日期相关
|
||||||
|
dateType?: 'date' | 'datetime' | 'daterange' | 'datetimerange'
|
||||||
|
format?: string
|
||||||
|
valueFormat?: string
|
||||||
|
// 开关相关
|
||||||
|
activeText?: string
|
||||||
|
inactiveText?: string
|
||||||
|
// 自定义验证规则
|
||||||
|
rules?: any[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单构建器配置接口
|
||||||
|
export interface FormBuilderConfig {
|
||||||
|
title?: string
|
||||||
|
items: FormBuilderItem[]
|
||||||
|
// 使用场景配置
|
||||||
|
mode?: 'simple' | 'advanced' | 'custom'
|
||||||
|
// 是否显示预览
|
||||||
|
showPreview?: boolean
|
||||||
|
// 是否允许拖拽排序
|
||||||
|
allowDrag?: boolean
|
||||||
|
// 自定义字段类型
|
||||||
|
customFieldTypes?: FormFieldType[]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单构建器事件接口
|
||||||
|
export interface FormBuilderEvents {
|
||||||
|
change: (items: FormBuilderItem[]) => void
|
||||||
|
itemAdd: (item: FormBuilderItem) => void
|
||||||
|
itemRemove: (item: FormBuilderItem) => void
|
||||||
|
itemUpdate: (item: FormBuilderItem, index: number) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
// 表单构建器方法接口
|
||||||
|
export interface FormBuilderMethods {
|
||||||
|
getFormItems: () => FormBuilderItem[]
|
||||||
|
setFormItems: (items: FormBuilderItem[]) => void
|
||||||
|
clearItems: () => void
|
||||||
|
addItemWithData: (label: string, key: string, type?: FormFieldType, defaultValue?: any) => void
|
||||||
|
exportConfig: () => FormBuilderConfig
|
||||||
|
importConfig: (config: FormBuilderConfig) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
147
src/components/FormBuilder/utils.ts
Normal file
147
src/components/FormBuilder/utils.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import type { FormBuilderItem, FormBuilderConfig } from './types'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建表单构建器项目
|
||||||
|
*/
|
||||||
|
export const createFormBuilderItem = (
|
||||||
|
label: string,
|
||||||
|
key: string,
|
||||||
|
defaultValue: string = '',
|
||||||
|
id?: string
|
||||||
|
): FormBuilderItem => {
|
||||||
|
return {
|
||||||
|
id: id || `item_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
||||||
|
label,
|
||||||
|
key,
|
||||||
|
defaultValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建表单构建器配置
|
||||||
|
*/
|
||||||
|
export const createFormBuilderConfig = (
|
||||||
|
items: FormBuilderItem[],
|
||||||
|
title: string = '表单构建器'
|
||||||
|
): FormBuilderConfig => {
|
||||||
|
return {
|
||||||
|
title,
|
||||||
|
items
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证表单构建器项目
|
||||||
|
*/
|
||||||
|
export const validateFormBuilderItem = (item: FormBuilderItem): boolean => {
|
||||||
|
if (!item.id || !item.label || !item.key) {
|
||||||
|
console.error('表单构建器项目配置不完整:', item)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 验证键值格式
|
||||||
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(item.key)) {
|
||||||
|
console.error('键值格式不正确,只能包含字母、数字和下划线,且不能以数字开头:', item.key)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 验证表单构建器配置
|
||||||
|
*/
|
||||||
|
export const validateFormBuilderConfig = (config: FormBuilderConfig): boolean => {
|
||||||
|
if (!config.items || config.items.length === 0) {
|
||||||
|
console.error('表单构建器配置必须有表单项')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查键值是否重复
|
||||||
|
const keys = config.items.map(item => item.key)
|
||||||
|
const uniqueKeys = new Set(keys)
|
||||||
|
if (keys.length !== uniqueKeys.size) {
|
||||||
|
console.error('表单项的键值不能重复')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return config.items.every(validateFormBuilderItem)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成默认的表单构建器项目
|
||||||
|
*/
|
||||||
|
export const generateDefaultItems = (count: number = 3): FormBuilderItem[] => {
|
||||||
|
const items: FormBuilderItem[] = []
|
||||||
|
|
||||||
|
for (let i = 1; i <= count; i++) {
|
||||||
|
items.push(createFormBuilderItem(
|
||||||
|
`项目${i}`,
|
||||||
|
`item_${i}`,
|
||||||
|
''
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从JSON数据导入表单构建器项目
|
||||||
|
*/
|
||||||
|
export const importFromJson = (jsonData: any[]): FormBuilderItem[] => {
|
||||||
|
const items: FormBuilderItem[] = []
|
||||||
|
|
||||||
|
jsonData.forEach((item, index) => {
|
||||||
|
if (typeof item === 'object' && item !== null) {
|
||||||
|
items.push(createFormBuilderItem(
|
||||||
|
item.label || `项目${index + 1}`,
|
||||||
|
item.key || `item_${index + 1}`,
|
||||||
|
item.defaultValue || '',
|
||||||
|
item.id
|
||||||
|
))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出表单构建器项目为JSON
|
||||||
|
*/
|
||||||
|
export const exportToJson = (items: FormBuilderItem[]): string => {
|
||||||
|
return JSON.stringify(items, null, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 复制表单构建器项目
|
||||||
|
*/
|
||||||
|
export const duplicateItem = (item: FormBuilderItem): FormBuilderItem => {
|
||||||
|
return createFormBuilderItem(
|
||||||
|
`${item.label}_副本`,
|
||||||
|
`${item.key}_copy`,
|
||||||
|
item.defaultValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量创建表单构建器项目
|
||||||
|
*/
|
||||||
|
export const createBatchItems = (
|
||||||
|
labels: string[],
|
||||||
|
keys?: string[],
|
||||||
|
defaultValues?: string[]
|
||||||
|
): FormBuilderItem[] => {
|
||||||
|
const items: FormBuilderItem[] = []
|
||||||
|
|
||||||
|
labels.forEach((label, index) => {
|
||||||
|
const key = keys?.[index] || `item_${index + 1}`
|
||||||
|
const defaultValue = defaultValues?.[index] || ''
|
||||||
|
|
||||||
|
items.push(createFormBuilderItem(label, key, defaultValue))
|
||||||
|
})
|
||||||
|
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ import { Setting } from '@/layout/components/Setting'
|
|||||||
import { useRenderLayout } from './components/useRenderLayout'
|
import { useRenderLayout } from './components/useRenderLayout'
|
||||||
import { useDesign } from '@/hooks/web/useDesign'
|
import { useDesign } from '@/hooks/web/useDesign'
|
||||||
import MobileLayout from './components/MobileLayout.vue'
|
import MobileLayout from './components/MobileLayout.vue'
|
||||||
|
import AiChatFloat from '@/layout/components/AiChatFloat/index.vue'
|
||||||
|
|
||||||
const { getPrefixCls } = useDesign()
|
const { getPrefixCls } = useDesign()
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ export default defineComponent({
|
|||||||
{renderLayout()}
|
{renderLayout()}
|
||||||
|
|
||||||
<Backtop></Backtop>
|
<Backtop></Backtop>
|
||||||
|
<AiChatFloat></AiChatFloat>
|
||||||
<Setting></Setting>
|
<Setting></Setting>
|
||||||
</section>
|
</section>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -24,13 +24,14 @@ const pageTitle = computed(() => {
|
|||||||
const tabs = [
|
const tabs = [
|
||||||
{ name: 'Index', path: '/index', label: '首页', icon: 'home' },
|
{ name: 'Index', path: '/index', label: '首页', icon: 'home' },
|
||||||
{ name: 'Workbench', path: '/index', label: '工作台', icon: 'workbench' },
|
{ name: 'Workbench', path: '/index', label: '工作台', icon: 'workbench' },
|
||||||
{ name: 'Function', path: '/index', label: '功能', icon: 'function' },
|
{ name: 'Function', path: '/function', label: '功能', icon: 'function' },
|
||||||
{ name: 'Mine', path: '/user/center', label: '我的', icon: 'mine' }
|
{ name: 'Mine', path: '/user/center', label: '我的', icon: 'mine' }
|
||||||
]
|
]
|
||||||
|
|
||||||
const activeTab = computed(() => {
|
const activeTab = computed(() => {
|
||||||
const p = route.path
|
const p = route.path
|
||||||
if (p === '/index' || p === '/') return 'Index'
|
if (p === '/index' || p === '/') return 'Index'
|
||||||
|
if (p === '/function' || p.startsWith('/function/')) return 'Function'
|
||||||
if (p.includes('/user/center')) return 'Mine'
|
if (p.includes('/user/center')) return 'Mine'
|
||||||
return 'Index'
|
return 'Index'
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -128,6 +128,28 @@ const remainingRouter: AppRouteRecordRaw[] = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/function',
|
||||||
|
component: Layout,
|
||||||
|
name: 'FunctionCenter',
|
||||||
|
meta: {
|
||||||
|
hidden: true
|
||||||
|
},
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: () => import('@/views/function/index.vue'),
|
||||||
|
name: 'FunctionIndex',
|
||||||
|
meta: {
|
||||||
|
canTo: true,
|
||||||
|
hidden: true,
|
||||||
|
noTagsView: true,
|
||||||
|
icon: 'ep:grid',
|
||||||
|
title: '功能'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/dict',
|
path: '/dict',
|
||||||
component: Layout,
|
component: Layout,
|
||||||
|
|||||||
@@ -316,6 +316,19 @@ export function getLast1Year(): [dayjs.ConfigType, dayjs.ConfigType] {
|
|||||||
return getDateRange(lastYearDay, yesterday)
|
return getDateRange(lastYearDay, yesterday)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const formatTime = (timestamp?: number) => {
|
||||||
|
if (!timestamp) return '-'
|
||||||
|
const d = new Date(timestamp)
|
||||||
|
const pad = (n: number) => n.toString().padStart(2, '0')
|
||||||
|
return `${d.getFullYear()}-${pad(d.getMonth()+1)}-${pad(d.getDate())} ` +
|
||||||
|
`${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export const formatDate2 = (date: any) => {
|
||||||
|
if (!date) return '-'
|
||||||
|
return formatDate(new Date(date), 'YYYY-MM-DD')
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取指定日期的开始时间、截止时间
|
* 获取指定日期的开始时间、截止时间
|
||||||
* @param beginDate 开始日期
|
* @param beginDate 开始日期
|
||||||
|
|||||||
@@ -5,34 +5,37 @@
|
|||||||
<div class="mobile-home__banner-bg">
|
<div class="mobile-home__banner-bg">
|
||||||
<div class="mobile-home__banner-content">
|
<div class="mobile-home__banner-content">
|
||||||
<div class="mobile-home__brand">
|
<div class="mobile-home__brand">
|
||||||
<div class="mobile-home__brand-logo">亚为MOM</div>
|
<div class="mobile-home__brand-logo">
|
||||||
<div class="mobile-home__brand-text">
|
<img src="@/assets/imgs/logo.png" alt="Logo" class="mobile-home__logo-icon" />
|
||||||
<div class="mobile-home__brand-title">智能一体化管理系统</div>
|
<div class="mobile-home__logo-text">
|
||||||
<div class="mobile-home__brand-sub">私有化部署,可定制</div>
|
<div class="mobile-home__logo-title">亚为MOM</div>
|
||||||
|
<div class="mobile-home__logo-subtitle">Manufacturing Operations Management</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-home__banner-deco">
|
<div class="mobile-home__brand-desc">
|
||||||
<svg width="80" height="80" viewBox="0 0 80 80" fill="none">
|
<div class="mobile-home__brand-title">智能制造一体化管理平台</div>
|
||||||
<rect x="10" y="20" width="25" height="35" rx="3" fill="rgba(255,255,255,0.3)"/>
|
<div class="mobile-home__brand-tags">
|
||||||
<rect x="40" y="10" width="30" height="45" rx="3" fill="rgba(255,255,255,0.2)"/>
|
<span class="tag">私有化部署</span>
|
||||||
<circle cx="55" cy="60" r="15" fill="rgba(255,255,255,0.15)"/>
|
<span class="tag">可定制</span>
|
||||||
<rect x="15" y="58" width="20" height="3" rx="1" fill="rgba(255,255,255,0.4)"/>
|
<span class="tag">安全可靠</span>
|
||||||
<rect x="15" y="64" width="14" height="3" rx="1" fill="rgba(255,255,255,0.3)"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- 装饰元素 -->
|
||||||
|
<div class="mobile-home__banner-circles">
|
||||||
|
<div class="circle circle-1"></div>
|
||||||
|
<div class="circle circle-2"></div>
|
||||||
|
<div class="circle circle-3"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 常用功能区 -->
|
<!-- 常用功能区 -->
|
||||||
<div class="mobile-home__section">
|
<div class="mobile-home__section">
|
||||||
<div class="mobile-home__section-header">
|
<div class="mobile-home__section-header">
|
||||||
<div class="mobile-home__section-icon">
|
|
||||||
<svg viewBox="0 0 24 24" width="18" height="18" fill="#409eff">
|
|
||||||
<path d="M3.5 18.49l6-6.01 4 4L22 6.92l-1.41-1.41-7.09 7.97-4-4L2 16.99z"/>
|
|
||||||
</svg>
|
|
||||||
</div>
|
|
||||||
<span class="mobile-home__section-title">常用功能</span>
|
<span class="mobile-home__section-title">常用功能</span>
|
||||||
|
<span class="mobile-home__section-more">全部 ></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mobile-home__grid" v-loading="loading">
|
<div class="mobile-home__grid" v-loading="loading">
|
||||||
@@ -42,7 +45,7 @@
|
|||||||
class="mobile-home__grid-item"
|
class="mobile-home__grid-item"
|
||||||
@click="handleShortcutClick(item.url)"
|
@click="handleShortcutClick(item.url)"
|
||||||
>
|
>
|
||||||
<div class="mobile-home__grid-icon" :style="{ color: item.color, background: item.color + '18' }">
|
<div class="mobile-home__grid-icon" :style="{ background: item.color }">
|
||||||
<Icon :icon="item.icon" />
|
<Icon :icon="item.icon" />
|
||||||
</div>
|
</div>
|
||||||
<span class="mobile-home__grid-label">{{ item.name }}</span>
|
<span class="mobile-home__grid-label">{{ item.name }}</span>
|
||||||
@@ -131,129 +134,251 @@ getAllApi()
|
|||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.mobile-home {
|
.mobile-home {
|
||||||
background: #f5f5f5;
|
background: linear-gradient(180deg, #f0f4f8 0%, #f5f7fa 100%);
|
||||||
min-height: 100%;
|
min-height: 100vh;
|
||||||
padding-bottom: 16px;
|
padding-bottom: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Banner */
|
/* Banner 区域 */
|
||||||
.mobile-home__banner {
|
.mobile-home__banner {
|
||||||
padding: 0 12px;
|
margin-bottom: 16px;
|
||||||
margin-bottom: 12px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-home__banner-bg {
|
.mobile-home__banner-bg {
|
||||||
background: linear-gradient(135deg, #43cea2 0%, #2a9d8f 50%, #264653 100%);
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
border-radius: 0 0 16px 16px;
|
border-radius: 0 0 24px 24px;
|
||||||
padding: 20px 16px 24px;
|
padding: 24px 16px 32px;
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
box-shadow: 0 4px 20px rgba(102, 126, 234, 0.3);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-home__banner-content {
|
.mobile-home__banner-content {
|
||||||
display: flex;
|
position: relative;
|
||||||
justify-content: space-between;
|
z-index: 2;
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-home__brand {
|
.mobile-home__brand {
|
||||||
display: flex;
|
color: #fff;
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-home__brand-logo {
|
.mobile-home__brand-logo {
|
||||||
font-size: 28px;
|
display: flex;
|
||||||
font-weight: 800;
|
align-items: center;
|
||||||
color: #fff;
|
gap: 12px;
|
||||||
letter-spacing: 2px;
|
margin-bottom: 16px;
|
||||||
text-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
||||||
}
|
}
|
||||||
.mobile-home__brand-text {
|
|
||||||
|
.mobile-home__logo-icon {
|
||||||
|
width: 48px;
|
||||||
|
height: 48px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 700;
|
||||||
color: #fff;
|
color: #fff;
|
||||||
|
border: 2px solid rgba(255, 255, 255, 0.3);
|
||||||
|
object-fit: contain;
|
||||||
|
padding: 6px;
|
||||||
}
|
}
|
||||||
.mobile-home__brand-title {
|
|
||||||
font-size: 16px;
|
.mobile-home__logo-text {
|
||||||
font-weight: 600;
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-home__logo-title {
|
||||||
|
font-size: 22px;
|
||||||
|
font-weight: 700;
|
||||||
margin-bottom: 2px;
|
margin-bottom: 2px;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
}
|
}
|
||||||
.mobile-home__brand-sub {
|
|
||||||
font-size: 12px;
|
.mobile-home__logo-subtitle {
|
||||||
opacity: 0.8;
|
font-size: 10px;
|
||||||
|
opacity: 0.85;
|
||||||
|
letter-spacing: 0.3px;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
.mobile-home__banner-deco {
|
|
||||||
opacity: 0.7;
|
.mobile-home__brand-desc {
|
||||||
flex-shrink: 0;
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-home__brand-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-home__brand-tags {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
padding: 4px 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.2);
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
border-radius: 12px;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 500;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Banner 装饰圆圈 */
|
||||||
|
.mobile-home__banner-circles {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
z-index: 1;
|
||||||
|
|
||||||
|
.circle {
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
|
||||||
|
&.circle-1 {
|
||||||
|
width: 120px;
|
||||||
|
height: 120px;
|
||||||
|
top: -40px;
|
||||||
|
right: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.circle-2 {
|
||||||
|
width: 80px;
|
||||||
|
height: 80px;
|
||||||
|
bottom: -20px;
|
||||||
|
right: 60px;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.circle-3 {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
top: 50%;
|
||||||
|
right: -10px;
|
||||||
|
background: rgba(255, 255, 255, 0.06);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 功能区 */
|
/* 功能区 */
|
||||||
.mobile-home__section {
|
.mobile-home__section {
|
||||||
margin: 0 12px;
|
margin: 0 12px;
|
||||||
background: #fff;
|
background: #fff;
|
||||||
border-radius: 12px;
|
border-radius: 16px;
|
||||||
padding: 14px;
|
padding: 16px;
|
||||||
box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-home__section-header {
|
.mobile-home__section-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
justify-content: space-between;
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 12px;
|
||||||
border-bottom: 2px solid #409eff;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
}
|
|
||||||
.mobile-home__section-icon {
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
background: rgba(64,158,255,0.1);
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-home__section-title {
|
.mobile-home__section-title {
|
||||||
font-size: 16px;
|
font-size: 17px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #303133;
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-home__section-more {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #909399;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 功能网格 */
|
/* 功能网格 */
|
||||||
.mobile-home__grid {
|
.mobile-home__grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: repeat(4, 1fr);
|
grid-template-columns: repeat(4, 1fr);
|
||||||
gap: 16px 8px;
|
gap: 16px 12px;
|
||||||
min-height: 100px;
|
min-height: 100px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-home__grid-item {
|
.mobile-home__grid-item {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 6px;
|
gap: 8px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
-webkit-tap-highlight-color: transparent;
|
-webkit-tap-highlight-color: transparent;
|
||||||
|
|
||||||
&:active {
|
&:active .mobile-home__grid-icon {
|
||||||
opacity: 0.7;
|
transform: scale(0.9);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-home__grid-icon {
|
.mobile-home__grid-icon {
|
||||||
width: 44px;
|
width: 56px;
|
||||||
height: 44px;
|
height: 56px;
|
||||||
border-radius: 12px;
|
border-radius: 16px;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
font-size: 22px;
|
font-size: 28px;
|
||||||
transition: transform 0.2s;
|
color: #fff;
|
||||||
|
transition: transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
&:active {
|
&::before {
|
||||||
transform: scale(0.92);
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
border-radius: 16px;
|
||||||
|
background: linear-gradient(135deg, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0) 100%);
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.mobile-home__grid-label {
|
.mobile-home__grid-label {
|
||||||
font-size: 11px;
|
font-size: 12px;
|
||||||
color: #303133;
|
color: #303133;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
max-width: 64px;
|
max-width: 70px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
white-space: nowrap;
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 2;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
white-space: normal;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 响应式调整 */
|
||||||
|
@media (max-width: 375px) {
|
||||||
|
.mobile-home__grid {
|
||||||
|
grid-template-columns: repeat(4, 1fr);
|
||||||
|
gap: 12px 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-home__grid-icon {
|
||||||
|
width: 52px;
|
||||||
|
height: 52px;
|
||||||
|
font-size: 26px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-home__grid-label {
|
||||||
|
font-size: 11px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -199,9 +199,9 @@ const loginData = reactive({
|
|||||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||||
loginForm: {
|
loginForm: {
|
||||||
tenantName: '武汉亚为电子科技有限公司',
|
tenantName: 'YAVII',
|
||||||
username: 'admin',
|
username: 'YAVII',
|
||||||
password: '123456',
|
password: 'yavii123',
|
||||||
captchaVerification: '',
|
captchaVerification: '',
|
||||||
rememberMe: false
|
rememberMe: false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,9 +158,9 @@ const loginData = reactive({
|
|||||||
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,
|
||||||
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,
|
||||||
loginForm: {
|
loginForm: {
|
||||||
tenantName: '武汉亚为电子科技有限公司',
|
tenantName: 'YAVII',
|
||||||
username: 'admin',
|
username: 'YAVII',
|
||||||
password: '123456',
|
password: 'yavii123',
|
||||||
captcha: '', // 新增验证码字段
|
captcha: '', // 新增验证码字段
|
||||||
captchaVerification: '',
|
captchaVerification: '',
|
||||||
rememberMe: true // 默认记录我。如果不需要,可手动修改
|
rememberMe: true // 默认记录我。如果不需要,可手动修改
|
||||||
|
|||||||
@@ -101,7 +101,7 @@
|
|||||||
<el-dialog v-model="versionDialogVisible" title="版本信息" width="350px" center>
|
<el-dialog v-model="versionDialogVisible" title="版本信息" width="350px" center>
|
||||||
<div class="version-info">
|
<div class="version-info">
|
||||||
<p>应用名称:MOM管理系统</p>
|
<p>应用名称:MOM管理系统</p>
|
||||||
<p>当前版本:v1.0.0</p>
|
<p>当前版本:v1.0.1</p>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button type="primary" @click="versionDialogVisible = false">确定</el-button>
|
<el-button type="primary" @click="versionDialogVisible = false">确定</el-button>
|
||||||
|
|||||||
265
src/views/aftersale/aftersaleanalysis/index.vue
Normal file
265
src/views/aftersale/aftersaleanalysis/index.vue
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="110px">
|
||||||
|
<!-- <el-form-item label="登记创建时间" prop="registerCreateTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.registerCreateTime"
|
||||||
|
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 label="处理完成时间" prop="processFinishTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.processFinishTime"
|
||||||
|
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 label="回访创建时间" prop="visitCreateTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.visitCreateTime"
|
||||||
|
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 label="统计上限" prop="pageSize">
|
||||||
|
<el-input-number v-model="queryParams.pageSize" :min="100" :max="2000" :step="100" />
|
||||||
|
</el-form-item> -->
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @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-form-item>
|
||||||
|
</el-form>
|
||||||
|
<!-- <el-alert
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
class="mt-2"
|
||||||
|
:title="`统计基于当前筛选条件的前 ${queryParams.pageSize} 条记录`"
|
||||||
|
/> -->
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<div v-loading="loading">
|
||||||
|
<ContentWrap>
|
||||||
|
<div class="mb-4 text-lg font-medium">售后登记统计</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
|
<SummaryCard title="登记总数" :value="registerSummary.total" icon="ep:document" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
||||||
|
<SummaryCard title="待审核" :value="registerSummary.pending" icon="ep:clock" icon-color="text-orange-600" icon-bg-color="bg-orange-100" />
|
||||||
|
<SummaryCard title="审核通过" :value="registerSummary.approved" icon="ep:circle-check" icon-color="text-green-600" icon-bg-color="bg-green-100" />
|
||||||
|
<SummaryCard title="审核驳回" :value="registerSummary.rejected" icon="ep:circle-close" icon-color="text-red-600" icon-bg-color="bg-red-100" />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">售后类型分布</div>
|
||||||
|
<Echart :height="320" :options="registerTypeOption" />
|
||||||
|
</el-card>
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">申请状态分布</div>
|
||||||
|
<Echart :height="320" :options="registerStatusOption" />
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<ContentWrap>
|
||||||
|
<div class="mb-4 text-lg font-medium">售后处理统计</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
|
<SummaryCard title="处理总数" :value="processSummary.total" icon="ep:setting" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
||||||
|
<SummaryCard title="处理中" :value="processSummary.processing" icon="ep:loading" icon-color="text-orange-600" icon-bg-color="bg-orange-100" />
|
||||||
|
<SummaryCard title="处理完成" :value="processSummary.completed" icon="ep:check" icon-color="text-green-600" icon-bg-color="bg-green-100" />
|
||||||
|
<SummaryCard title="处理失败" :value="processSummary.failed" icon="ep:close" icon-color="text-red-600" icon-bg-color="bg-red-100" />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">处理类型分布</div>
|
||||||
|
<Echart :height="320" :options="processTypeOption" />
|
||||||
|
</el-card>
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">处理状态分布</div>
|
||||||
|
<Echart :height="320" :options="processStatusOption" />
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<ContentWrap>
|
||||||
|
<div class="mb-4 text-lg font-medium">售后回访统计</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
|
<SummaryCard title="回访总数" :value="visitSummary.total" icon="ep:user" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
||||||
|
<SummaryCard title="平均评分" :value="visitSummary.avgRating" :decimals="1" icon="ep:star" icon-color="text-yellow-600" icon-bg-color="bg-yellow-100" />
|
||||||
|
<SummaryCard title="五星评分" :value="visitSummary.fiveStar" icon="ep:star" icon-color="text-yellow-600" icon-bg-color="bg-yellow-100" />
|
||||||
|
<SummaryCard title="强复购意愿" :value="visitSummary.highRepurchase" icon="ep:promotion" icon-color="text-green-600" icon-bg-color="bg-green-100" />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">评分分布</div>
|
||||||
|
<Echart :height="320" :options="visitRatingOption" />
|
||||||
|
</el-card>
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">复购意愿分布</div>
|
||||||
|
<Echart :height="320" :options="visitRepurchaseOption" />
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</ContentWrap>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { EChartsOption } from 'echarts'
|
||||||
|
import SummaryCard from '@/components/SummaryCard/index.vue'
|
||||||
|
import { Echart } from '@/components/Echart'
|
||||||
|
import {
|
||||||
|
AfterSaleAnalysisApi,
|
||||||
|
DistItem,
|
||||||
|
ProcessSummary,
|
||||||
|
RegisterSummary,
|
||||||
|
VisitSummary
|
||||||
|
} from '@/api/erp/aftersale/aftersaleanalysis'
|
||||||
|
|
||||||
|
defineOptions({ name: 'AfterSaleAnalysis' })
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const queryFormRef = ref()
|
||||||
|
const queryParams = reactive({
|
||||||
|
registerCreateTime: [],
|
||||||
|
processFinishTime: [],
|
||||||
|
visitCreateTime: [],
|
||||||
|
pageSize: 500
|
||||||
|
})
|
||||||
|
|
||||||
|
const registerSummary = reactive<RegisterSummary>({
|
||||||
|
total: 0,
|
||||||
|
pending: 0,
|
||||||
|
approved: 0,
|
||||||
|
rejected: 0
|
||||||
|
})
|
||||||
|
const processSummary = reactive<ProcessSummary>({
|
||||||
|
total: 0,
|
||||||
|
processing: 0,
|
||||||
|
completed: 0,
|
||||||
|
failed: 0
|
||||||
|
})
|
||||||
|
const visitSummary = reactive<VisitSummary>({
|
||||||
|
total: 0,
|
||||||
|
avgRating: 0,
|
||||||
|
fiveStar: 0,
|
||||||
|
highRepurchase: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const registerTypeDist = ref<DistItem[]>([])
|
||||||
|
const registerStatusDist = ref<DistItem[]>([])
|
||||||
|
const processTypeDist = ref<DistItem[]>([])
|
||||||
|
const processStatusDist = ref<DistItem[]>([])
|
||||||
|
const visitRatingDist = ref<DistItem[]>([])
|
||||||
|
const visitRepurchaseDist = ref<DistItem[]>([])
|
||||||
|
|
||||||
|
const buildPieOption = (data: { name: string; value: number }[]): EChartsOption => ({
|
||||||
|
tooltip: { trigger: 'item' },
|
||||||
|
legend: { bottom: 0 },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['35%', '60%'],
|
||||||
|
data,
|
||||||
|
label: { formatter: '{b}: {c}' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const buildBarOption = (labels: string[], data: number[]): EChartsOption => ({
|
||||||
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||||
|
grid: { left: 20, right: 20, bottom: 20, containLabel: true },
|
||||||
|
xAxis: { type: 'category', data: labels },
|
||||||
|
yAxis: { type: 'value', minInterval: 1 },
|
||||||
|
series: [{ type: 'bar', data, barWidth: '45%' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const registerTypeOption = computed(() => buildPieOption(registerTypeDist.value))
|
||||||
|
const registerStatusOption = computed(() =>
|
||||||
|
buildBarOption(
|
||||||
|
registerStatusDist.value.map((item) => item.name),
|
||||||
|
registerStatusDist.value.map((item) => item.value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const processTypeOption = computed(() =>
|
||||||
|
buildBarOption(
|
||||||
|
processTypeDist.value.map((item) => item.name),
|
||||||
|
processTypeDist.value.map((item) => item.value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const processStatusOption = computed(() => buildPieOption(processStatusDist.value))
|
||||||
|
const visitRatingOption = computed(() =>
|
||||||
|
buildBarOption(
|
||||||
|
visitRatingDist.value.map((item) => item.name),
|
||||||
|
visitRatingDist.value.map((item) => item.value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const visitRepurchaseOption = computed(() => buildPieOption(visitRepurchaseDist.value))
|
||||||
|
|
||||||
|
const resetSummary = () => {
|
||||||
|
Object.assign(registerSummary, { total: 0, pending: 0, approved: 0, rejected: 0 })
|
||||||
|
Object.assign(processSummary, { total: 0, processing: 0, completed: 0, failed: 0 })
|
||||||
|
Object.assign(visitSummary, { total: 0, avgRating: 0, fiveStar: 0, highRepurchase: 0 })
|
||||||
|
registerTypeDist.value = []
|
||||||
|
registerStatusDist.value = []
|
||||||
|
processTypeDist.value = []
|
||||||
|
processStatusDist.value = []
|
||||||
|
visitRatingDist.value = []
|
||||||
|
visitRepurchaseDist.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
resetSummary()
|
||||||
|
const res = await AfterSaleAnalysisApi.getAnalysis({
|
||||||
|
registerCreateTime: queryParams.registerCreateTime,
|
||||||
|
processFinishTime: queryParams.processFinishTime,
|
||||||
|
visitCreateTime: queryParams.visitCreateTime,
|
||||||
|
pageSize: queryParams.pageSize
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = (res as any).data ?? res
|
||||||
|
|
||||||
|
Object.assign(registerSummary, data.registerSummary || {})
|
||||||
|
Object.assign(processSummary, data.processSummary || {})
|
||||||
|
Object.assign(visitSummary, data.visitSummary || {})
|
||||||
|
|
||||||
|
registerTypeDist.value = data.registerTypeDist || []
|
||||||
|
registerStatusDist.value = data.registerStatusDist || []
|
||||||
|
processTypeDist.value = data.processTypeDist || []
|
||||||
|
processStatusDist.value = data.processStatusDist || []
|
||||||
|
visitRatingDist.value = data.visitRatingDist || []
|
||||||
|
visitRepurchaseDist.value = data.visitRepurchaseDist || []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value?.resetFields()
|
||||||
|
queryParams.pageSize = 500
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
407
src/views/aftersale/aftersaleprocess/AfterSaleProcessForm.vue
Normal file
407
src/views/aftersale/aftersaleprocess/AfterSaleProcessForm.vue
Normal file
@@ -0,0 +1,407 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="100px"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<el-form-item label="售后登记" prop="afterSaleId">
|
||||||
|
<el-input
|
||||||
|
v-model="selectedRegisterLabel"
|
||||||
|
placeholder="请选择已审核通过的售后登记"
|
||||||
|
readonly
|
||||||
|
@click="openRegisterSelector"
|
||||||
|
style="cursor: pointer"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button :icon="Search" @click="openRegisterSelector" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理类型" prop="processType">
|
||||||
|
<el-select v-model="formData.processType" placeholder="请选择处理类型" filterable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in processTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理数量" prop="processCount">
|
||||||
|
<el-input-number v-model="formData.processCount" :min="0" class="!w-full" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理结果" prop="processResult">
|
||||||
|
<el-input v-model="formData.processResult" placeholder="请输入处理结果" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理凭证" prop="processEvidence">
|
||||||
|
<UploadImgs v-model="processEvidenceList" :limit="5" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="业务订单" prop="relatedOrderId">
|
||||||
|
<el-input v-model="formData.relatedOrderId" placeholder="请输入业务订单" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理人" prop="processUser">
|
||||||
|
<el-input v-model="formData.processUser" placeholder="请输入处理人" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="时间" prop="processTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formData.processTime as any"
|
||||||
|
type="datetime"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
placeholder="选择时间"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="formData.processStatus === 2" label="处理完成时间" prop="finishTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formData.finishTime as any"
|
||||||
|
type="datetime"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
placeholder="选择处理完成时间"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="评价" prop="userRating">
|
||||||
|
<el-rate
|
||||||
|
v-model="formData.userRating"
|
||||||
|
:max="10"
|
||||||
|
class="!w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- 售后登记选择弹窗 -->
|
||||||
|
<Dialog v-model="registerSelectorVisible" title="选择售后登记" width="70%" :appendToBody="true">
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form
|
||||||
|
ref="registerQueryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
:model="registerQueryParams"
|
||||||
|
class="-mb-15px"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item label="售后类型" prop="afterSaleType">
|
||||||
|
<el-select
|
||||||
|
v-model="registerQueryParams.afterSaleType"
|
||||||
|
placeholder="请选择售后类型"
|
||||||
|
clearable
|
||||||
|
class="!w-200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in afterSaleTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="销售订单ID" prop="orderId">
|
||||||
|
<el-input
|
||||||
|
v-model="registerQueryParams.orderId"
|
||||||
|
class="!w-200px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入销售订单ID"
|
||||||
|
@keyup.enter="handleRegisterQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="handleRegisterQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:search" />
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetRegisterQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:refresh" />
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table v-loading="registerLoading" :data="registerList" show-overflow-tooltip>
|
||||||
|
<el-table-column label="#" width="55">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-radio :value="row.id" v-model="selectedRegisterId" @change="handleRegisterSelected(row)">
|
||||||
|
|
||||||
|
</el-radio>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="登记ID" align="center" prop="id" width="100" />
|
||||||
|
<el-table-column label="销售订单ID" align="center" prop="orderId" width="120" />
|
||||||
|
<el-table-column label="售后类型" align="center" prop="afterSaleType" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatAfterSaleType(row.afterSaleType) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="申请原因" align="center" prop="applyReason" min-width="150" />
|
||||||
|
<el-table-column label="申请人" align="center" prop="applicant" width="100" />
|
||||||
|
<el-table-column
|
||||||
|
label="创建时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="160px"
|
||||||
|
/>
|
||||||
|
</el-table>
|
||||||
|
<Pagination
|
||||||
|
v-model:limit="registerQueryParams.pageSize"
|
||||||
|
v-model:page="registerQueryParams.pageNo"
|
||||||
|
:total="registerTotal"
|
||||||
|
@pagination="getRegisterList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Search } from '@element-plus/icons-vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import { AfterSaleProcessApi, AfterSaleProcess } from '@/api/erp/aftersale/aftersaleprocess'
|
||||||
|
import { AfterSaleRegisterApi, AfterSaleRegister } from '@/api/erp/aftersale/aftersaleregister'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
|
||||||
|
/** ERP 售后处理 表单 */
|
||||||
|
defineOptions({ name: 'AfterSaleProcessForm' })
|
||||||
|
|
||||||
|
const processTypeOptions = [
|
||||||
|
{ label: '退货入库', value: 1 },
|
||||||
|
{ label: '换货出库', value: 2 },
|
||||||
|
{ label: '维修处理', value: 3 },
|
||||||
|
{ label: '退款处理', value: 4 },
|
||||||
|
{ label: '取消处理', value: 5 },
|
||||||
|
{ label: '其他', value: 6 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const processStatusOptions = [
|
||||||
|
{ label: '退换货', value: 1 },
|
||||||
|
{ label: '维修申请', value: 2 },
|
||||||
|
{ label: '售后评价', value: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const processEvidenceList = ref<string[]>([])
|
||||||
|
|
||||||
|
// 售后登记选择器相关
|
||||||
|
const registerSelectorVisible = ref(false)
|
||||||
|
const selectedRegisterId = ref<number>()
|
||||||
|
const selectedRegisterLabel = ref<string>('')
|
||||||
|
const registerLoading = ref(false)
|
||||||
|
const registerList = ref<AfterSaleRegister[]>([])
|
||||||
|
const registerTotal = ref(0)
|
||||||
|
const registerQueryParams = ref({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
applyStatus: 2, // 仅已审核通过
|
||||||
|
afterSaleType: undefined,
|
||||||
|
orderId: undefined
|
||||||
|
})
|
||||||
|
const registerQueryFormRef = ref()
|
||||||
|
|
||||||
|
const afterSaleTypeOptions = [
|
||||||
|
{ label: '退货', value: 1 },
|
||||||
|
{ label: '换货', value: 2 },
|
||||||
|
{ label: '维修', value: 3 },
|
||||||
|
{ label: '退款', value: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 打开售后登记选择器 */
|
||||||
|
const openRegisterSelector = () => {
|
||||||
|
registerSelectorVisible.value = true
|
||||||
|
registerQueryParams.value = {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
applyStatus: 2,
|
||||||
|
afterSaleType: undefined,
|
||||||
|
orderId: undefined
|
||||||
|
}
|
||||||
|
getRegisterList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询售后登记列表 */
|
||||||
|
const getRegisterList = async () => {
|
||||||
|
registerLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = await AfterSaleRegisterApi.getAfterSaleRegisterPage(registerQueryParams.value)
|
||||||
|
registerList.value = data.list
|
||||||
|
registerTotal.value = data.total
|
||||||
|
} finally {
|
||||||
|
registerLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索售后登记 */
|
||||||
|
const handleRegisterQuery = () => {
|
||||||
|
registerQueryParams.value.pageNo = 1
|
||||||
|
getRegisterList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置售后登记查询 */
|
||||||
|
const resetRegisterQuery = () => {
|
||||||
|
registerQueryParams.value = {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
applyStatus: 2,
|
||||||
|
afterSaleType: undefined,
|
||||||
|
orderId: undefined
|
||||||
|
}
|
||||||
|
getRegisterList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选中售后登记 */
|
||||||
|
const handleRegisterSelected = (row: AfterSaleRegister) => {
|
||||||
|
formData.value.afterSaleId = row.id
|
||||||
|
selectedRegisterLabel.value = `登记 #${row.id}(订单 ${row.orderId || '-'})`
|
||||||
|
selectedRegisterId.value = row.id
|
||||||
|
registerSelectorVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 格式化售后类型 */
|
||||||
|
const formatAfterSaleType = (value?: number) => {
|
||||||
|
if (!value) return '-'
|
||||||
|
const match = afterSaleTypeOptions.find(item => item.value === value)
|
||||||
|
return match?.label || String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const userStore = useUserStoreWithOut()
|
||||||
|
const currentUserNickname = computed(() => userStore.getUser?.nickname || '')
|
||||||
|
const nowString = () => dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
const formData = ref<Partial<AfterSaleProcess>>({
|
||||||
|
id: undefined,
|
||||||
|
afterSaleId: undefined,
|
||||||
|
processType: undefined,
|
||||||
|
processCount: undefined,
|
||||||
|
processResult: undefined,
|
||||||
|
processEvidence: '',
|
||||||
|
relatedOrderId: undefined,
|
||||||
|
processStatus: undefined,
|
||||||
|
processFailReason: undefined,
|
||||||
|
processUser: undefined,
|
||||||
|
processTime: undefined,
|
||||||
|
finishTime: undefined,
|
||||||
|
userRating: undefined
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
afterSaleId: [{ required: true, message: '售后登记不能为空', trigger: 'change' }],
|
||||||
|
processType: [{ required: true, message: '处理类型不能为空', trigger: 'change' }],
|
||||||
|
processResult: [{ required: true, message: '处理结果不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
// 同步处理凭证列表和表单数据
|
||||||
|
watch(
|
||||||
|
() => formData.value.processEvidence,
|
||||||
|
(newVal: string) => {
|
||||||
|
processEvidenceList.value = newVal ? newVal.split(',').filter(url => url.trim()) : []
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
processEvidenceList,
|
||||||
|
(newList: string[]) => {
|
||||||
|
formData.value.processEvidence = newList?.length ? newList.join(',') : ''
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
formData.value = await AfterSaleProcessApi.getAfterSaleProcess(id)
|
||||||
|
// 如果有售后登记ID,获取登记信息显示
|
||||||
|
if (formData.value.afterSaleId) {
|
||||||
|
const register = await AfterSaleRegisterApi.getAfterSaleRegister(formData.value.afterSaleId)
|
||||||
|
selectedRegisterLabel.value = `登记 #${register.id}(订单 ${register.orderId || '-'})`
|
||||||
|
selectedRegisterId.value = register.id
|
||||||
|
}
|
||||||
|
ensureDefaultsForExisting()
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单
|
||||||
|
await formRef.value.validate()
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
let data = formData.value as unknown as AfterSaleProcess
|
||||||
|
|
||||||
|
// 如果上传了处理凭证,自动设置为处理完成状态
|
||||||
|
if (data.processEvidence && data.processEvidence.trim() !== '') {
|
||||||
|
data = {
|
||||||
|
...data,
|
||||||
|
processStatus: 2, // 处理完成
|
||||||
|
finishTime: nowString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await AfterSaleProcessApi.createAfterSaleProcess(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await AfterSaleProcessApi.updateAfterSaleProcess(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
afterSaleId: undefined,
|
||||||
|
processType: undefined,
|
||||||
|
processCount: undefined,
|
||||||
|
processResult: undefined,
|
||||||
|
processEvidence: '',
|
||||||
|
relatedOrderId: undefined,
|
||||||
|
processStatus: 1, // 新增时默认为处理中状态
|
||||||
|
processFailReason: undefined,
|
||||||
|
processUser: currentUserNickname.value || undefined,
|
||||||
|
processTime: nowString(),
|
||||||
|
finishTime: undefined,
|
||||||
|
userRating: undefined
|
||||||
|
}
|
||||||
|
selectedRegisterLabel.value = ''
|
||||||
|
selectedRegisterId.value = undefined
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const ensureDefaultsForExisting = () => {
|
||||||
|
if (!formData.value.processUser) {
|
||||||
|
formData.value.processUser = currentUserNickname.value || undefined
|
||||||
|
}
|
||||||
|
if (!formData.value.processTime) {
|
||||||
|
formData.value.processTime = nowString()
|
||||||
|
}
|
||||||
|
if (formData.value.processStatus === 2 && !formData.value.finishTime) {
|
||||||
|
formData.value.finishTime = nowString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
405
src/views/aftersale/aftersaleprocess/index.vue
Normal file
405
src/views/aftersale/aftersaleprocess/index.vue
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-form-item label="售后登记号" prop="afterSaleId">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.afterSaleId"
|
||||||
|
placeholder="请输入售后登记号"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理类型" prop="processType">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.processType"
|
||||||
|
placeholder="请选择处理类型"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in processTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理结果" prop="processResult">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.processResult"
|
||||||
|
placeholder="请输入处理结果"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理状态" prop="processStatus">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.processStatus"
|
||||||
|
placeholder="请选择处理状态"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in processStatusOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理人" prop="processUser">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.processUser"
|
||||||
|
placeholder="请输入处理人"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理完成时间" prop="finishTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.finishTime"
|
||||||
|
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
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="openForm('create')"
|
||||||
|
v-hasPermi="['erp:after-sale-process:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['erp:after-sale-process:export']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:disabled="isEmpty(checkedIds)"
|
||||||
|
@click="handleDeleteBatch"
|
||||||
|
v-hasPermi="['erp:after-sale-process:delete']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table
|
||||||
|
row-key="id"
|
||||||
|
v-loading="loading"
|
||||||
|
:data="list"
|
||||||
|
:stripe="true"
|
||||||
|
:show-overflow-tooltip="true"
|
||||||
|
@selection-change="handleRowCheckboxChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column label="序号" align="center" type="index" width="80" />
|
||||||
|
<el-table-column
|
||||||
|
label="处理类型"
|
||||||
|
align="center"
|
||||||
|
prop="processType"
|
||||||
|
:formatter="formatProcessType"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="处理状态"
|
||||||
|
align="center"
|
||||||
|
prop="processStatus"
|
||||||
|
:formatter="formatProcessStatus"
|
||||||
|
/>
|
||||||
|
<el-table-column label="处理人" align="center" prop="processUser" />
|
||||||
|
<el-table-column label="用户评价" align="center" prop="userRating">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tooltip :content="`${scope.row.userRating || 0} 分`" placement="top">
|
||||||
|
<el-icon size="20" :style="{ color: getRatingColor(scope.row.userRating) }">
|
||||||
|
<Star />
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="创建时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="180px"
|
||||||
|
/>
|
||||||
|
<el-table-column label="操作" align="center" min-width="200px">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" @click="openDetail(scope.row)">详情</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sale-process:update']"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="success"
|
||||||
|
@click="handleProcessComplete(scope.row)"
|
||||||
|
v-if="scope.row.processStatus === 1"
|
||||||
|
v-hasPermi="['erp:after-sale-process:update']"
|
||||||
|
>
|
||||||
|
处理完成
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sale-process:delete']"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination
|
||||||
|
:total="total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<AfterSaleProcessForm ref="formRef" @success="getList" />
|
||||||
|
|
||||||
|
<!-- 详情弹窗 -->
|
||||||
|
<el-dialog v-model="detailVisible" title="售后处理详情" width="560px">
|
||||||
|
<el-descriptions v-if="detailRow" :column="2" border>
|
||||||
|
<el-descriptions-item label="售后登记号">{{ detailRow.afterSaleId }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理类型">{{ formatProcessType(detailRow, null as any, detailRow.processType as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理数量">{{ detailRow.processCount ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="业务订单号">{{ detailRow.relatedOrderId ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理结果">{{ detailRow.processResult ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="失败原因">{{ detailRow.processFailReason ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理状态">{{ formatProcessStatus(detailRow, null as any, detailRow.processStatus as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理人">{{ detailRow.processUser ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理时间">{{ dateFormatter(detailRow, null as any, detailRow.processTime as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理完成时间">{{ dateFormatter(detailRow, null as any, detailRow.finishTime as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="用户评价">
|
||||||
|
<el-tooltip :content="`${detailRow.userRating || 0} 分`" placement="top">
|
||||||
|
<el-icon size="20" :style="{ color: getRatingColor(detailRow.userRating) }">
|
||||||
|
<Star />
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建时间">{{ dateFormatter(detailRow, null as any, (detailRow as any).createTime) }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="detailVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
import { AfterSaleProcessApi, AfterSaleProcess } from '@/api/erp/aftersale/aftersaleprocess'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
import AfterSaleProcessForm from './AfterSaleProcessForm.vue'
|
||||||
|
import { Star } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const processTypeOptions = [
|
||||||
|
{ label: '退货入库', value: 1 },
|
||||||
|
{ label: '换货出库', value: 2 },
|
||||||
|
{ label: '维修处理', value: 3 },
|
||||||
|
{ label: '退款处理', value: 4 },
|
||||||
|
{ label: '取消处理', value: 5 },
|
||||||
|
{ label: '其他', value: 6 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const processStatusOptions = [
|
||||||
|
{ label: '处理中', value: 1 },
|
||||||
|
{ label: '处理完成', value: 2 },
|
||||||
|
{ label: '处理失败', value: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const optionLabel = (options: { label: string; value: number }[], value?: number | string) => {
|
||||||
|
if (value === undefined || value === null || value === '') return '-'
|
||||||
|
const match = options.find((item) => item.value === value || item.value === Number(value))
|
||||||
|
return match?.label ?? String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatProcessType = (_row: AfterSaleProcess, _column: any, value: number) =>
|
||||||
|
optionLabel(processTypeOptions, value)
|
||||||
|
const formatProcessStatus = (_row: AfterSaleProcess, _column: any, value: number) =>
|
||||||
|
optionLabel(processStatusOptions, value)
|
||||||
|
|
||||||
|
/** ERP 售后处理 列表 */
|
||||||
|
defineOptions({ name: 'AfterSaleProcess' })
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const userStore = useUserStoreWithOut()
|
||||||
|
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref<AfterSaleProcess[]>([]) // 列表的数据
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
afterSaleId: undefined,
|
||||||
|
processType: undefined,
|
||||||
|
processCount: undefined,
|
||||||
|
processResult: undefined,
|
||||||
|
processEvidence: undefined,
|
||||||
|
relatedOrderId: undefined,
|
||||||
|
processStatus: undefined,
|
||||||
|
processFailReason: undefined,
|
||||||
|
processUser: undefined,
|
||||||
|
processTime: [],
|
||||||
|
finishTime: [],
|
||||||
|
createTime: [],
|
||||||
|
userRating: undefined
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await AfterSaleProcessApi.getAfterSaleProcessPage(queryParams)
|
||||||
|
list.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加/修改操作 */
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await AfterSaleProcessApi.deleteAfterSaleProcess(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理完成操作 */
|
||||||
|
const handleProcessComplete = async (row: AfterSaleProcess) => {
|
||||||
|
try {
|
||||||
|
// 处理完成的二次确认
|
||||||
|
await message.confirm('确认处理完成吗?')
|
||||||
|
// 更新状态为处理完成
|
||||||
|
const updateData = {
|
||||||
|
...row,
|
||||||
|
processStatus: 2,
|
||||||
|
processUser: userStore.getUser?.nickname || row.processUser,
|
||||||
|
finishTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
await AfterSaleProcessApi.updateAfterSaleProcess(updateData)
|
||||||
|
message.success('处理完成')
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量删除ERP 售后处理 */
|
||||||
|
const handleDeleteBatch = async () => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
await AfterSaleProcessApi.deleteAfterSaleProcessList(checkedIds.value);
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
await getList();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedIds = ref<number[]>([])
|
||||||
|
const handleRowCheckboxChange = (records: AfterSaleProcess[]) => {
|
||||||
|
checkedIds.value = records.map((item) => item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailVisible = ref(false)
|
||||||
|
const detailRow = ref<AfterSaleProcess | null>(null)
|
||||||
|
const openDetail = (row: AfterSaleProcess) => {
|
||||||
|
detailRow.value = row
|
||||||
|
detailVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await message.exportConfirm()
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await AfterSaleProcessApi.exportAfterSaleProcess(queryParams)
|
||||||
|
download.excel(data, 'ERP 售后处理.xls')
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据评分获取颜色 */
|
||||||
|
const getRatingColor = (rating?: number) => {
|
||||||
|
if (!rating || rating === 0) return '#cccccc' // 灰色表示无评价
|
||||||
|
if (rating < 4) return '#ff4d4f' // 红色表示差评
|
||||||
|
if (rating < 7) return '#ffa940' // 橙色表示一般
|
||||||
|
if (rating < 9) return '#52c41a' // 绿色表示好评
|
||||||
|
return '#1890ff' // 蓝色表示优秀
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
307
src/views/aftersale/aftersaleregister/AfterSaleRegisterForm.vue
Normal file
307
src/views/aftersale/aftersaleregister/AfterSaleRegisterForm.vue
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="100px"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<el-form-item label="销售订单" prop="orderId">
|
||||||
|
<el-input
|
||||||
|
v-model="selectedOrderNo"
|
||||||
|
placeholder="请选择销售订单"
|
||||||
|
readonly
|
||||||
|
@click="openOrderSelector"
|
||||||
|
style="cursor: pointer"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button :icon="Search" @click="openOrderSelector" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="售后类型" prop="afterSaleType">
|
||||||
|
<el-select v-model="formData.afterSaleType" placeholder="请选择售后类型" filterable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in afterSaleTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="申请原因" prop="applyReason">
|
||||||
|
<el-input v-model="formData.applyReason" placeholder="请输入申请原因" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="联系人" prop="contactName">
|
||||||
|
<el-input v-model="formData.contactName" placeholder="请输入联系人" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="联系电话" prop="contactPhone">
|
||||||
|
<el-input v-model="formData.contactPhone" placeholder="请输入联系电话" maxlength="20" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="申请人" prop="applicant">
|
||||||
|
<el-input v-model="formData.applicant" placeholder="请输入申请人" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- 销售订单选择弹窗 -->
|
||||||
|
<Dialog v-model="orderSelectorVisible" title="选择销售订单" width="70%" :appendToBody="true">
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form
|
||||||
|
ref="orderQueryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
:model="orderQueryParams"
|
||||||
|
class="-mb-15px"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item label="订单单号" prop="no">
|
||||||
|
<el-input
|
||||||
|
v-model="orderQueryParams.no"
|
||||||
|
class="!w-200px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入订单单号"
|
||||||
|
@keyup.enter="handleOrderQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="handleOrderQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:search" />
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetOrderQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:refresh" />
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table v-loading="orderLoading" :data="orderList" show-overflow-tooltip>
|
||||||
|
<el-table-column label="#" width="55">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-radio :value="row.id" v-model="selectedOrderId" @change="handleOrderSelected(row)">
|
||||||
|
|
||||||
|
</el-radio>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="订单单号" align="center" prop="no" min-width="140" />
|
||||||
|
<el-table-column label="客户" align="center" prop="customerName" min-width="120" />
|
||||||
|
<el-table-column
|
||||||
|
label="订单时间"
|
||||||
|
align="center"
|
||||||
|
prop="orderTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="160px"
|
||||||
|
/>
|
||||||
|
</el-table>
|
||||||
|
<Pagination
|
||||||
|
v-model:limit="orderQueryParams.pageSize"
|
||||||
|
v-model:page="orderQueryParams.pageNo"
|
||||||
|
:total="orderTotal"
|
||||||
|
@pagination="getOrderList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Search } from '@element-plus/icons-vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import { AfterSaleRegisterApi, AfterSaleRegister } from '@/api/erp/aftersale/aftersaleregister'
|
||||||
|
import { SaleOrderApi, SaleOrderVO } from '@/api/erp/sale/order'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
|
||||||
|
/** ERP 售后登记 表单 */
|
||||||
|
defineOptions({ name: 'AfterSaleRegisterForm' })
|
||||||
|
|
||||||
|
const afterSaleTypeOptions = [
|
||||||
|
{ label: '退货', value: 1 },
|
||||||
|
{ label: '换货', value: 2 },
|
||||||
|
{ label: '维修', value: 3 },
|
||||||
|
{ label: '退款', value: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const applyStatusOptions = [
|
||||||
|
{ label: '待审核', value: 1 },
|
||||||
|
{ label: '审核通过', value: 2 },
|
||||||
|
{ label: '审核驳回', value: 3 },
|
||||||
|
{ label: '已取消', value: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 销售订单选择器相关
|
||||||
|
const orderSelectorVisible = ref(false)
|
||||||
|
const selectedOrderId = ref<number>()
|
||||||
|
const selectedOrderNo = ref<string>('')
|
||||||
|
const orderLoading = ref(false)
|
||||||
|
const orderList = ref<SaleOrderVO[]>([])
|
||||||
|
const orderTotal = ref(0)
|
||||||
|
const orderQueryParams = ref({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
no: ''
|
||||||
|
})
|
||||||
|
const orderQueryFormRef = ref()
|
||||||
|
|
||||||
|
/** 打开订单选择器 */
|
||||||
|
const openOrderSelector = () => {
|
||||||
|
orderSelectorVisible.value = true
|
||||||
|
orderQueryParams.value = {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
no: ''
|
||||||
|
}
|
||||||
|
getOrderList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询订单列表 */
|
||||||
|
const getOrderList = async () => {
|
||||||
|
orderLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = await SaleOrderApi.getSaleOrderPage(orderQueryParams.value)
|
||||||
|
orderList.value = data.list
|
||||||
|
orderTotal.value = data.total
|
||||||
|
} finally {
|
||||||
|
orderLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索订单 */
|
||||||
|
const handleOrderQuery = () => {
|
||||||
|
orderQueryParams.value.pageNo = 1
|
||||||
|
getOrderList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置订单查询 */
|
||||||
|
const resetOrderQuery = () => {
|
||||||
|
orderQueryParams.value = {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
no: ''
|
||||||
|
}
|
||||||
|
getOrderList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选中订单 */
|
||||||
|
const handleOrderSelected = (row: SaleOrderVO) => {
|
||||||
|
formData.value.orderId = row.id
|
||||||
|
selectedOrderNo.value = row.no || ''
|
||||||
|
selectedOrderId.value = row.id
|
||||||
|
orderSelectorVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const userStore = useUserStoreWithOut()
|
||||||
|
const currentUserNickname = computed(() => userStore.getUser?.nickname || '')
|
||||||
|
const nowString = () => dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
const formData = ref<Partial<AfterSaleRegister>>({
|
||||||
|
id: undefined,
|
||||||
|
orderId: undefined,
|
||||||
|
orderItemId: undefined,
|
||||||
|
afterSaleType: undefined,
|
||||||
|
applyReason: undefined,
|
||||||
|
contactName: undefined,
|
||||||
|
contactPhone: undefined,
|
||||||
|
applyStatus: undefined,
|
||||||
|
rejectReason: undefined,
|
||||||
|
applicant: undefined,
|
||||||
|
auditUser: undefined,
|
||||||
|
auditTime: undefined
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
afterSaleType: [{ required: true, message: '售后类型不能为空', trigger: 'change' }],
|
||||||
|
applyReason: [{ required: true, message: '申请原因不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
formData.value = await AfterSaleRegisterApi.getAfterSaleRegister(id)
|
||||||
|
// 如果有订单ID,获取订单号显示
|
||||||
|
if (formData.value.orderId) {
|
||||||
|
const order = await SaleOrderApi.getSaleOrder(formData.value.orderId)
|
||||||
|
selectedOrderNo.value = order.no || ''
|
||||||
|
selectedOrderId.value = order.id
|
||||||
|
}
|
||||||
|
ensureDefaultsForExisting()
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单(当前无必填规则,直接通过)
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = formData.value as unknown as AfterSaleRegister
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await AfterSaleRegisterApi.createAfterSaleRegister(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await AfterSaleRegisterApi.updateAfterSaleRegister(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
orderId: undefined,
|
||||||
|
orderItemId: undefined,
|
||||||
|
afterSaleType: undefined,
|
||||||
|
applyReason: undefined,
|
||||||
|
contactName: undefined,
|
||||||
|
contactPhone: undefined,
|
||||||
|
applyStatus: 1, // 新增时默认为待审核状态
|
||||||
|
rejectReason: undefined,
|
||||||
|
applicant: currentUserNickname.value || undefined,
|
||||||
|
auditUser: undefined,
|
||||||
|
auditTime: undefined
|
||||||
|
}
|
||||||
|
selectedOrderNo.value = ''
|
||||||
|
selectedOrderId.value = undefined
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const ensureDefaultsForExisting = () => {
|
||||||
|
if (!formData.value.applicant) {
|
||||||
|
formData.value.applicant = currentUserNickname.value || undefined
|
||||||
|
}
|
||||||
|
if (formData.value.applyStatus === 2) {
|
||||||
|
if (!formData.value.auditUser) {
|
||||||
|
formData.value.auditUser = currentUserNickname.value || undefined
|
||||||
|
}
|
||||||
|
if (!formData.value.auditTime) {
|
||||||
|
formData.value.auditTime = nowString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
391
src/views/aftersale/aftersaleregister/index.vue
Normal file
391
src/views/aftersale/aftersaleregister/index.vue
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-form-item label="销售订单" prop="orderId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.orderId"
|
||||||
|
placeholder="请选择销售订单"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in saleOrderOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.no"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="售后类型" prop="afterSaleType">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.afterSaleType"
|
||||||
|
placeholder="请选择售后类型"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in afterSaleTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="申请原因" prop="applyReason">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.applyReason"
|
||||||
|
placeholder="请输入申请原因"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="申请状态" prop="applyStatus">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.applyStatus"
|
||||||
|
placeholder="请选择申请状态"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in applyStatusOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="申请人" prop="applicant">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.applicant"
|
||||||
|
placeholder="请输入申请人"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="审核人" prop="auditUser">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.auditUser"
|
||||||
|
placeholder="请输入审核人"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</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
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="openForm('create')"
|
||||||
|
v-hasPermi="['erp:after-sale-register:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['erp:after-sale-register:export']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:disabled="isEmpty(checkedIds)"
|
||||||
|
@click="handleDeleteBatch"
|
||||||
|
v-hasPermi="['erp:after-sale-register:delete']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table
|
||||||
|
row-key="id"
|
||||||
|
v-loading="loading"
|
||||||
|
:data="list"
|
||||||
|
:stripe="true"
|
||||||
|
:show-overflow-tooltip="true"
|
||||||
|
@selection-change="handleRowCheckboxChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column label="序号" align="center" type="index" width="80" />
|
||||||
|
<el-table-column
|
||||||
|
label="售后类型"
|
||||||
|
align="center"
|
||||||
|
prop="afterSaleType"
|
||||||
|
:formatter="formatAfterSaleType"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="申请状态"
|
||||||
|
align="center"
|
||||||
|
prop="applyStatus"
|
||||||
|
:formatter="formatApplyStatus"
|
||||||
|
/>
|
||||||
|
<el-table-column label="申请人" align="center" prop="applicant" />
|
||||||
|
<el-table-column label="审核人" align="center" prop="auditUser" />
|
||||||
|
<el-table-column
|
||||||
|
label="创建时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="180px"
|
||||||
|
/>
|
||||||
|
<el-table-column label="操作" align="center" min-width="200px">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" @click="openDetail(scope.row)">详情</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sale-register:update']"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="success"
|
||||||
|
@click="handleAudit(scope.row)"
|
||||||
|
v-if="scope.row.applyStatus === 1"
|
||||||
|
v-hasPermi="['erp:after-sale-register:update']"
|
||||||
|
>
|
||||||
|
审核通过
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sale-register:delete']"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination
|
||||||
|
:total="total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<AfterSaleRegisterForm ref="formRef" @success="getList" />
|
||||||
|
|
||||||
|
<!-- 详情弹窗 -->
|
||||||
|
<el-dialog v-model="detailVisible" title="售后登记详情" width="560px">
|
||||||
|
<el-descriptions v-if="detailRow" :column="2" border>
|
||||||
|
<el-descriptions-item label="销售订单">{{ detailRow.orderId }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="售后类型">{{ formatAfterSaleType(detailRow, null as any, detailRow.afterSaleType as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="申请原因">{{ detailRow.applyReason ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="联系人">{{ detailRow.contactName ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="联系电话">{{ detailRow.contactPhone ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="申请状态">{{ formatApplyStatus(detailRow, null as any, detailRow.applyStatus as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="驳回原因">{{ detailRow.rejectReason ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="申请人">{{ detailRow.applicant ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="审核人">{{ detailRow.auditUser ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="审核时间">{{ dateFormatter(detailRow, null as any, detailRow.auditTime as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建时间">{{ dateFormatter(detailRow, null as any, (detailRow as any).createTime) }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="detailVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
import { AfterSaleRegisterApi, AfterSaleRegister } from '@/api/erp/aftersale/aftersaleregister'
|
||||||
|
import { SaleOrderApi, SaleOrderVO } from '@/api/erp/sale/order'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
import AfterSaleRegisterForm from './AfterSaleRegisterForm.vue'
|
||||||
|
|
||||||
|
const afterSaleTypeOptions = [
|
||||||
|
{ label: '退货', value: 1 },
|
||||||
|
{ label: '换货', value: 2 },
|
||||||
|
{ label: '维修', value: 3 },
|
||||||
|
{ label: '退款', value: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const applyStatusOptions = [
|
||||||
|
{ label: '待审核', value: 1 },
|
||||||
|
{ label: '审核通过', value: 2 },
|
||||||
|
{ label: '审核驳回', value: 3 },
|
||||||
|
{ label: '已取消', value: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const optionLabel = (options: { label: string; value: number }[], value?: number | string) => {
|
||||||
|
if (value === undefined || value === null || value === '') return '-'
|
||||||
|
const match = options.find((item) => item.value === value || item.value === Number(value))
|
||||||
|
return match?.label ?? String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatAfterSaleType = (_row: AfterSaleRegister, _column: any, value: number) =>
|
||||||
|
optionLabel(afterSaleTypeOptions, value)
|
||||||
|
const formatApplyStatus = (_row: AfterSaleRegister, _column: any, value: number) =>
|
||||||
|
optionLabel(applyStatusOptions, value)
|
||||||
|
|
||||||
|
const saleOrderOptions = ref<SaleOrderVO[]>([])
|
||||||
|
const fetchSaleOrderOptions = async () => {
|
||||||
|
const data = await SaleOrderApi.getSaleOrderPage({ pageNo: 1, pageSize: 100 })
|
||||||
|
saleOrderOptions.value = data.list || []
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ERP 售后登记 列表 */
|
||||||
|
defineOptions({ name: 'AfterSaleRegister' })
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const userStore = useUserStoreWithOut()
|
||||||
|
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref<AfterSaleRegister[]>([]) // 列表的数据
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
orderId: undefined,
|
||||||
|
orderItemId: undefined,
|
||||||
|
afterSaleType: undefined,
|
||||||
|
applyReason: undefined,
|
||||||
|
contactName: undefined,
|
||||||
|
contactPhone: undefined,
|
||||||
|
applyStatus: undefined,
|
||||||
|
rejectReason: undefined,
|
||||||
|
applicant: undefined,
|
||||||
|
auditUser: undefined,
|
||||||
|
auditTime: [],
|
||||||
|
createTime: []
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await AfterSaleRegisterApi.getAfterSaleRegisterPage(queryParams)
|
||||||
|
list.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加/修改操作 */
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await AfterSaleRegisterApi.deleteAfterSaleRegister(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 审核通过操作 */
|
||||||
|
const handleAudit = async (row: AfterSaleRegister) => {
|
||||||
|
try {
|
||||||
|
// 审核确认
|
||||||
|
await message.confirm('确认审核通过该售后登记吗?')
|
||||||
|
// 更新状态为审核通过
|
||||||
|
const updateData = {
|
||||||
|
...row,
|
||||||
|
applyStatus: 2,
|
||||||
|
auditUser: userStore.getUser?.nickname || '',
|
||||||
|
auditTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
await AfterSaleRegisterApi.updateAfterSaleRegister(updateData)
|
||||||
|
message.success('审核通过成功')
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量删除ERP 售后登记 */
|
||||||
|
const handleDeleteBatch = async () => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
await AfterSaleRegisterApi.deleteAfterSaleRegisterList(checkedIds.value);
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
await getList();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedIds = ref<number[]>([])
|
||||||
|
const handleRowCheckboxChange = (records: AfterSaleRegister[]) => {
|
||||||
|
checkedIds.value = records.map((item) => item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailVisible = ref(false)
|
||||||
|
const detailRow = ref<AfterSaleRegister | null>(null)
|
||||||
|
const openDetail = (row: AfterSaleRegister) => {
|
||||||
|
detailRow.value = row
|
||||||
|
detailVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await message.exportConfirm()
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await AfterSaleRegisterApi.exportAfterSaleRegister(queryParams)
|
||||||
|
download.excel(data, 'ERP 售后登记.xls')
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
fetchSaleOrderOptions()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
267
src/views/aftersale/aftersalesvisit/AfterSalesVisitForm.vue
Normal file
267
src/views/aftersale/aftersalesvisit/AfterSalesVisitForm.vue
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible" width="600px">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="90px"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<!-- 客户基本信息 -->
|
||||||
|
<el-divider content-position="left">客户信息</el-divider>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="客户名称" prop="customerName">
|
||||||
|
<el-select
|
||||||
|
v-model="formData.customerName"
|
||||||
|
placeholder="请选择客户"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
@change="handleCustomerChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="customer in customerList"
|
||||||
|
:key="customer.id"
|
||||||
|
:label="customer.name"
|
||||||
|
:value="customer.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="联系方式" prop="contactInfo">
|
||||||
|
<el-input v-model="formData.contactInfo" placeholder="请输入联系方式" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="客户类型" prop="customerType">
|
||||||
|
<el-select v-model="formData.customerType" placeholder="请选择客户类型" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in customerTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="客户用途" prop="customerUsage">
|
||||||
|
<el-input v-model="formData.customerUsage" placeholder="请输入客户用途" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 产品信息 -->
|
||||||
|
<el-divider content-position="left">产品信息</el-divider>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="产品名称" prop="productName">
|
||||||
|
<el-input v-model="formData.productName" placeholder="请输入产品名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="产品评价" prop="productEvaluation">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.productEvaluation"
|
||||||
|
placeholder="请输入产品评价"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 服务评价 -->
|
||||||
|
<el-divider content-position="left">服务评价</el-divider>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="采购意愿" prop="repurchaseIntention">
|
||||||
|
<el-select v-model="formData.repurchaseIntention" placeholder="请选择采购意愿" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in repurchaseOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="服务评分" prop="serviceRating">
|
||||||
|
<el-rate
|
||||||
|
v-model="formData.serviceRating"
|
||||||
|
:max="5"
|
||||||
|
:allow-half="false"
|
||||||
|
:show-text="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm" :loading="formLoading">
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { AfterSalesVisitApi, AfterSalesVisit } from '@/api/erp/aftersale/aftersalesvisit'
|
||||||
|
import { CustomerApi, CustomerVO } from '@/api/erp/sale/customer'
|
||||||
|
|
||||||
|
/** 售后回访 表单 */
|
||||||
|
defineOptions({ name: 'AfterSalesVisitForm' })
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
const formData = ref({
|
||||||
|
id: undefined,
|
||||||
|
customerName: undefined,
|
||||||
|
contactInfo: undefined,
|
||||||
|
customerUsage: undefined,
|
||||||
|
productName: undefined,
|
||||||
|
productEvaluation: undefined,
|
||||||
|
customerType: undefined,
|
||||||
|
repurchaseIntention: undefined,
|
||||||
|
serviceRating: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// 客户类型选项
|
||||||
|
const customerTypeOptions = [
|
||||||
|
{ value: 'large_food_factory', label: '大型食品厂' },
|
||||||
|
{ value: 'small_food_factory', label: '中小型加工厂' },
|
||||||
|
{ value: 'distributor', label: '经销商' },
|
||||||
|
{ value: 'catering_chain', label: '餐饮连锁' },
|
||||||
|
{ value: 'government_unit', label: '机关单位' },
|
||||||
|
{ value: 'other', label: '其他' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 重复采购意愿选项
|
||||||
|
const repurchaseOptions = [
|
||||||
|
{ value: 'definitely', label: '一定会' },
|
||||||
|
{ value: 'probably', label: '应该会' },
|
||||||
|
{ value: 'uncertain', label: '不确定' },
|
||||||
|
{ value: 'unlikely', label: '可能不会' },
|
||||||
|
{ value: 'never', label: '肯定不会' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const formRules = reactive({
|
||||||
|
customerName: [
|
||||||
|
{ required: true, message: '客户名称不能为空', trigger: 'blur' },
|
||||||
|
{ min: 2, max: 50, message: '客户名称长度应在2-50字符之间', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
contactInfo: [
|
||||||
|
{ required: true, message: '联系方式不能为空', trigger: 'blur' },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
productName: [
|
||||||
|
{ required: true, message: '产品名称不能为空', trigger: 'blur' },
|
||||||
|
{ min: 1, max: 100, message: '产品名称长度应在1-100字符之间', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
customerType: [{ required: true, message: '请选择客户类型', trigger: 'change' }],
|
||||||
|
repurchaseIntention: [{ required: true, message: '请选择采购意愿', trigger: 'change' }],
|
||||||
|
serviceRating: [
|
||||||
|
{ required: true, message: '请给出服务评分', trigger: 'change' },
|
||||||
|
{ type: 'number', min: 0, max: 5, message: '评分应在0-5之间', trigger: 'change' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
const customerList = ref<CustomerVO[]>([]) // 客户列表
|
||||||
|
|
||||||
|
/** 加载客户列表 */
|
||||||
|
const loadCustomerList = async () => {
|
||||||
|
try {
|
||||||
|
const data = await CustomerApi.getCustomerSimpleList()
|
||||||
|
customerList.value = data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载客户列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理客户选择 */
|
||||||
|
const handleCustomerChange = (customerName: string) => {
|
||||||
|
if (!customerName) {
|
||||||
|
formData.value.contactInfo = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selectedCustomer = customerList.value.find(customer => customer.name === customerName)
|
||||||
|
if (selectedCustomer) {
|
||||||
|
// 自动填充联系方式,优先使用手机号,其次电话
|
||||||
|
formData.value.contactInfo = selectedCustomer.mobile || selectedCustomer.telephone || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
formData.value = await AfterSalesVisitApi.getAfterSalesVisit(id)
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单
|
||||||
|
await formRef.value.validate()
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = formData.value as unknown as AfterSalesVisit
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await AfterSalesVisitApi.createAfterSalesVisit(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await AfterSalesVisitApi.updateAfterSalesVisit(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
customerName: undefined,
|
||||||
|
contactInfo: undefined,
|
||||||
|
customerUsage: undefined,
|
||||||
|
productName: undefined,
|
||||||
|
productEvaluation: undefined,
|
||||||
|
customerType: undefined,
|
||||||
|
repurchaseIntention: undefined,
|
||||||
|
serviceRating: undefined
|
||||||
|
}
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(() => {
|
||||||
|
loadCustomerList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
306
src/views/aftersale/aftersalesvisit/index.vue
Normal file
306
src/views/aftersale/aftersalesvisit/index.vue
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 统计卡片 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
|
<SummaryCard title="总回访数" :value="total" icon="ep:user" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
||||||
|
<SummaryCard title="平均评分" :value="avgRating" :decimals="1" icon="ep:star" icon-color="text-yellow-600"
|
||||||
|
icon-bg-color="bg-yellow-100" />
|
||||||
|
<SummaryCard title="今日新增" :value="todayCount" icon="ep:calendar" icon-color="text-green-600"
|
||||||
|
icon-bg-color="bg-green-100" />
|
||||||
|
<SummaryCard title="五星评分" :value="fiveStarCount" icon="ep:star" icon-color="text-yellow-600"
|
||||||
|
icon-bg-color="bg-yellow-100" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px">
|
||||||
|
<el-form-item label="客户名称" prop="customerName">
|
||||||
|
<el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable @keyup.enter="handleQuery"
|
||||||
|
class="!w-200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="联系方式" prop="contactInfo">
|
||||||
|
<el-input v-model="queryParams.contactInfo" placeholder="请输入联系方式" clearable @keyup.enter="handleQuery"
|
||||||
|
class="!w-200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="产品名称" prop="productName">
|
||||||
|
<el-input v-model="queryParams.productName" placeholder="请输入产品名称" clearable @keyup.enter="handleQuery"
|
||||||
|
class="!w-200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="客户类型" prop="customerType">
|
||||||
|
<el-select v-model="queryParams.customerType" placeholder="请选择客户类型" clearable class="!w-200px">
|
||||||
|
<el-option v-for="item in customerTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="采购意愿" prop="repurchaseIntention">
|
||||||
|
<el-select v-model="queryParams.repurchaseIntention" placeholder="请选择采购意愿" clearable class="!w-200px">
|
||||||
|
<el-option v-for="item in repurchaseOptions" :key="item.value" :label="item.label" :value="item.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 type="primary" @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-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="flex flex-wrap gap-2 mb-4">
|
||||||
|
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:after-sales-visit:create']">
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" plain @click="handleExport" :loading="exportLoading"
|
||||||
|
v-hasPermi="['erp:after-sales-visit:export']">
|
||||||
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" plain :disabled="isEmpty(checkedIds)" @click="handleDeleteBatch"
|
||||||
|
v-hasPermi="['erp:after-sales-visit:delete']">
|
||||||
|
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table row-key="id" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"
|
||||||
|
@selection-change="handleRowCheckboxChange" class="mb-4">
|
||||||
|
<el-table-column type="selection" width="50" align="center" />
|
||||||
|
<el-table-column label="客户名称" min-width="120" prop="customerName" show-overflow-tooltip />
|
||||||
|
<el-table-column label="联系方式" min-width="120" prop="contactInfo" show-overflow-tooltip />
|
||||||
|
<el-table-column label="产品名称" min-width="120" prop="productName" show-overflow-tooltip />
|
||||||
|
<el-table-column label="客户类型" min-width="140" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="getCustomerTypeColor(scope.row.customerType)">
|
||||||
|
{{ getCustomerTypeLabel(scope.row.customerType) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="采购意愿" min-width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<span :class="getRepurchaseColor(scope.row.repurchaseIntention)">
|
||||||
|
{{ getRepurchaseLabel(scope.row.repurchaseIntention) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="服务评分" width="120" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-rate :model-value="scope.row.serviceRating" disabled :max="5" show-score text-color="#ff9900"
|
||||||
|
score-template="{value}星" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="创建时间" width="160" align="center" prop="createTime" :formatter="dateFormatter" />
|
||||||
|
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" size="small" @click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sales-visit:update']">
|
||||||
|
<Icon icon="ep:edit" class="mr-1" />编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="danger" size="small" @click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sales-visit:delete']">
|
||||||
|
<Icon icon="ep:delete" class="mr-1" />删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<AfterSalesVisitForm ref="formRef" @success="getList" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
import SummaryCard from '@/components/SummaryCard/index.vue'
|
||||||
|
import { AfterSalesVisitApi, AfterSalesVisit } from '@/api/erp/aftersale/aftersalesvisit'
|
||||||
|
import AfterSalesVisitForm from './AfterSalesVisitForm.vue'
|
||||||
|
|
||||||
|
/** 售后回访 列表 */
|
||||||
|
defineOptions({ name: 'AfterSalesVisit' })
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref<AfterSalesVisit[]>([]) // 列表的数据
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const activeNames = ref(['search']) // 折叠面板默认展开
|
||||||
|
|
||||||
|
// 客户类型选项
|
||||||
|
const customerTypeOptions = [
|
||||||
|
{ value: 'large_food_factory', label: '大型食品厂' },
|
||||||
|
{ value: 'small_food_factory', label: '中小型加工厂' },
|
||||||
|
{ value: 'distributor', label: '经销商' },
|
||||||
|
{ value: 'catering_chain', label: '餐饮连锁' },
|
||||||
|
{ value: 'government_unit', label: '机关单位' },
|
||||||
|
{ value: 'other', label: '其他' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 重复采购意愿选项
|
||||||
|
const repurchaseOptions = [
|
||||||
|
{ value: 'definitely', label: '一定会' },
|
||||||
|
{ value: 'probably', label: '应该会' },
|
||||||
|
{ value: 'uncertain', label: '不确定' },
|
||||||
|
{ value: 'unlikely', label: '可能不会' },
|
||||||
|
{ value: 'never', label: '肯定不会' }
|
||||||
|
]
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
customerName: undefined,
|
||||||
|
contactInfo: undefined,
|
||||||
|
customerUsage: undefined,
|
||||||
|
productName: undefined,
|
||||||
|
productEvaluation: undefined,
|
||||||
|
customerType: undefined,
|
||||||
|
repurchaseIntention: undefined,
|
||||||
|
serviceRating: undefined,
|
||||||
|
createTime: []
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await AfterSalesVisitApi.getAfterSalesVisitPage(queryParams)
|
||||||
|
list.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value.resetFields()
|
||||||
|
// 重置评分查询条件
|
||||||
|
queryParams.serviceRating = undefined
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加/修改操作 */
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await AfterSalesVisitApi.deleteAfterSalesVisit(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量删除售后回访 */
|
||||||
|
const handleDeleteBatch = async () => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
await AfterSalesVisitApi.deleteAfterSalesVisitList(checkedIds.value);
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
await getList();
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedIds = ref<number[]>([])
|
||||||
|
const handleRowCheckboxChange = (records: AfterSalesVisit[]) => {
|
||||||
|
checkedIds.value = records.map((item) => item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await message.exportConfirm()
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await AfterSalesVisitApi.exportAfterSalesVisit(queryParams)
|
||||||
|
download.excel(data, '售后回访.xls')
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 统计数据计算 */
|
||||||
|
const avgRating = computed(() => {
|
||||||
|
if (list.value.length === 0) return 0
|
||||||
|
const sum = list.value.reduce((acc, item) => acc + (item.serviceRating || 0), 0)
|
||||||
|
return sum / list.value.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const todayCount = computed(() => {
|
||||||
|
const today = new Date().toISOString().split('T')[0]
|
||||||
|
return list.value.filter(item => {
|
||||||
|
const createDate = new Date(item.createTime).toISOString().split('T')[0]
|
||||||
|
return createDate === today
|
||||||
|
}).length
|
||||||
|
})
|
||||||
|
|
||||||
|
const fiveStarCount = computed(() => {
|
||||||
|
return list.value.filter(item => (item.serviceRating || 0) === 5).length
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 格式化函数 */
|
||||||
|
const getCustomerTypeLabel = (value: string) => {
|
||||||
|
const option = customerTypeOptions.find(item => item.value === value)
|
||||||
|
return option ? option.label : value
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCustomerTypeColor = (value: string) => {
|
||||||
|
const colorMap = {
|
||||||
|
'large_food_factory': 'success',
|
||||||
|
'small_food_factory': 'info',
|
||||||
|
'distributor': 'warning',
|
||||||
|
'catering_chain': 'danger',
|
||||||
|
'government_unit': 'primary',
|
||||||
|
'other': ''
|
||||||
|
}
|
||||||
|
return colorMap[value as keyof typeof colorMap] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRepurchaseLabel = (value: string) => {
|
||||||
|
const option = repurchaseOptions.find(item => item.value === value)
|
||||||
|
return option ? option.label : value
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRepurchaseColor = (value: string) => {
|
||||||
|
const colorMap = {
|
||||||
|
'definitely': 'text-green-600 font-medium',
|
||||||
|
'probably': 'text-blue-600',
|
||||||
|
'uncertain': 'text-yellow-600',
|
||||||
|
'unlikely': 'text-orange-600',
|
||||||
|
'never': 'text-red-600'
|
||||||
|
}
|
||||||
|
return colorMap[value as keyof typeof colorMap] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,21 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
<el-drawer
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
direction="rtl"
|
||||||
|
size="100%"
|
||||||
|
:close-on-press-escape="true"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
class="mobile-form-drawer"
|
||||||
|
>
|
||||||
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
v-loading="formLoading"
|
|
||||||
:model="formData"
|
:model="formData"
|
||||||
:rules="formRules"
|
:rules="formRules"
|
||||||
label-width="100px"
|
label-position="top"
|
||||||
>
|
>
|
||||||
<el-row>
|
<!-- 基本信息 -->
|
||||||
<el-col :span="12">
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">基本信息</div>
|
||||||
<el-form-item label="线索名称" prop="name">
|
<el-form-item label="线索名称" prop="name">
|
||||||
<el-input v-model="formData.name" placeholder="请输入线索名称" />
|
<el-input v-model="formData.name" placeholder="请输入线索名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="客户来源" prop="source">
|
<el-form-item label="客户来源" prop="source">
|
||||||
<el-select v-model="formData.source" placeholder="请选择客户来源" class="w-1/1">
|
<el-select v-model="formData.source" placeholder="请选择客户来源" style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -24,20 +31,11 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="手机" prop="mobile">
|
|
||||||
<el-input v-model="formData.mobile" placeholder="请输入手机" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="负责人" prop="ownerUserId">
|
<el-form-item label="负责人" prop="ownerUserId">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="formData.ownerUserId"
|
v-model="formData.ownerUserId"
|
||||||
:disabled="formType !== 'create'"
|
:disabled="formType !== 'create'"
|
||||||
class="w-1/1"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in userOptions"
|
v-for="item in userOptions"
|
||||||
@@ -47,36 +45,33 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
<el-row>
|
<!-- 联系方式 -->
|
||||||
<el-col :span="12">
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">联系方式</div>
|
||||||
|
<el-form-item label="手机" prop="mobile">
|
||||||
|
<el-input v-model="formData.mobile" placeholder="请输入手机" />
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="电话" prop="telephone">
|
<el-form-item label="电话" prop="telephone">
|
||||||
<el-input v-model="formData.telephone" placeholder="请输入电话" />
|
<el-input v-model="formData.telephone" placeholder="请输入电话" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="邮箱" prop="email">
|
<el-form-item label="邮箱" prop="email">
|
||||||
<el-input v-model="formData.email" placeholder="请输入邮箱" />
|
<el-input v-model="formData.email" placeholder="请输入邮箱" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="微信" prop="wechat">
|
<el-form-item label="微信" prop="wechat">
|
||||||
<el-input v-model="formData.wechat" placeholder="请输入微信" />
|
<el-input v-model="formData.wechat" placeholder="请输入微信" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="QQ" prop="qq">
|
<el-form-item label="QQ" prop="qq">
|
||||||
<el-input v-model="formData.qq" placeholder="请输入 QQ" />
|
<el-input v-model="formData.qq" placeholder="请输入 QQ" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
<el-row>
|
<!-- 客户信息 -->
|
||||||
<el-col :span="12">
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">客户信息</div>
|
||||||
<el-form-item label="客户行业" prop="industryId">
|
<el-form-item label="客户行业" prop="industryId">
|
||||||
<el-select v-model="formData.industryId" placeholder="请选择客户行业" class="w-1/1">
|
<el-select v-model="formData.industryId" placeholder="请选择客户行业" style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -85,10 +80,8 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="客户级别" prop="level">
|
<el-form-item label="客户级别" prop="level">
|
||||||
<el-select v-model="formData.level" placeholder="请选择客户级别" class="w-1/1">
|
<el-select v-model="formData.level" placeholder="请选择客户级别" style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -97,52 +90,52 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
<el-row>
|
<!-- 地址信息 -->
|
||||||
<el-col :span="12">
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">地址信息</div>
|
||||||
<el-form-item label="地址" prop="areaId">
|
<el-form-item label="地址" prop="areaId">
|
||||||
<el-cascader
|
<el-cascader
|
||||||
v-model="formData.areaId"
|
v-model="formData.areaId"
|
||||||
:options="areaList"
|
:options="areaList"
|
||||||
:props="defaultProps"
|
:props="defaultProps"
|
||||||
class="w-1/1"
|
style="width: 100%"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
placeholder="请选择城市"
|
placeholder="请选择城市"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="详细地址" prop="detailAddress">
|
<el-form-item label="详细地址" prop="detailAddress">
|
||||||
<el-input v-model="formData.detailAddress" placeholder="请输入详细地址" />
|
<el-input v-model="formData.detailAddress" placeholder="请输入详细地址" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
<el-row>
|
<!-- 其他信息 -->
|
||||||
<el-col :span="12">
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">其他信息</div>
|
||||||
<el-form-item label="下次联系时间" prop="contactNextTime">
|
<el-form-item label="下次联系时间" prop="contactNextTime">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="formData.contactNextTime"
|
v-model="formData.contactNextTime"
|
||||||
placeholder="选择下次联系时间"
|
placeholder="选择下次联系时间"
|
||||||
type="datetime"
|
type="datetime"
|
||||||
value-format="x"
|
value-format="x"
|
||||||
class="!w-1/1"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" />
|
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" :rows="3" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
|
||||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
<!-- 底部操作按钮 -->
|
||||||
|
<div class="mobile-form__footer">
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||||
</Dialog>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
@@ -258,3 +251,44 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -4px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-loading="loading">
|
<div class="mobile-detail-header" v-loading="loading">
|
||||||
<div class="flex items-start justify-between">
|
<!-- 标题和操作按钮 -->
|
||||||
<div>
|
<div class="mobile-detail-header__top">
|
||||||
<!-- 左上:线索基本信息 -->
|
<div class="mobile-detail-header__title">{{ clue.name }}</div>
|
||||||
<el-col>
|
<div class="mobile-detail-header__actions">
|
||||||
<el-row>
|
|
||||||
<span class="text-xl font-bold">{{ clue.name }}</span>
|
|
||||||
</el-row>
|
|
||||||
</el-col>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<!-- 右上:按钮 -->
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<!-- 关键信息卡片 -->
|
||||||
<ContentWrap class="mt-10px">
|
<div class="mobile-detail-header__card">
|
||||||
<el-descriptions :column="5" direction="vertical">
|
<div class="mobile-info-list">
|
||||||
<el-descriptions-item label="线索来源">
|
<div class="mobile-info-row">
|
||||||
|
<span class="mobile-info-row__label">线索来源</span>
|
||||||
|
<span class="mobile-info-row__value">
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="clue.source" />
|
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="clue.source" />
|
||||||
</el-descriptions-item>
|
</span>
|
||||||
<el-descriptions-item label="手机"> {{ clue.mobile }} </el-descriptions-item>
|
</div>
|
||||||
<el-descriptions-item label="负责人">
|
<div class="mobile-info-row">
|
||||||
{{ clue.ownerUserName }}
|
<span class="mobile-info-row__label">手机</span>
|
||||||
</el-descriptions-item>
|
<span class="mobile-info-row__value">{{ clue.mobile || '-' }}</span>
|
||||||
<el-descriptions-item label="创建时间">
|
</div>
|
||||||
{{ formatDate(clue.createTime) }}
|
<div class="mobile-info-row">
|
||||||
</el-descriptions-item>
|
<span class="mobile-info-row__label">负责人</span>
|
||||||
</el-descriptions>
|
<span class="mobile-info-row__value">{{ clue.ownerUserName || '-' }}</span>
|
||||||
</ContentWrap>
|
</div>
|
||||||
|
<div class="mobile-info-row">
|
||||||
|
<span class="mobile-info-row__label">创建时间</span>
|
||||||
|
<span class="mobile-info-row__value">{{ formatDate(clue.createTime) || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
@@ -41,3 +43,57 @@ defineProps<{
|
|||||||
loading: boolean // 加载中
|
loading: boolean // 加载中
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-detail-header {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
.mobile-detail-header__top {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
.mobile-detail-header__title {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
flex: 1;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
.mobile-detail-header__actions {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.mobile-detail-header__card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-info-list {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.mobile-info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6px 0;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
&__label {
|
||||||
|
color: #909399;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
&__value {
|
||||||
|
color: #303133;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,61 +1,97 @@
|
|||||||
<template>
|
<template>
|
||||||
<ContentWrap>
|
<div class="mobile-detail-info">
|
||||||
<el-collapse v-model="activeNames" class="">
|
<!-- 基本信息 -->
|
||||||
<el-collapse-item name="basicInfo">
|
<div class="mobile-form__section">
|
||||||
<template #title>
|
<div class="mobile-form__section-title">基本信息</div>
|
||||||
<span class="text-base font-bold">基本信息</span>
|
<div class="mobile-info-list">
|
||||||
</template>
|
<div class="mobile-info-row">
|
||||||
<el-descriptions :column="4">
|
<span class="mobile-info-row__label">线索名称</span>
|
||||||
<el-descriptions-item label="线索名称">
|
<span class="mobile-info-row__value">{{ clue.name || '-' }}</span>
|
||||||
{{ clue.name }}
|
</div>
|
||||||
</el-descriptions-item>
|
<div class="mobile-info-row">
|
||||||
<el-descriptions-item label="客户来源">
|
<span class="mobile-info-row__label">客户来源</span>
|
||||||
|
<span class="mobile-info-row__value">
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="clue.source" />
|
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="clue.source" />
|
||||||
</el-descriptions-item>
|
</span>
|
||||||
<el-descriptions-item label="手机">{{ clue.mobile }}</el-descriptions-item>
|
</div>
|
||||||
<el-descriptions-item label="电话">{{ clue.telephone }}</el-descriptions-item>
|
<div class="mobile-info-row">
|
||||||
<el-descriptions-item label="邮箱">{{ clue.email }}</el-descriptions-item>
|
<span class="mobile-info-row__label">手机</span>
|
||||||
<el-descriptions-item label="地址">
|
<span class="mobile-info-row__value">{{ clue.mobile || '-' }}</span>
|
||||||
{{ clue.areaName }} {{ clue.detailAddress }}
|
</div>
|
||||||
</el-descriptions-item>
|
<div class="mobile-info-row">
|
||||||
<el-descriptions-item label="QQ">{{ clue.qq }}</el-descriptions-item>
|
<span class="mobile-info-row__label">电话</span>
|
||||||
<el-descriptions-item label="微信">{{ clue.wechat }}</el-descriptions-item>
|
<span class="mobile-info-row__value">{{ clue.telephone || '-' }}</span>
|
||||||
<el-descriptions-item label="客户行业">
|
</div>
|
||||||
|
<div class="mobile-info-row">
|
||||||
|
<span class="mobile-info-row__label">邮箱</span>
|
||||||
|
<span class="mobile-info-row__value">{{ clue.email || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-info-row">
|
||||||
|
<span class="mobile-info-row__label">地址</span>
|
||||||
|
<span class="mobile-info-row__value">{{ clue.areaName || '' }} {{ clue.detailAddress || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-info-row">
|
||||||
|
<span class="mobile-info-row__label">QQ</span>
|
||||||
|
<span class="mobile-info-row__value">{{ clue.qq || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-info-row">
|
||||||
|
<span class="mobile-info-row__label">微信</span>
|
||||||
|
<span class="mobile-info-row__value">{{ clue.wechat || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-info-row">
|
||||||
|
<span class="mobile-info-row__label">客户行业</span>
|
||||||
|
<span class="mobile-info-row__value">
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="clue.industryId" />
|
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="clue.industryId" />
|
||||||
</el-descriptions-item>
|
</span>
|
||||||
<el-descriptions-item label="客户级别">
|
</div>
|
||||||
|
<div class="mobile-info-row">
|
||||||
|
<span class="mobile-info-row__label">客户级别</span>
|
||||||
|
<span class="mobile-info-row__value">
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="clue.level" />
|
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="clue.level" />
|
||||||
</el-descriptions-item>
|
</span>
|
||||||
<el-descriptions-item label="下次联系时间">
|
</div>
|
||||||
{{ formatDate(clue.contactNextTime) }}
|
<div class="mobile-info-row">
|
||||||
</el-descriptions-item>
|
<span class="mobile-info-row__label">下次联系时间</span>
|
||||||
<el-descriptions-item label="备注">{{ clue.remark }}</el-descriptions-item>
|
<span class="mobile-info-row__value">{{ formatDate(clue.contactNextTime) || '-' }}</span>
|
||||||
</el-descriptions>
|
</div>
|
||||||
</el-collapse-item>
|
<div class="mobile-info-row" v-if="clue.remark">
|
||||||
<el-collapse-item name="systemInfo">
|
<span class="mobile-info-row__label">备注</span>
|
||||||
<template #title>
|
<span class="mobile-info-row__value">{{ clue.remark }}</span>
|
||||||
<span class="text-base font-bold">系统信息</span>
|
</div>
|
||||||
</template>
|
</div>
|
||||||
<el-descriptions :column="4">
|
</div>
|
||||||
<el-descriptions-item label="负责人">{{ clue.ownerUserName }}</el-descriptions-item>
|
|
||||||
<el-descriptions-item label="最后跟进记录">
|
<!-- 系统信息 -->
|
||||||
{{ clue.contactLastContent }}
|
<div class="mobile-form__section">
|
||||||
</el-descriptions-item>
|
<div class="mobile-form__section-title">系统信息</div>
|
||||||
<el-descriptions-item label="最后跟进时间">
|
<div class="mobile-info-list">
|
||||||
{{ formatDate(clue.contactLastTime) }}
|
<div class="mobile-info-row">
|
||||||
</el-descriptions-item>
|
<span class="mobile-info-row__label">负责人</span>
|
||||||
<el-descriptions-item label=""> </el-descriptions-item>
|
<span class="mobile-info-row__value">{{ clue.ownerUserName || '-' }}</span>
|
||||||
<el-descriptions-item label="创建人">{{ clue.creatorName }}</el-descriptions-item>
|
</div>
|
||||||
<el-descriptions-item label="创建时间">
|
<div class="mobile-info-row" v-if="clue.contactLastContent">
|
||||||
{{ formatDate(clue.createTime) }}
|
<span class="mobile-info-row__label">最后跟进记录</span>
|
||||||
</el-descriptions-item>
|
<span class="mobile-info-row__value">{{ clue.contactLastContent }}</span>
|
||||||
<el-descriptions-item label="更新时间">
|
</div>
|
||||||
{{ formatDate(clue.updateTime) }}
|
<div class="mobile-info-row">
|
||||||
</el-descriptions-item>
|
<span class="mobile-info-row__label">最后跟进时间</span>
|
||||||
</el-descriptions>
|
<span class="mobile-info-row__value">{{ formatDate(clue.contactLastTime) || '-' }}</span>
|
||||||
</el-collapse-item>
|
</div>
|
||||||
</el-collapse>
|
<div class="mobile-info-row">
|
||||||
</ContentWrap>
|
<span class="mobile-info-row__label">创建人</span>
|
||||||
|
<span class="mobile-info-row__value">{{ clue.creatorName || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-info-row">
|
||||||
|
<span class="mobile-info-row__label">创建时间</span>
|
||||||
|
<span class="mobile-info-row__value">{{ formatDate(clue.createTime) || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-info-row">
|
||||||
|
<span class="mobile-info-row__label">更新时间</span>
|
||||||
|
<span class="mobile-info-row__value">{{ formatDate(clue.updateTime) || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as ClueApi from '@/api/crm/clue'
|
import * as ClueApi from '@/api/crm/clue'
|
||||||
@@ -66,7 +102,48 @@ defineOptions({ name: 'CrmClueDetailsInfo' })
|
|||||||
const { clue } = defineProps<{
|
const { clue } = defineProps<{
|
||||||
clue: ClueApi.ClueVO // 线索明细
|
clue: ClueApi.ClueVO // 线索明细
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const activeNames = ref(['basicInfo', 'systemInfo']) // 展示的折叠面板
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped></style>
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-detail-info {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-info-list {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.mobile-info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 6px 0;
|
||||||
|
border-bottom: 1px solid #f5f5f5;
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
&__label {
|
||||||
|
color: #909399;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
&__value {
|
||||||
|
color: #303133;
|
||||||
|
text-align: right;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,52 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert title="【线索】线索管理" url="https://doc.iocoder.cn/crm/clue/" />
|
<div class="mobile-page">
|
||||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" />
|
<!-- 搜索头部 -->
|
||||||
|
<div class="mobile-page__header">
|
||||||
<ContentWrap>
|
<div class="mobile-page__search">
|
||||||
<!-- 搜索工作栏 -->
|
|
||||||
<el-form
|
|
||||||
class="-mb-15px"
|
|
||||||
:model="queryParams"
|
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="线索名称" prop="name">
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.name"
|
v-model="queryParams.name"
|
||||||
placeholder="请输入线索名称"
|
placeholder="搜索线索名称"
|
||||||
clearable
|
clearable
|
||||||
@keyup.enter="handleQuery"
|
@keyup.enter="handleQuery"
|
||||||
class="!w-240px"
|
:prefix-icon="Search"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
<el-button type="primary" :icon="Filter" @click="filterDrawerVisible = true" />
|
||||||
<el-form-item label="转化状态" prop="transformStatus">
|
</div>
|
||||||
<el-select v-model="queryParams.transformStatus" class="!w-240px">
|
<div class="mobile-page__actions">
|
||||||
<el-option :value="false" label="未转化" />
|
|
||||||
<el-option :value="true" label="已转化" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="手机号" prop="mobile">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.mobile"
|
|
||||||
placeholder="请输入手机号"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="电话" prop="telephone">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.telephone"
|
|
||||||
placeholder="请输入电话"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</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 type="primary" @click="openForm('create')" v-hasPermi="['crm:clue:create']">
|
<el-button type="primary" @click="openForm('create')" v-hasPermi="['crm:clue:create']">
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
</el-button>
|
</el-button>
|
||||||
@@ -57,108 +23,129 @@
|
|||||||
:loading="exportLoading"
|
:loading="exportLoading"
|
||||||
v-hasPermi="['crm:clue:export']"
|
v-hasPermi="['crm:clue:export']"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
导出
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form-item>
|
</div>
|
||||||
</el-form>
|
</div>
|
||||||
</ContentWrap>
|
|
||||||
|
|
||||||
<!-- 列表 -->
|
<!-- Tab 切换 -->
|
||||||
<ContentWrap>
|
<div class="mobile-page__tabs">
|
||||||
<el-tabs v-model="activeName" @tab-click="handleTabClick">
|
<el-tabs v-model="activeName" @tab-click="handleTabClick">
|
||||||
<el-tab-pane label="我负责的" name="1" />
|
<el-tab-pane label="我负责的" name="1" />
|
||||||
<el-tab-pane label="我参与的" name="2" />
|
<el-tab-pane label="我参与的" name="2" />
|
||||||
<el-tab-pane label="下属负责的" name="3" />
|
<el-tab-pane label="下属负责的" name="3" />
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
</div>
|
||||||
<el-table-column label="线索名称" align="center" prop="name" fixed="left" width="160">
|
|
||||||
<template #default="scope">
|
<!-- 线索列表 -->
|
||||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
<div class="mobile-page__content" v-loading="loading">
|
||||||
{{ scope.row.name }}
|
<div class="mobile-item-list">
|
||||||
</el-link>
|
<div
|
||||||
</template>
|
v-for="item in list"
|
||||||
</el-table-column>
|
:key="item.id"
|
||||||
<el-table-column label="线索来源" align="center" prop="source" width="100">
|
class="mobile-item-card mobile-item-card--clickable"
|
||||||
<template #default="scope">
|
@click="openDetail(item.id)"
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
>
|
||||||
</template>
|
<div class="mobile-item-card__header">
|
||||||
</el-table-column>
|
<span class="mobile-item-card__name">{{ item.name }}</span>
|
||||||
<el-table-column label="手机" align="center" prop="mobile" width="120" />
|
<el-tag v-if="item.transformStatus" type="success" size="small">已转化</el-tag>
|
||||||
<el-table-column label="电话" align="center" prop="telephone" width="130" />
|
<el-tag v-else type="info" size="small">未转化</el-tag>
|
||||||
<el-table-column label="邮箱" align="center" prop="email" width="180" />
|
</div>
|
||||||
<el-table-column label="地址" align="center" prop="detailAddress" width="180" />
|
<div class="mobile-item-card__body">
|
||||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
<div class="mobile-item-card__info-row">
|
||||||
<template #default="scope">
|
<span class="mobile-item-card__info-label">线索来源</span>
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
<span class="mobile-item-card__info-value">
|
||||||
</template>
|
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="item.source" />
|
||||||
</el-table-column>
|
</span>
|
||||||
<el-table-column align="center" label="客户级别" prop="level" width="135">
|
</div>
|
||||||
<template #default="scope">
|
<div class="mobile-item-card__info-row">
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
<span class="mobile-item-card__info-label">手机</span>
|
||||||
</template>
|
<span class="mobile-item-card__info-value">{{ item.mobile || '-' }}</span>
|
||||||
</el-table-column>
|
</div>
|
||||||
<el-table-column
|
<div class="mobile-item-card__info-row">
|
||||||
:formatter="dateFormatter"
|
<span class="mobile-item-card__info-label">负责人</span>
|
||||||
align="center"
|
<span class="mobile-item-card__info-value">{{ item.ownerUserName || '-' }}</span>
|
||||||
label="下次联系时间"
|
</div>
|
||||||
prop="contactNextTime"
|
<div class="mobile-item-card__info-row">
|
||||||
width="180px"
|
<span class="mobile-item-card__info-label">客户级别</span>
|
||||||
/>
|
<span class="mobile-item-card__info-value">
|
||||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="item.level" />
|
||||||
<el-table-column
|
</span>
|
||||||
label="最后跟进时间"
|
</div>
|
||||||
align="center"
|
<div class="mobile-item-card__info-row">
|
||||||
prop="contactLastTime"
|
<span class="mobile-item-card__info-label">最后跟进</span>
|
||||||
:formatter="dateFormatter"
|
<span class="mobile-item-card__info-value">{{ item.contactLastTime ? dateFormatter(null, null, item.contactLastTime) : '-' }}</span>
|
||||||
width="180px"
|
</div>
|
||||||
/>
|
</div>
|
||||||
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
|
<div class="mobile-item-card__footer" @click.stop>
|
||||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
|
|
||||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100" />
|
|
||||||
<el-table-column
|
|
||||||
label="更新时间"
|
|
||||||
align="center"
|
|
||||||
prop="updateTime"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
label="创建时间"
|
|
||||||
align="center"
|
|
||||||
prop="createTime"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
|
||||||
<el-table-column label="操作" align="center" min-width="110" fixed="right">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button
|
<el-button
|
||||||
link
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openForm('update', scope.row.id)"
|
@click="openForm('update', item.id)"
|
||||||
v-hasPermi="['crm:clue:update']"
|
v-hasPermi="['crm:clue:update']"
|
||||||
>
|
>编辑</el-button>
|
||||||
编辑
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
<el-button
|
||||||
link
|
size="small"
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleDelete(scope.row.id)"
|
@click="handleDelete(item.id)"
|
||||||
v-hasPermi="['crm:clue:delete']"
|
v-hasPermi="['crm:clue:delete']"
|
||||||
>
|
>删除</el-button>
|
||||||
删除
|
</div>
|
||||||
</el-button>
|
</div>
|
||||||
</template>
|
<div v-if="list.length === 0 && !loading" class="mobile-empty-tip">暂无线索数据</div>
|
||||||
</el-table-column>
|
</div>
|
||||||
</el-table>
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<Pagination
|
<div class="mobile-pagination" v-if="total > 0">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNo"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
v-model:page="queryParams.pageNo"
|
:page-sizes="[10, 20]"
|
||||||
v-model:limit="queryParams.pageSize"
|
layout="total, prev, pager, next"
|
||||||
@pagination="getList"
|
:pager-count="5"
|
||||||
|
@size-change="getList"
|
||||||
|
@current-change="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选抽屉 -->
|
||||||
|
<el-drawer
|
||||||
|
v-model="filterDrawerVisible"
|
||||||
|
title="筛选条件"
|
||||||
|
direction="rtl"
|
||||||
|
size="100%"
|
||||||
|
:append-to-body="true"
|
||||||
|
class="mobile-form-drawer"
|
||||||
|
>
|
||||||
|
<div class="mobile-form">
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">筛选条件</div>
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
|
<el-form-item label="线索名称" prop="name">
|
||||||
|
<el-input v-model="queryParams.name" placeholder="请输入线索名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="转化状态" prop="transformStatus">
|
||||||
|
<el-select v-model="queryParams.transformStatus" style="width: 100%">
|
||||||
|
<el-option :value="false" label="未转化" />
|
||||||
|
<el-option :value="true" label="已转化" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="手机号" prop="mobile">
|
||||||
|
<el-input v-model="queryParams.mobile" placeholder="请输入手机号" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="电话" prop="telephone">
|
||||||
|
<el-input v-model="queryParams.telephone" placeholder="请输入电话" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="resetQuery" style="flex: 1">重置</el-button>
|
||||||
|
<el-button type="primary" @click="handleFilterConfirm" style="flex: 1">确认</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ClueForm ref="formRef" @success="getList" />
|
<ClueForm ref="formRef" @success="getList" />
|
||||||
@@ -171,6 +158,7 @@ import download from '@/utils/download'
|
|||||||
import * as ClueApi from '@/api/crm/clue'
|
import * as ClueApi from '@/api/crm/clue'
|
||||||
import ClueForm from './ClueForm.vue'
|
import ClueForm from './ClueForm.vue'
|
||||||
import { TabsPaneContext } from 'element-plus'
|
import { TabsPaneContext } from 'element-plus'
|
||||||
|
import { Search, Filter } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
defineOptions({ name: 'CrmClue' })
|
defineOptions({ name: 'CrmClue' })
|
||||||
|
|
||||||
@@ -180,6 +168,7 @@ const { t } = useI18n() // 国际化
|
|||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const list = ref([]) // 列表的数据
|
const list = ref([]) // 列表的数据
|
||||||
|
const filterDrawerVisible = ref(false) // 筛选抽屉
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@@ -263,8 +252,149 @@ const handleExport = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 筛选确认 */
|
||||||
|
const handleFilterConfirm = () => {
|
||||||
|
filterDrawerVisible.value = false
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-page {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.mobile-page__header {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mobile-page__search {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.mobile-page__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.mobile-page__tabs {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mobile-page__content {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
.mobile-item-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.mobile-item-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
&--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
&:active {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
&__name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
&__body {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
&__info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
&__info-label {
|
||||||
|
color: #909399;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
&__info-value {
|
||||||
|
color: #606266;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-empty-tip {
|
||||||
|
text-align: center;
|
||||||
|
color: #909399;
|
||||||
|
padding: 40px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.mobile-pagination {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
:deep(.el-pagination) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-form {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,21 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="dialogVisible" :title="dialogTitle">
|
<el-drawer
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
direction="rtl"
|
||||||
|
size="100%"
|
||||||
|
:close-on-press-escape="true"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
class="mobile-form-drawer"
|
||||||
|
>
|
||||||
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
v-loading="formLoading"
|
|
||||||
:model="formData"
|
:model="formData"
|
||||||
:rules="formRules"
|
:rules="formRules"
|
||||||
label-width="100px"
|
label-position="top"
|
||||||
>
|
>
|
||||||
<el-row>
|
<!-- 基本信息 -->
|
||||||
<el-col :span="12">
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">基本信息</div>
|
||||||
<el-form-item label="客户名称" prop="name">
|
<el-form-item label="客户名称" prop="name">
|
||||||
<el-input v-model="formData.name" placeholder="请输入客户名称" />
|
<el-input v-model="formData.name" placeholder="请输入客户名称" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="客户来源" prop="source">
|
<el-form-item label="客户来源" prop="source">
|
||||||
<el-select v-model="formData.source" placeholder="请选择客户来源" class="w-1/1">
|
<el-select v-model="formData.source" placeholder="请选择客户来源" style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -24,20 +31,11 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="手机" prop="mobile">
|
|
||||||
<el-input v-model="formData.mobile" placeholder="请输入手机" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="负责人" prop="ownerUserId">
|
<el-form-item label="负责人" prop="ownerUserId">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="formData.ownerUserId"
|
v-model="formData.ownerUserId"
|
||||||
:disabled="formType !== 'create'"
|
:disabled="formType !== 'create'"
|
||||||
class="w-1/1"
|
style="width: 100%"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in userOptions"
|
v-for="item in userOptions"
|
||||||
@@ -47,36 +45,33 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
<el-row>
|
<!-- 联系方式 -->
|
||||||
<el-col :span="12">
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">联系方式</div>
|
||||||
|
<el-form-item label="手机" prop="mobile">
|
||||||
|
<el-input v-model="formData.mobile" placeholder="请输入手机" />
|
||||||
|
</el-form-item>
|
||||||
<el-form-item label="电话" prop="telephone">
|
<el-form-item label="电话" prop="telephone">
|
||||||
<el-input v-model="formData.telephone" placeholder="请输入电话" />
|
<el-input v-model="formData.telephone" placeholder="请输入电话" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="邮箱" prop="email">
|
<el-form-item label="邮箱" prop="email">
|
||||||
<el-input v-model="formData.email" placeholder="请输入邮箱" />
|
<el-input v-model="formData.email" placeholder="请输入邮箱" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
</el-row>
|
|
||||||
<el-row>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="微信" prop="wechat">
|
<el-form-item label="微信" prop="wechat">
|
||||||
<el-input v-model="formData.wechat" placeholder="请输入微信" />
|
<el-input v-model="formData.wechat" placeholder="请输入微信" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="QQ" prop="qq">
|
<el-form-item label="QQ" prop="qq">
|
||||||
<el-input v-model="formData.qq" placeholder="请输入 QQ" />
|
<el-input v-model="formData.qq" placeholder="请输入 QQ" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
<el-row>
|
<!-- 客户信息 -->
|
||||||
<el-col :span="12">
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">客户信息</div>
|
||||||
<el-form-item label="客户行业" prop="industryId">
|
<el-form-item label="客户行业" prop="industryId">
|
||||||
<el-select v-model="formData.industryId" placeholder="请选择客户行业" class="w-1/1">
|
<el-select v-model="formData.industryId" placeholder="请选择客户行业" style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -85,10 +80,8 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="客户级别" prop="level">
|
<el-form-item label="客户级别" prop="level">
|
||||||
<el-select v-model="formData.level" placeholder="请选择客户级别" class="w-1/1">
|
<el-select v-model="formData.level" placeholder="请选择客户级别" style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -97,52 +90,52 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
<el-row>
|
<!-- 地址信息 -->
|
||||||
<el-col :span="12">
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">地址信息</div>
|
||||||
<el-form-item label="地址" prop="areaId">
|
<el-form-item label="地址" prop="areaId">
|
||||||
<el-cascader
|
<el-cascader
|
||||||
v-model="formData.areaId"
|
v-model="formData.areaId"
|
||||||
:options="areaList"
|
:options="areaList"
|
||||||
:props="defaultProps"
|
:props="defaultProps"
|
||||||
class="w-1/1"
|
style="width: 100%"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
placeholder="请选择城市"
|
placeholder="请选择城市"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="详细地址" prop="detailAddress">
|
<el-form-item label="详细地址" prop="detailAddress">
|
||||||
<el-input v-model="formData.detailAddress" placeholder="请输入详细地址" />
|
<el-input v-model="formData.detailAddress" placeholder="请输入详细地址" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
<el-row>
|
<!-- 其他信息 -->
|
||||||
<el-col :span="12">
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">其他信息</div>
|
||||||
<el-form-item label="下次联系时间" prop="contactNextTime">
|
<el-form-item label="下次联系时间" prop="contactNextTime">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="formData.contactNextTime"
|
v-model="formData.contactNextTime"
|
||||||
placeholder="选择下次联系时间"
|
placeholder="选择下次联系时间"
|
||||||
type="datetime"
|
type="datetime"
|
||||||
value-format="x"
|
value-format="x"
|
||||||
class="!w-1/1"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark">
|
||||||
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" />
|
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" :rows="3" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-col>
|
</div>
|
||||||
</el-row>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
|
||||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
<!-- 底部操作按钮 -->
|
||||||
|
<div class="mobile-form__footer">
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||||
</Dialog>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
@@ -258,3 +251,44 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -4px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,9 +1,20 @@
|
|||||||
<!-- 客户导入窗口 -->
|
<!-- 客户导入窗口 -->
|
||||||
<template>
|
<template>
|
||||||
<Dialog v-model="dialogVisible" title="客户导入" width="400">
|
<el-drawer
|
||||||
<div class="flex items-center my-10px">
|
v-model="dialogVisible"
|
||||||
<span class="mr-10px">负责人</span>
|
title="客户导入"
|
||||||
<el-select v-model="ownerUserId" class="!w-240px" clearable>
|
direction="rtl"
|
||||||
|
size="100%"
|
||||||
|
:close-on-press-escape="true"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
class="mobile-form-drawer"
|
||||||
|
>
|
||||||
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">导入设置</div>
|
||||||
|
<el-form label-position="top">
|
||||||
|
<el-form-item label="负责人">
|
||||||
|
<el-select v-model="ownerUserId" style="width: 100%" clearable>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in userOptions"
|
v-for="item in userOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -11,7 +22,12 @@
|
|||||||
:value="item.id"
|
:value="item.id"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">上传文件</div>
|
||||||
<el-upload
|
<el-upload
|
||||||
ref="uploadRef"
|
ref="uploadRef"
|
||||||
v-model:file-list="fileList"
|
v-model:file-list="fileList"
|
||||||
@@ -43,11 +59,15 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-upload>
|
</el-upload>
|
||||||
<template #footer>
|
</div>
|
||||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
|
||||||
|
<!-- 底部操作按钮 -->
|
||||||
|
<div class="mobile-form__footer">
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||||
</Dialog>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as CustomerApi from '@/api/crm/customer'
|
import * as CustomerApi from '@/api/crm/customer'
|
||||||
@@ -156,3 +176,44 @@ const importTemplate = async () => {
|
|||||||
download.excel(res, '客户导入模版.xls')
|
download.excel(res, '客户导入模版.xls')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -4px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,41 +1,139 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert title="【客户】客户管理、公海客户" url="https://doc.iocoder.cn/crm/customer/" />
|
<div class="mobile-page">
|
||||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" />
|
<!-- 搜索头部 -->
|
||||||
|
<div class="mobile-page__header">
|
||||||
<ContentWrap>
|
<div class="mobile-page__search">
|
||||||
<!-- 搜索工作栏 -->
|
|
||||||
<el-form
|
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
|
||||||
:model="queryParams"
|
|
||||||
class="-mb-15px"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="客户名称" prop="name">
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.name"
|
v-model="queryParams.name"
|
||||||
class="!w-240px"
|
placeholder="搜索客户名称"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请输入客户名称"
|
|
||||||
@keyup.enter="handleQuery"
|
@keyup.enter="handleQuery"
|
||||||
|
:prefix-icon="Search"
|
||||||
/>
|
/>
|
||||||
|
<el-button type="primary" :icon="Filter" @click="filterDrawerVisible = true" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-page__actions">
|
||||||
|
<el-button type="primary" @click="openForm('create')" v-hasPermi="['crm:customer:create']">
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button type="warning" plain @click="handleImport" v-hasPermi="['crm:customer:import']">
|
||||||
|
导入
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['crm:customer:export']"
|
||||||
|
>
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab 切换 -->
|
||||||
|
<div class="mobile-page__tabs">
|
||||||
|
<el-tabs v-model="activeName" @tab-click="handleTabClick">
|
||||||
|
<el-tab-pane label="我负责的" name="1" />
|
||||||
|
<el-tab-pane label="我参与的" name="2" />
|
||||||
|
<el-tab-pane label="下属负责的" name="3" />
|
||||||
|
</el-tabs>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 客户列表 -->
|
||||||
|
<div class="mobile-page__content" v-loading="loading">
|
||||||
|
<div class="mobile-item-list">
|
||||||
|
<div
|
||||||
|
v-for="item in list"
|
||||||
|
:key="item.id"
|
||||||
|
class="mobile-item-card mobile-item-card--clickable"
|
||||||
|
@click="openDetail(item.id)"
|
||||||
|
>
|
||||||
|
<div class="mobile-item-card__header">
|
||||||
|
<span class="mobile-item-card__name">{{ item.name }}</span>
|
||||||
|
<el-tag v-if="item.dealStatus" type="success" size="small">已成交</el-tag>
|
||||||
|
<el-tag v-else type="info" size="small">未成交</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__body">
|
||||||
|
<div class="mobile-item-card__info-row">
|
||||||
|
<span class="mobile-item-card__info-label">客户来源</span>
|
||||||
|
<span class="mobile-item-card__info-value">
|
||||||
|
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="item.source" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__info-row">
|
||||||
|
<span class="mobile-item-card__info-label">手机</span>
|
||||||
|
<span class="mobile-item-card__info-value">{{ item.mobile || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__info-row">
|
||||||
|
<span class="mobile-item-card__info-label">负责人</span>
|
||||||
|
<span class="mobile-item-card__info-value">{{ item.ownerUserName || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__info-row">
|
||||||
|
<span class="mobile-item-card__info-label">客户级别</span>
|
||||||
|
<span class="mobile-item-card__info-value">
|
||||||
|
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="item.level" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__info-row">
|
||||||
|
<span class="mobile-item-card__info-label">最后跟进</span>
|
||||||
|
<span class="mobile-item-card__info-value">{{ item.contactLastTime ? dateFormatter(null, null, item.contactLastTime) : '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__footer" @click.stop>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', item.id)"
|
||||||
|
v-hasPermi="['crm:customer:update']"
|
||||||
|
>编辑</el-button>
|
||||||
|
<el-button
|
||||||
|
size="small"
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(item.id)"
|
||||||
|
v-hasPermi="['crm:customer:delete']"
|
||||||
|
>删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="list.length === 0 && !loading" class="mobile-empty-tip">暂无客户数据</div>
|
||||||
|
</div>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="mobile-pagination" v-if="total > 0">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNo"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[10, 20]"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
:pager-count="5"
|
||||||
|
@size-change="getList"
|
||||||
|
@current-change="getList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选抽屉 -->
|
||||||
|
<el-drawer
|
||||||
|
v-model="filterDrawerVisible"
|
||||||
|
title="筛选条件"
|
||||||
|
direction="rtl"
|
||||||
|
size="100%"
|
||||||
|
:append-to-body="true"
|
||||||
|
class="mobile-form-drawer"
|
||||||
|
>
|
||||||
|
<div class="mobile-form">
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">筛选条件</div>
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
|
<el-form-item label="客户名称" prop="name">
|
||||||
|
<el-input v-model="queryParams.name" placeholder="请输入客户名称" clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="手机" prop="mobile">
|
<el-form-item label="手机" prop="mobile">
|
||||||
<el-input
|
<el-input v-model="queryParams.mobile" placeholder="请输入手机" clearable />
|
||||||
v-model="queryParams.mobile"
|
|
||||||
class="!w-240px"
|
|
||||||
clearable
|
|
||||||
placeholder="请输入手机"
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="所属行业" prop="industryId">
|
<el-form-item label="所属行业" prop="industryId">
|
||||||
<el-select
|
<el-select v-model="queryParams.industryId" placeholder="请选择所属行业" clearable style="width: 100%">
|
||||||
v-model="queryParams.industryId"
|
|
||||||
class="!w-240px"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择所属行业"
|
|
||||||
>
|
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -45,12 +143,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="客户级别" prop="level">
|
<el-form-item label="客户级别" prop="level">
|
||||||
<el-select
|
<el-select v-model="queryParams.level" placeholder="请选择客户级别" clearable style="width: 100%">
|
||||||
v-model="queryParams.level"
|
|
||||||
class="!w-240px"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择客户级别"
|
|
||||||
>
|
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -60,12 +153,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="客户来源" prop="source">
|
<el-form-item label="客户来源" prop="source">
|
||||||
<el-select
|
<el-select v-model="queryParams.source" placeholder="请选择客户来源" clearable style="width: 100%">
|
||||||
v-model="queryParams.source"
|
|
||||||
class="!w-240px"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择客户来源"
|
|
||||||
>
|
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -74,146 +162,14 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
|
||||||
<el-button @click="handleQuery">
|
|
||||||
<Icon class="mr-5px" icon="ep:search" />
|
|
||||||
搜索
|
|
||||||
</el-button>
|
|
||||||
<el-button @click="resetQuery">
|
|
||||||
<Icon class="mr-5px" icon="ep:refresh" />
|
|
||||||
重置
|
|
||||||
</el-button>
|
|
||||||
<el-button v-hasPermi="['crm:customer:create']" type="primary" @click="openForm('create')">
|
|
||||||
<Icon class="mr-5px" icon="ep:plus" />
|
|
||||||
新增
|
|
||||||
</el-button>
|
|
||||||
<el-button v-hasPermi="['crm:customer:import']" plain type="warning" @click="handleImport">
|
|
||||||
<Icon icon="ep:upload" />
|
|
||||||
导入
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-hasPermi="['crm:customer:export']"
|
|
||||||
:loading="exportLoading"
|
|
||||||
plain
|
|
||||||
type="success"
|
|
||||||
@click="handleExport"
|
|
||||||
>
|
|
||||||
<Icon class="mr-5px" icon="ep:download" />
|
|
||||||
导出
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</div>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
<!-- 列表 -->
|
<el-button @click="resetQuery" style="flex: 1">重置</el-button>
|
||||||
<ContentWrap>
|
<el-button type="primary" @click="handleFilterConfirm" style="flex: 1">确认</el-button>
|
||||||
<el-tabs v-model="activeName" @tab-click="handleTabClick">
|
</div>
|
||||||
<el-tab-pane label="我负责的" name="1" />
|
</div>
|
||||||
<el-tab-pane label="我参与的" name="2" />
|
</el-drawer>
|
||||||
<el-tab-pane label="下属负责的" name="3" />
|
|
||||||
</el-tabs>
|
|
||||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
|
||||||
<el-table-column align="center" fixed="left" label="客户名称" prop="name" width="160">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
|
||||||
{{ scope.row.name }}
|
|
||||||
</el-link>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="客户来源" prop="source" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="手机" prop="mobile" width="120" />
|
|
||||||
<el-table-column align="center" label="电话" prop="telephone" width="130" />
|
|
||||||
<el-table-column align="center" label="邮箱" prop="email" width="180" />
|
|
||||||
<el-table-column align="center" label="客户级别" prop="level" width="135">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
align="center"
|
|
||||||
label="下次联系时间"
|
|
||||||
prop="contactNextTime"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
|
||||||
<el-table-column align="center" label="锁定状态" prop="lockStatus">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.lockStatus" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="成交状态" prop="dealStatus">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
align="center"
|
|
||||||
label="最后跟进时间"
|
|
||||||
prop="contactLastTime"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
|
|
||||||
<el-table-column align="center" label="地址" prop="detailAddress" width="180" />
|
|
||||||
<el-table-column align="center" label="距离进入公海天数" prop="poolDay" width="140">
|
|
||||||
<template #default="scope"> {{ scope.row.poolDay }} 天</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="负责人" prop="ownerUserName" width="100px" />
|
|
||||||
<el-table-column align="center" label="所属部门" prop="ownerUserDeptName" width="100px" />
|
|
||||||
<el-table-column
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
align="center"
|
|
||||||
label="更新时间"
|
|
||||||
prop="updateTime"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
align="center"
|
|
||||||
label="创建时间"
|
|
||||||
prop="createTime"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
|
||||||
<el-table-column align="center" fixed="right" label="操作" min-width="150">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button
|
|
||||||
v-hasPermi="['crm:customer:update']"
|
|
||||||
link
|
|
||||||
type="primary"
|
|
||||||
@click="openForm('update', scope.row.id)"
|
|
||||||
>
|
|
||||||
编辑
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-hasPermi="['crm:customer:delete']"
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
@click="handleDelete(scope.row.id)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
<!-- 分页 -->
|
|
||||||
<Pagination
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
:total="total"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<CustomerForm ref="formRef" @success="getList" />
|
<CustomerForm ref="formRef" @success="getList" />
|
||||||
@@ -228,6 +184,7 @@ import * as CustomerApi from '@/api/crm/customer'
|
|||||||
import CustomerForm from './CustomerForm.vue'
|
import CustomerForm from './CustomerForm.vue'
|
||||||
import CustomerImportForm from './CustomerImportForm.vue'
|
import CustomerImportForm from './CustomerImportForm.vue'
|
||||||
import { TabsPaneContext } from 'element-plus'
|
import { TabsPaneContext } from 'element-plus'
|
||||||
|
import { Search, Filter } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
defineOptions({ name: 'CrmCustomer' })
|
defineOptions({ name: 'CrmCustomer' })
|
||||||
|
|
||||||
@@ -237,6 +194,7 @@ const { t } = useI18n() // 国际化
|
|||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const list = ref([]) // 列表的数据
|
const list = ref([]) // 列表的数据
|
||||||
|
const filterDrawerVisible = ref(false) // 筛选抽屉
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@@ -328,6 +286,12 @@ const handleExport = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 筛选确认 */
|
||||||
|
const handleFilterConfirm = () => {
|
||||||
|
filterDrawerVisible.value = false
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
/** 监听路由变化更新列表 */
|
/** 监听路由变化更新列表 */
|
||||||
watch(
|
watch(
|
||||||
() => currentRoute.value,
|
() => currentRoute.value,
|
||||||
@@ -341,3 +305,138 @@ onMounted(() => {
|
|||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-page {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.mobile-page__header {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mobile-page__search {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.mobile-page__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.mobile-page__tabs {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0 12px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mobile-page__content {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
.mobile-item-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.mobile-item-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
&--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
&:active {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
&__name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
&__body {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
&__info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
&__info-label {
|
||||||
|
color: #909399;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
&__info-value {
|
||||||
|
color: #606266;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-empty-tip {
|
||||||
|
text-align: center;
|
||||||
|
color: #909399;
|
||||||
|
padding: 40px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.mobile-pagination {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
:deep(.el-pagination) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-form {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<el-drawer
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
direction="rtl"
|
||||||
|
size="100%"
|
||||||
|
:close-on-press-escape="true"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
class="mobile-form-drawer"
|
||||||
|
>
|
||||||
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="formData"
|
:model="formData"
|
||||||
:rules="formRules"
|
:rules="formRules"
|
||||||
label-width="200px"
|
label-position="top"
|
||||||
v-loading="formLoading"
|
|
||||||
>
|
>
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">规则设置</div>
|
||||||
<el-form-item label="规则适用人群" prop="userIds">
|
<el-form-item label="规则适用人群" prop="userIds">
|
||||||
<el-select multiple filterable v-model="formData.userIds">
|
<el-select multiple filterable v-model="formData.userIds" style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in userOptions"
|
v-for="item in userOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -27,6 +37,7 @@
|
|||||||
check-strictly
|
check-strictly
|
||||||
node-key="id"
|
node-key="id"
|
||||||
placeholder="请选择规则适用部门"
|
placeholder="请选择规则适用部门"
|
||||||
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
@@ -37,7 +48,7 @@
|
|||||||
"
|
"
|
||||||
prop="maxCount"
|
prop="maxCount"
|
||||||
>
|
>
|
||||||
<el-input-number v-model="formData.maxCount" placeholder="请输入数量上限" />
|
<el-input-number v-model="formData.maxCount" placeholder="请输入数量上限" style="width: 100%" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item
|
<el-form-item
|
||||||
label="成交客户是否占用拥有客户数"
|
label="成交客户是否占用拥有客户数"
|
||||||
@@ -46,12 +57,16 @@
|
|||||||
>
|
>
|
||||||
<el-switch v-model="formData.dealCountEnabled" />
|
<el-switch v-model="formData.dealCountEnabled" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
<!-- 底部操作按钮 -->
|
||||||
|
<div class="mobile-form__footer">
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
</Dialog>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as CustomerLimitConfigApi from '@/api/crm/customer/limitConfig'
|
import * as CustomerLimitConfigApi from '@/api/crm/customer/limitConfig'
|
||||||
@@ -148,3 +163,44 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -4px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,84 +1,83 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-button plain @click="handleQuery"> <Icon icon="ep:refresh" class="mr-5px" /> 刷新 </el-button>
|
<div class="mobile-list-page">
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="mobile-list-page__actions">
|
||||||
|
<el-button plain size="small" @click="handleQuery">
|
||||||
|
<Icon icon="ep:refresh" class="mr-5px" /> 刷新
|
||||||
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
plain
|
size="small"
|
||||||
@click="openForm('create')"
|
@click="openForm('create')"
|
||||||
v-hasPermi="['crm:customer-limit-config:create']"
|
v-hasPermi="['crm:customer-limit-config:create']"
|
||||||
>
|
>
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-table
|
</div>
|
||||||
v-loading="loading"
|
|
||||||
:data="list"
|
<!-- 列表 -->
|
||||||
:stripe="true"
|
<div class="mobile-item-list" v-loading="loading">
|
||||||
:show-overflow-tooltip="true"
|
<div
|
||||||
class="mt-4"
|
v-for="item in list"
|
||||||
|
:key="item.id"
|
||||||
|
class="mobile-item-card"
|
||||||
>
|
>
|
||||||
<el-table-column label="编号" align="center" prop="id" />
|
<div class="mobile-item-card__header">
|
||||||
<el-table-column
|
<span class="mobile-item-card__name">规则 #{{ item.id }}</span>
|
||||||
label="规则适用人群"
|
<el-tag type="primary" size="small">上限 {{ item.maxCount }}</el-tag>
|
||||||
align="center"
|
</div>
|
||||||
:formatter="(row) => row.users?.map((user: any) => user.nickname).join(',')"
|
<div class="mobile-item-card__body">
|
||||||
/>
|
<div class="mobile-item-card__info-row">
|
||||||
<el-table-column
|
<span class="mobile-item-card__info-label">适用人群</span>
|
||||||
label="规则适用部门"
|
<span class="mobile-item-card__info-value">{{ item.users?.map((user: any) => user.nickname).join(',') || '-' }}</span>
|
||||||
align="center"
|
</div>
|
||||||
:formatter="(row) => row.depts?.map((dept: any) => dept.name).join(',')"
|
<div class="mobile-item-card__info-row">
|
||||||
/>
|
<span class="mobile-item-card__info-label">适用部门</span>
|
||||||
<el-table-column
|
<span class="mobile-item-card__info-value">{{ item.depts?.map((dept: any) => dept.name).join(',') || '-' }}</span>
|
||||||
:label="
|
</div>
|
||||||
confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT ? '拥有客户数上限' : '锁定客户数上限'
|
<div class="mobile-item-card__info-row" v-if="confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT">
|
||||||
"
|
<span class="mobile-item-card__info-label">成交客户占用</span>
|
||||||
align="center"
|
<span class="mobile-item-card__info-value">
|
||||||
prop="maxCount"
|
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="item.dealCountEnabled" />
|
||||||
/>
|
</span>
|
||||||
<el-table-column
|
</div>
|
||||||
v-if="confType === LimitConfType.CUSTOMER_QUANTITY_LIMIT"
|
<div class="mobile-item-card__info-row">
|
||||||
label="成交客户是否占用拥有客户数"
|
<span class="mobile-item-card__info-label">创建时间</span>
|
||||||
align="center"
|
<span class="mobile-item-card__info-value">{{ dateFormatter(null, null, item.createTime) }}</span>
|
||||||
prop="dealCountEnabled"
|
</div>
|
||||||
min-width="100"
|
</div>
|
||||||
>
|
<div class="mobile-item-card__footer">
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealCountEnabled" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="创建时间"
|
|
||||||
align="center"
|
|
||||||
prop="createTime"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column label="操作" align="center" min-width="110" fixed="right">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button
|
<el-button
|
||||||
link
|
size="small"
|
||||||
type="primary"
|
type="primary"
|
||||||
@click="openForm('update', scope.row.id)"
|
@click="openForm('update', item.id)"
|
||||||
v-hasPermi="['crm:customer-limit-config:update']"
|
v-hasPermi="['crm:customer-limit-config:update']"
|
||||||
>
|
>编辑</el-button>
|
||||||
编辑
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
<el-button
|
||||||
link
|
size="small"
|
||||||
type="danger"
|
type="danger"
|
||||||
@click="handleDelete(scope.row.id)"
|
@click="handleDelete(item.id)"
|
||||||
v-hasPermi="['crm:customer-limit-config:delete']"
|
v-hasPermi="['crm:customer-limit-config:delete']"
|
||||||
>
|
>删除</el-button>
|
||||||
删除
|
</div>
|
||||||
</el-button>
|
</div>
|
||||||
</template>
|
<div v-if="list.length === 0 && !loading" class="mobile-empty-tip">暂无配置数据</div>
|
||||||
</el-table-column>
|
</div>
|
||||||
</el-table>
|
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<Pagination
|
<div class="mobile-pagination" v-if="total > 0">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNo"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
:total="total"
|
:total="total"
|
||||||
v-model:page="queryParams.pageNo"
|
:page-sizes="[10, 20]"
|
||||||
v-model:limit="queryParams.pageSize"
|
layout="total, prev, pager, next"
|
||||||
@pagination="getList"
|
:pager-count="5"
|
||||||
|
@size-change="getList"
|
||||||
|
@current-change="getList"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<CustomerLimitConfigForm ref="formRef" @success="getList" />
|
<CustomerLimitConfigForm ref="formRef" @success="getList" />
|
||||||
@@ -148,3 +147,79 @@ onMounted(() => {
|
|||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-list-page {
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
.mobile-list-page__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mobile-item-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.mobile-item-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
&__name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
&__body {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
&__info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
&__info-label {
|
||||||
|
color: #909399;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
&__info-value {
|
||||||
|
color: #606266;
|
||||||
|
text-align: right;
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-empty-tip {
|
||||||
|
text-align: center;
|
||||||
|
color: #909399;
|
||||||
|
padding: 40px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.mobile-pagination {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
:deep(.el-pagination) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,22 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert title="【客户】客户管理、公海客户" url="https://doc.iocoder.cn/crm/customer/" />
|
<div class="mobile-page">
|
||||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" />
|
<!-- Tab 切换 -->
|
||||||
|
<div class="mobile-page__tabs">
|
||||||
<!-- 列表 -->
|
<el-tabs v-model="activeTab">
|
||||||
<ContentWrap>
|
<el-tab-pane label="拥有客户数限制" name="quantity">
|
||||||
<el-tabs>
|
|
||||||
<el-tab-pane label="拥有客户数限制">
|
|
||||||
<CustomerLimitConfigList :confType="LimitConfType.CUSTOMER_QUANTITY_LIMIT" />
|
<CustomerLimitConfigList :confType="LimitConfType.CUSTOMER_QUANTITY_LIMIT" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="锁定客户数限制">
|
<el-tab-pane label="锁定客户数限制" name="lock">
|
||||||
<CustomerLimitConfigList :confType="LimitConfType.CUSTOMER_LOCK_LIMIT" />
|
<CustomerLimitConfigList :confType="LimitConfType.CUSTOMER_LOCK_LIMIT" />
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</ContentWrap>
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import CustomerLimitConfigList from './CustomerLimitConfigList.vue'
|
import CustomerLimitConfigList from './CustomerLimitConfigList.vue'
|
||||||
import { LimitConfType } from '@/api/crm/customer/limitConfig'
|
import { LimitConfType } from '@/api/crm/customer/limitConfig'
|
||||||
|
|
||||||
defineOptions({ name: 'CrmCustomerLimitConfig' })
|
defineOptions({ name: 'CrmCustomerLimitConfig' })
|
||||||
|
|
||||||
|
const activeTab = ref('quantity')
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-page {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.mobile-page__tabs {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="dialogVisible" title="分配客户">
|
<el-drawer
|
||||||
|
v-model="dialogVisible"
|
||||||
|
title="分配客户"
|
||||||
|
direction="rtl"
|
||||||
|
size="100%"
|
||||||
|
:close-on-press-escape="true"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
class="mobile-form-drawer"
|
||||||
|
>
|
||||||
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
v-loading="formLoading"
|
|
||||||
:model="formData"
|
:model="formData"
|
||||||
:rules="formRules"
|
:rules="formRules"
|
||||||
label-width="100px"
|
label-position="top"
|
||||||
>
|
>
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">分配设置</div>
|
||||||
<el-form-item label="负责人" prop="ownerUserId">
|
<el-form-item label="负责人" prop="ownerUserId">
|
||||||
<el-select v-model="formData.ownerUserId" class="w-1/1">
|
<el-select v-model="formData.ownerUserId" style="width: 100%">
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in userOptions"
|
v-for="item in userOptions"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
@@ -17,12 +27,16 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
|
||||||
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
<!-- 底部操作按钮 -->
|
||||||
|
<div class="mobile-form__footer">
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
<el-button :disabled="formLoading" type="primary" @click="submitForm">确 定</el-button>
|
||||||
</Dialog>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import * as CustomerApi from '@/api/crm/customer'
|
import * as CustomerApi from '@/api/crm/customer'
|
||||||
@@ -83,3 +97,44 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -4px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,41 +1,106 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert title="【客户】客户管理、公海客户" url="https://doc.iocoder.cn/crm/customer/" />
|
<div class="mobile-page">
|
||||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" />
|
<!-- 搜索头部 -->
|
||||||
|
<div class="mobile-page__header">
|
||||||
<ContentWrap>
|
<div class="mobile-page__search">
|
||||||
<!-- 搜索工作栏 -->
|
|
||||||
<el-form
|
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
|
||||||
:model="queryParams"
|
|
||||||
class="-mb-15px"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="客户名称" prop="name">
|
|
||||||
<el-input
|
<el-input
|
||||||
v-model="queryParams.name"
|
v-model="queryParams.name"
|
||||||
class="!w-240px"
|
placeholder="搜索客户名称"
|
||||||
clearable
|
clearable
|
||||||
placeholder="请输入客户名称"
|
|
||||||
@keyup.enter="handleQuery"
|
@keyup.enter="handleQuery"
|
||||||
|
:prefix-icon="Search"
|
||||||
/>
|
/>
|
||||||
|
<el-button type="primary" :icon="Filter" @click="filterDrawerVisible = true" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-page__actions">
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['crm:customer:export']"
|
||||||
|
>
|
||||||
|
导出
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 公海客户列表 -->
|
||||||
|
<div class="mobile-page__content" v-loading="loading">
|
||||||
|
<div class="mobile-item-list">
|
||||||
|
<div
|
||||||
|
v-for="item in list"
|
||||||
|
:key="item.id"
|
||||||
|
class="mobile-item-card mobile-item-card--clickable"
|
||||||
|
@click="openDetail(item.id)"
|
||||||
|
>
|
||||||
|
<div class="mobile-item-card__header">
|
||||||
|
<span class="mobile-item-card__name">{{ item.name }}</span>
|
||||||
|
<el-tag v-if="item.dealStatus" type="success" size="small">已成交</el-tag>
|
||||||
|
<el-tag v-else type="info" size="small">未成交</el-tag>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__body">
|
||||||
|
<div class="mobile-item-card__info-row">
|
||||||
|
<span class="mobile-item-card__info-label">客户来源</span>
|
||||||
|
<span class="mobile-item-card__info-value">
|
||||||
|
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="item.source" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__info-row">
|
||||||
|
<span class="mobile-item-card__info-label">手机</span>
|
||||||
|
<span class="mobile-item-card__info-value">{{ item.mobile || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__info-row">
|
||||||
|
<span class="mobile-item-card__info-label">客户级别</span>
|
||||||
|
<span class="mobile-item-card__info-value">
|
||||||
|
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="item.level" />
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__info-row">
|
||||||
|
<span class="mobile-item-card__info-label">最后跟进</span>
|
||||||
|
<span class="mobile-item-card__info-value">{{ item.contactLastTime ? dateFormatter(null, null, item.contactLastTime) : '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-if="list.length === 0 && !loading" class="mobile-empty-tip">暂无公海客户数据</div>
|
||||||
|
</div>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="mobile-pagination" v-if="total > 0">
|
||||||
|
<el-pagination
|
||||||
|
v-model:current-page="queryParams.pageNo"
|
||||||
|
v-model:page-size="queryParams.pageSize"
|
||||||
|
:total="total"
|
||||||
|
:page-sizes="[10, 20]"
|
||||||
|
layout="total, prev, pager, next"
|
||||||
|
:pager-count="5"
|
||||||
|
@size-change="getList"
|
||||||
|
@current-change="getList"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选抽屉 -->
|
||||||
|
<el-drawer
|
||||||
|
v-model="filterDrawerVisible"
|
||||||
|
title="筛选条件"
|
||||||
|
direction="rtl"
|
||||||
|
size="100%"
|
||||||
|
:append-to-body="true"
|
||||||
|
class="mobile-form-drawer"
|
||||||
|
>
|
||||||
|
<div class="mobile-form">
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">筛选条件</div>
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
|
<el-form-item label="客户名称" prop="name">
|
||||||
|
<el-input v-model="queryParams.name" placeholder="请输入客户名称" clearable />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="手机" prop="mobile">
|
<el-form-item label="手机" prop="mobile">
|
||||||
<el-input
|
<el-input v-model="queryParams.mobile" placeholder="请输入手机" clearable />
|
||||||
v-model="queryParams.mobile"
|
|
||||||
class="!w-240px"
|
|
||||||
clearable
|
|
||||||
placeholder="请输入手机"
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="所属行业" prop="industryId">
|
<el-form-item label="所属行业" prop="industryId">
|
||||||
<el-select
|
<el-select v-model="queryParams.industryId" placeholder="请选择所属行业" clearable style="width: 100%">
|
||||||
v-model="queryParams.industryId"
|
|
||||||
class="!w-240px"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择所属行业"
|
|
||||||
>
|
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_INDUSTRY)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -45,12 +110,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="客户级别" prop="level">
|
<el-form-item label="客户级别" prop="level">
|
||||||
<el-select
|
<el-select v-model="queryParams.level" placeholder="请选择客户级别" clearable style="width: 100%">
|
||||||
v-model="queryParams.level"
|
|
||||||
class="!w-240px"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择客户级别"
|
|
||||||
>
|
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_LEVEL)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -60,12 +120,7 @@
|
|||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="客户来源" prop="source">
|
<el-form-item label="客户来源" prop="source">
|
||||||
<el-select
|
<el-select v-model="queryParams.source" placeholder="请选择客户来源" clearable style="width: 100%">
|
||||||
v-model="queryParams.source"
|
|
||||||
class="!w-240px"
|
|
||||||
clearable
|
|
||||||
placeholder="请选择客户来源"
|
|
||||||
>
|
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.CRM_CUSTOMER_SOURCE)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -74,102 +129,14 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
|
||||||
<el-button @click="handleQuery">
|
|
||||||
<Icon class="mr-5px" icon="ep:search" />
|
|
||||||
搜索
|
|
||||||
</el-button>
|
|
||||||
<el-button @click="resetQuery(undefined)">
|
|
||||||
<Icon class="mr-5px" icon="ep:refresh" />
|
|
||||||
重置
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
v-hasPermi="['crm:customer:export']"
|
|
||||||
:loading="exportLoading"
|
|
||||||
plain
|
|
||||||
type="success"
|
|
||||||
@click="handleExport"
|
|
||||||
>
|
|
||||||
<Icon class="mr-5px" icon="ep:download" />
|
|
||||||
导出
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</div>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
<!-- 列表 -->
|
<el-button @click="resetQuery" style="flex: 1">重置</el-button>
|
||||||
<ContentWrap>
|
<el-button type="primary" @click="handleFilterConfirm" style="flex: 1">确认</el-button>
|
||||||
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
</div>
|
||||||
<el-table-column align="center" label="客户名称" fixed="left" prop="name" width="160">
|
</div>
|
||||||
<template #default="scope">
|
</el-drawer>
|
||||||
<el-link :underline="false" type="primary" @click="openDetail(scope.row.id)">
|
|
||||||
{{ scope.row.name }}
|
|
||||||
</el-link>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="客户来源" prop="source" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_SOURCE" :value="scope.row.source" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="手机" align="center" prop="mobile" width="120" />
|
|
||||||
<el-table-column label="电话" align="center" prop="telephone" width="130" />
|
|
||||||
<el-table-column label="邮箱" align="center" prop="email" width="180" />
|
|
||||||
<el-table-column align="center" label="客户级别" prop="level" width="135">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_LEVEL" :value="scope.row.level" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column align="center" label="客户行业" prop="industryId" width="100">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.CRM_CUSTOMER_INDUSTRY" :value="scope.row.industryId" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
align="center"
|
|
||||||
label="下次联系时间"
|
|
||||||
prop="contactNextTime"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column align="center" label="备注" prop="remark" width="200" />
|
|
||||||
<el-table-column align="center" label="成交状态" prop="dealStatus">
|
|
||||||
<template #default="scope">
|
|
||||||
<dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.dealStatus" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
align="center"
|
|
||||||
label="最后跟进时间"
|
|
||||||
prop="contactLastTime"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column align="center" label="最后跟进记录" prop="contactLastContent" width="200" />
|
|
||||||
<el-table-column
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
align="center"
|
|
||||||
label="更新时间"
|
|
||||||
prop="updateTime"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
align="center"
|
|
||||||
label="创建时间"
|
|
||||||
prop="createTime"
|
|
||||||
width="180px"
|
|
||||||
/>
|
|
||||||
<el-table-column align="center" label="创建人" prop="creatorName" width="100px" />
|
|
||||||
</el-table>
|
|
||||||
<!-- 分页 -->
|
|
||||||
<Pagination
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
:total="total"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
@@ -177,6 +144,7 @@ import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
|||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import * as CustomerApi from '@/api/crm/customer'
|
import * as CustomerApi from '@/api/crm/customer'
|
||||||
|
import { Search, Filter } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
defineOptions({ name: 'CrmCustomerPool' })
|
defineOptions({ name: 'CrmCustomerPool' })
|
||||||
|
|
||||||
@@ -185,6 +153,7 @@ const message = useMessage() // 消息弹窗
|
|||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const list = ref([]) // 列表的数据
|
const list = ref([]) // 列表的数据
|
||||||
|
const filterDrawerVisible = ref(false) // 筛选抽屉
|
||||||
const queryParams = ref({
|
const queryParams = ref({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@@ -255,6 +224,12 @@ const handleExport = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 筛选确认 */
|
||||||
|
const handleFilterConfirm = () => {
|
||||||
|
filterDrawerVisible.value = false
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
/** 监听路由变化更新列表 */
|
/** 监听路由变化更新列表 */
|
||||||
watch(
|
watch(
|
||||||
() => currentRoute.value,
|
() => currentRoute.value,
|
||||||
@@ -268,3 +243,124 @@ onMounted(() => {
|
|||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-page {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.mobile-page__header {
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mobile-page__search {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.mobile-page__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
.mobile-page__content {
|
||||||
|
min-height: 200px;
|
||||||
|
}
|
||||||
|
.mobile-item-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.mobile-item-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
&--clickable {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s;
|
||||||
|
&:active {
|
||||||
|
background: #f5f5f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
&__name {
|
||||||
|
flex: 1;
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
&__body {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
&__info-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
&__info-label {
|
||||||
|
color: #909399;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
&__info-value {
|
||||||
|
color: #606266;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-empty-tip {
|
||||||
|
text-align: center;
|
||||||
|
color: #909399;
|
||||||
|
padding: 40px 0;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.mobile-pagination {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
:deep(.el-pagination) {
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-form {
|
||||||
|
padding: 0 4px;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -4px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,62 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert title="【客户】客户管理、公海客户" url="https://doc.iocoder.cn/crm/customer/" />
|
<div class="mobile-page" v-loading="formLoading">
|
||||||
<doc-alert title="【通用】数据权限" url="https://doc.iocoder.cn/crm/permission/" />
|
|
||||||
|
|
||||||
<ContentWrap>
|
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="formData"
|
:model="formData"
|
||||||
:rules="formRules"
|
:rules="formRules"
|
||||||
label-width="160px"
|
label-position="top"
|
||||||
v-loading="formLoading"
|
|
||||||
>
|
>
|
||||||
<el-card shadow="never">
|
<!-- 公海规则设置 -->
|
||||||
<!-- 操作 -->
|
<div class="mobile-form__section">
|
||||||
<template #header>
|
<div class="mobile-form__section-header">
|
||||||
<div class="flex items-center justify-between">
|
<span class="mobile-form__section-title">客户公海规则设置</span>
|
||||||
<CardTitle title="客户公海规则设置" />
|
|
||||||
<el-button
|
<el-button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
size="small"
|
||||||
@click="onSubmit"
|
@click="onSubmit"
|
||||||
v-hasPermi="['crm:customer-pool-config:update']"
|
v-hasPermi="['crm:customer-pool-config:update']"
|
||||||
>
|
>
|
||||||
保存
|
保存
|
||||||
</el-button>
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
<el-form-item label="客户公海规则" prop="enabled">
|
||||||
<!-- 表单 -->
|
<el-radio-group v-model="formData.enabled" @change="changeEnable">
|
||||||
<el-form-item label="客户公海规则设置" prop="enabled">
|
<el-radio :value="false">不启用</el-radio>
|
||||||
<el-radio-group v-model="formData.enabled" @change="changeEnable" class="ml-4">
|
<el-radio :value="true">启用</el-radio>
|
||||||
<el-radio :value="false" size="large">不启用</el-radio>
|
|
||||||
<el-radio :value="true" size="large">启用</el-radio>
|
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div v-if="formData.enabled">
|
</div>
|
||||||
<el-form-item>
|
|
||||||
<el-input-number class="mr-2" v-model="formData.contactExpireDays" />
|
<!-- 规则详情 -->
|
||||||
天不跟进或
|
<div class="mobile-form__section" v-if="formData.enabled">
|
||||||
<el-input-number class="mx-2" v-model="formData.dealExpireDays" />
|
<div class="mobile-form__section-title">规则详情</div>
|
||||||
天未成交
|
<div class="mobile-form__inline-group">
|
||||||
</el-form-item>
|
<el-input-number v-model="formData.contactExpireDays" :min="1" style="width: 80px" />
|
||||||
<el-form-item label="提前提醒设置" prop="notifyEnabled">
|
<span class="mobile-form__inline-text">天不跟进或</span>
|
||||||
<el-radio-group
|
<el-input-number v-model="formData.dealExpireDays" :min="1" style="width: 80px" />
|
||||||
v-model="formData.notifyEnabled"
|
<span class="mobile-form__inline-text">天未成交</span>
|
||||||
@change="changeNotifyEnable"
|
</div>
|
||||||
class="ml-4"
|
<el-form-item label="提前提醒设置" prop="notifyEnabled" style="margin-top: 16px">
|
||||||
>
|
<el-radio-group v-model="formData.notifyEnabled" @change="changeNotifyEnable">
|
||||||
<el-radio :value="false" size="large">不提醒</el-radio>
|
<el-radio :value="false">不提醒</el-radio>
|
||||||
<el-radio :value="true" size="large">提醒</el-radio>
|
<el-radio :value="true">提醒</el-radio>
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div v-if="formData.notifyEnabled">
|
<div class="mobile-form__inline-group" v-if="formData.notifyEnabled">
|
||||||
<el-form-item>
|
<span class="mobile-form__inline-text">提前</span>
|
||||||
提前 <el-input-number class="mx-2" v-model="formData.notifyDays" /> 天提醒
|
<el-input-number v-model="formData.notifyDays" :min="1" style="width: 80px" />
|
||||||
</el-form-item>
|
<span class="mobile-form__inline-text">天提醒</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as CustomerPoolConfigApi from '@/api/crm/customer/poolConfig'
|
import * as CustomerPoolConfigApi from '@/api/crm/customer/poolConfig'
|
||||||
@@ -134,3 +127,42 @@ onMounted(() => {
|
|||||||
getConfig()
|
getConfig()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-page {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f7fa;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
.mobile-form__inline-group {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
.mobile-form__inline-text {
|
||||||
|
color: #606266;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
265
src/views/erp/aftersale/aftersaleanalysis/index.vue
Normal file
265
src/views/erp/aftersale/aftersaleanalysis/index.vue
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="110px">
|
||||||
|
<!-- <el-form-item label="登记创建时间" prop="registerCreateTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.registerCreateTime"
|
||||||
|
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 label="处理完成时间" prop="processFinishTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.processFinishTime"
|
||||||
|
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 label="回访创建时间" prop="visitCreateTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.visitCreateTime"
|
||||||
|
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 label="统计上限" prop="pageSize">
|
||||||
|
<el-input-number v-model="queryParams.pageSize" :min="100" :max="2000" :step="100" />
|
||||||
|
</el-form-item> -->
|
||||||
|
<el-form-item>
|
||||||
|
<el-button type="primary" @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-form-item>
|
||||||
|
</el-form>
|
||||||
|
<!-- <el-alert
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
class="mt-2"
|
||||||
|
:title="`统计基于当前筛选条件的前 ${queryParams.pageSize} 条记录`"
|
||||||
|
/> -->
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<div v-loading="loading">
|
||||||
|
<ContentWrap>
|
||||||
|
<div class="mb-4 text-lg font-medium">售后登记统计</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
|
<SummaryCard title="登记总数" :value="registerSummary.total" icon="ep:document" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
||||||
|
<SummaryCard title="待审核" :value="registerSummary.pending" icon="ep:clock" icon-color="text-orange-600" icon-bg-color="bg-orange-100" />
|
||||||
|
<SummaryCard title="审核通过" :value="registerSummary.approved" icon="ep:circle-check" icon-color="text-green-600" icon-bg-color="bg-green-100" />
|
||||||
|
<SummaryCard title="审核驳回" :value="registerSummary.rejected" icon="ep:circle-close" icon-color="text-red-600" icon-bg-color="bg-red-100" />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">售后类型分布</div>
|
||||||
|
<Echart :height="320" :options="registerTypeOption" />
|
||||||
|
</el-card>
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">申请状态分布</div>
|
||||||
|
<Echart :height="320" :options="registerStatusOption" />
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<ContentWrap>
|
||||||
|
<div class="mb-4 text-lg font-medium">售后处理统计</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
|
<SummaryCard title="处理总数" :value="processSummary.total" icon="ep:setting" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
||||||
|
<SummaryCard title="处理中" :value="processSummary.processing" icon="ep:loading" icon-color="text-orange-600" icon-bg-color="bg-orange-100" />
|
||||||
|
<SummaryCard title="处理完成" :value="processSummary.completed" icon="ep:check" icon-color="text-green-600" icon-bg-color="bg-green-100" />
|
||||||
|
<SummaryCard title="处理失败" :value="processSummary.failed" icon="ep:close" icon-color="text-red-600" icon-bg-color="bg-red-100" />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">处理类型分布</div>
|
||||||
|
<Echart :height="320" :options="processTypeOption" />
|
||||||
|
</el-card>
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">处理状态分布</div>
|
||||||
|
<Echart :height="320" :options="processStatusOption" />
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<ContentWrap>
|
||||||
|
<div class="mb-4 text-lg font-medium">售后回访统计</div>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
|
<SummaryCard title="回访总数" :value="visitSummary.total" icon="ep:user" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
||||||
|
<SummaryCard title="平均评分" :value="visitSummary.avgRating" :decimals="1" icon="ep:star" icon-color="text-yellow-600" icon-bg-color="bg-yellow-100" />
|
||||||
|
<SummaryCard title="五星评分" :value="visitSummary.fiveStar" icon="ep:star" icon-color="text-yellow-600" icon-bg-color="bg-yellow-100" />
|
||||||
|
<SummaryCard title="强复购意愿" :value="visitSummary.highRepurchase" icon="ep:promotion" icon-color="text-green-600" icon-bg-color="bg-green-100" />
|
||||||
|
</div>
|
||||||
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">评分分布</div>
|
||||||
|
<Echart :height="320" :options="visitRatingOption" />
|
||||||
|
</el-card>
|
||||||
|
<el-card shadow="never">
|
||||||
|
<div class="mb-2 font-medium">复购意愿分布</div>
|
||||||
|
<Echart :height="320" :options="visitRepurchaseOption" />
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</ContentWrap>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { EChartsOption } from 'echarts'
|
||||||
|
import SummaryCard from '@/components/SummaryCard/index.vue'
|
||||||
|
import { Echart } from '@/components/Echart'
|
||||||
|
import {
|
||||||
|
AfterSaleAnalysisApi,
|
||||||
|
DistItem,
|
||||||
|
ProcessSummary,
|
||||||
|
RegisterSummary,
|
||||||
|
VisitSummary
|
||||||
|
} from '@/api/erp/aftersale/aftersaleanalysis'
|
||||||
|
|
||||||
|
defineOptions({ name: 'AfterSaleAnalysis' })
|
||||||
|
|
||||||
|
const loading = ref(false)
|
||||||
|
const queryFormRef = ref()
|
||||||
|
const queryParams = reactive({
|
||||||
|
registerCreateTime: [],
|
||||||
|
processFinishTime: [],
|
||||||
|
visitCreateTime: [],
|
||||||
|
pageSize: 500
|
||||||
|
})
|
||||||
|
|
||||||
|
const registerSummary = reactive<RegisterSummary>({
|
||||||
|
total: 0,
|
||||||
|
pending: 0,
|
||||||
|
approved: 0,
|
||||||
|
rejected: 0
|
||||||
|
})
|
||||||
|
const processSummary = reactive<ProcessSummary>({
|
||||||
|
total: 0,
|
||||||
|
processing: 0,
|
||||||
|
completed: 0,
|
||||||
|
failed: 0
|
||||||
|
})
|
||||||
|
const visitSummary = reactive<VisitSummary>({
|
||||||
|
total: 0,
|
||||||
|
avgRating: 0,
|
||||||
|
fiveStar: 0,
|
||||||
|
highRepurchase: 0
|
||||||
|
})
|
||||||
|
|
||||||
|
const registerTypeDist = ref<DistItem[]>([])
|
||||||
|
const registerStatusDist = ref<DistItem[]>([])
|
||||||
|
const processTypeDist = ref<DistItem[]>([])
|
||||||
|
const processStatusDist = ref<DistItem[]>([])
|
||||||
|
const visitRatingDist = ref<DistItem[]>([])
|
||||||
|
const visitRepurchaseDist = ref<DistItem[]>([])
|
||||||
|
|
||||||
|
const buildPieOption = (data: { name: string; value: number }[]): EChartsOption => ({
|
||||||
|
tooltip: { trigger: 'item' },
|
||||||
|
legend: { bottom: 0 },
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['35%', '60%'],
|
||||||
|
data,
|
||||||
|
label: { formatter: '{b}: {c}' }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const buildBarOption = (labels: string[], data: number[]): EChartsOption => ({
|
||||||
|
tooltip: { trigger: 'axis', axisPointer: { type: 'shadow' } },
|
||||||
|
grid: { left: 20, right: 20, bottom: 20, containLabel: true },
|
||||||
|
xAxis: { type: 'category', data: labels },
|
||||||
|
yAxis: { type: 'value', minInterval: 1 },
|
||||||
|
series: [{ type: 'bar', data, barWidth: '45%' }]
|
||||||
|
})
|
||||||
|
|
||||||
|
const registerTypeOption = computed(() => buildPieOption(registerTypeDist.value))
|
||||||
|
const registerStatusOption = computed(() =>
|
||||||
|
buildBarOption(
|
||||||
|
registerStatusDist.value.map((item) => item.name),
|
||||||
|
registerStatusDist.value.map((item) => item.value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const processTypeOption = computed(() =>
|
||||||
|
buildBarOption(
|
||||||
|
processTypeDist.value.map((item) => item.name),
|
||||||
|
processTypeDist.value.map((item) => item.value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const processStatusOption = computed(() => buildPieOption(processStatusDist.value))
|
||||||
|
const visitRatingOption = computed(() =>
|
||||||
|
buildBarOption(
|
||||||
|
visitRatingDist.value.map((item) => item.name),
|
||||||
|
visitRatingDist.value.map((item) => item.value)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const visitRepurchaseOption = computed(() => buildPieOption(visitRepurchaseDist.value))
|
||||||
|
|
||||||
|
const resetSummary = () => {
|
||||||
|
Object.assign(registerSummary, { total: 0, pending: 0, approved: 0, rejected: 0 })
|
||||||
|
Object.assign(processSummary, { total: 0, processing: 0, completed: 0, failed: 0 })
|
||||||
|
Object.assign(visitSummary, { total: 0, avgRating: 0, fiveStar: 0, highRepurchase: 0 })
|
||||||
|
registerTypeDist.value = []
|
||||||
|
registerStatusDist.value = []
|
||||||
|
processTypeDist.value = []
|
||||||
|
processStatusDist.value = []
|
||||||
|
visitRatingDist.value = []
|
||||||
|
visitRepurchaseDist.value = []
|
||||||
|
}
|
||||||
|
|
||||||
|
const loadData = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
resetSummary()
|
||||||
|
const res = await AfterSaleAnalysisApi.getAnalysis({
|
||||||
|
registerCreateTime: queryParams.registerCreateTime,
|
||||||
|
processFinishTime: queryParams.processFinishTime,
|
||||||
|
visitCreateTime: queryParams.visitCreateTime,
|
||||||
|
pageSize: queryParams.pageSize
|
||||||
|
})
|
||||||
|
|
||||||
|
const data = (res as any).data ?? res
|
||||||
|
|
||||||
|
Object.assign(registerSummary, data.registerSummary || {})
|
||||||
|
Object.assign(processSummary, data.processSummary || {})
|
||||||
|
Object.assign(visitSummary, data.visitSummary || {})
|
||||||
|
|
||||||
|
registerTypeDist.value = data.registerTypeDist || []
|
||||||
|
registerStatusDist.value = data.registerStatusDist || []
|
||||||
|
processTypeDist.value = data.processTypeDist || []
|
||||||
|
processStatusDist.value = data.processStatusDist || []
|
||||||
|
visitRatingDist.value = data.visitRatingDist || []
|
||||||
|
visitRepurchaseDist.value = data.visitRepurchaseDist || []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleQuery = () => {
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value?.resetFields()
|
||||||
|
queryParams.pageSize = 500
|
||||||
|
loadData()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadData()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="100px"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<el-form-item label="售后登记" prop="afterSaleId">
|
||||||
|
<el-input
|
||||||
|
v-model="selectedRegisterLabel"
|
||||||
|
placeholder="请选择已审核通过的售后登记"
|
||||||
|
readonly
|
||||||
|
@click="openRegisterSelector"
|
||||||
|
style="cursor: pointer"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button :icon="Search" @click="openRegisterSelector" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理类型" prop="processType">
|
||||||
|
<el-select v-model="formData.processType" placeholder="请选择处理类型" filterable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in processTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理数量" prop="processCount">
|
||||||
|
<el-input-number v-model="formData.processCount" :min="0" class="!w-full" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理结果" prop="processResult">
|
||||||
|
<el-input v-model="formData.processResult" placeholder="请输入处理结果" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理凭证" prop="processEvidence">
|
||||||
|
<UploadImgs v-model="processEvidenceList" :limit="5" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="业务订单" prop="relatedOrderId">
|
||||||
|
<el-input v-model="formData.relatedOrderId" placeholder="请输入业务订单" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理人" prop="processUser">
|
||||||
|
<el-input v-model="formData.processUser" placeholder="请输入处理人" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="时间" prop="processTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formData.processTime as any"
|
||||||
|
type="datetime"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
placeholder="选择时间"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item v-if="formData.processStatus === 2" label="处理完成时间" prop="finishTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="formData.finishTime as any"
|
||||||
|
type="datetime"
|
||||||
|
value-format="YYYY-MM-DD HH:mm:ss"
|
||||||
|
placeholder="选择处理完成时间"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="评价" prop="userRating">
|
||||||
|
<el-rate
|
||||||
|
v-model="formData.userRating"
|
||||||
|
:max="10"
|
||||||
|
class="!w-full"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- 售后登记选择弹窗 -->
|
||||||
|
<Dialog v-model="registerSelectorVisible" title="选择售后登记" width="70%" :appendToBody="true">
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form
|
||||||
|
ref="registerQueryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
:model="registerQueryParams"
|
||||||
|
class="-mb-15px"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item label="售后类型" prop="afterSaleType">
|
||||||
|
<el-select
|
||||||
|
v-model="registerQueryParams.afterSaleType"
|
||||||
|
placeholder="请选择售后类型"
|
||||||
|
clearable
|
||||||
|
class="!w-200px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in afterSaleTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="销售订单ID" prop="orderId">
|
||||||
|
<el-input
|
||||||
|
v-model="registerQueryParams.orderId"
|
||||||
|
class="!w-200px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入销售订单ID"
|
||||||
|
@keyup.enter="handleRegisterQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="handleRegisterQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:search" />
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetRegisterQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:refresh" />
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table v-loading="registerLoading" :data="registerList" show-overflow-tooltip>
|
||||||
|
<el-table-column label="#" width="55">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-radio :value="row.id" v-model="selectedRegisterId" @change="handleRegisterSelected(row)">
|
||||||
|
|
||||||
|
</el-radio>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="登记ID" align="center" prop="id" width="100" />
|
||||||
|
<el-table-column label="销售订单ID" align="center" prop="orderId" width="120" />
|
||||||
|
<el-table-column label="售后类型" align="center" prop="afterSaleType" width="120">
|
||||||
|
<template #default="{ row }">
|
||||||
|
{{ formatAfterSaleType(row.afterSaleType) }}
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="申请原因" align="center" prop="applyReason" min-width="150" />
|
||||||
|
<el-table-column label="申请人" align="center" prop="applicant" width="100" />
|
||||||
|
<el-table-column
|
||||||
|
label="创建时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="160px"
|
||||||
|
/>
|
||||||
|
</el-table>
|
||||||
|
<Pagination
|
||||||
|
v-model:limit="registerQueryParams.pageSize"
|
||||||
|
v-model:page="registerQueryParams.pageNo"
|
||||||
|
:total="registerTotal"
|
||||||
|
@pagination="getRegisterList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Search } from '@element-plus/icons-vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import { AfterSaleProcessApi, AfterSaleProcess } from '@/api/erp/aftersale/aftersaleprocess'
|
||||||
|
import { AfterSaleRegisterApi, AfterSaleRegister } from '@/api/erp/aftersale/aftersaleregister'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
|
||||||
|
/** ERP 售后处理 表单 */
|
||||||
|
defineOptions({ name: 'AfterSaleProcessForm' })
|
||||||
|
|
||||||
|
const processTypeOptions = [
|
||||||
|
{ label: '退货入库', value: 1 },
|
||||||
|
{ label: '换货出库', value: 2 },
|
||||||
|
{ label: '维修处理', value: 3 },
|
||||||
|
{ label: '退款处理', value: 4 },
|
||||||
|
{ label: '取消处理', value: 5 },
|
||||||
|
{ label: '其他', value: 6 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const processStatusOptions = [
|
||||||
|
{ label: '退换货', value: 1 },
|
||||||
|
{ label: '维修申请', value: 2 },
|
||||||
|
{ label: '售后评价', value: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const processEvidenceList = ref<string[]>([])
|
||||||
|
|
||||||
|
// 售后登记选择器相关
|
||||||
|
const registerSelectorVisible = ref(false)
|
||||||
|
const selectedRegisterId = ref<number>()
|
||||||
|
const selectedRegisterLabel = ref<string>('')
|
||||||
|
const registerLoading = ref(false)
|
||||||
|
const registerList = ref<AfterSaleRegister[]>([])
|
||||||
|
const registerTotal = ref(0)
|
||||||
|
const registerQueryParams = ref({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
applyStatus: 2, // 仅已审核通过
|
||||||
|
afterSaleType: undefined,
|
||||||
|
orderId: undefined
|
||||||
|
})
|
||||||
|
const registerQueryFormRef = ref()
|
||||||
|
|
||||||
|
const afterSaleTypeOptions = [
|
||||||
|
{ label: '退货', value: 1 },
|
||||||
|
{ label: '换货', value: 2 },
|
||||||
|
{ label: '维修', value: 3 },
|
||||||
|
{ label: '退款', value: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
/** 打开售后登记选择器 */
|
||||||
|
const openRegisterSelector = () => {
|
||||||
|
registerSelectorVisible.value = true
|
||||||
|
registerQueryParams.value = {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
applyStatus: 2,
|
||||||
|
afterSaleType: undefined,
|
||||||
|
orderId: undefined
|
||||||
|
}
|
||||||
|
getRegisterList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询售后登记列表 */
|
||||||
|
const getRegisterList = async () => {
|
||||||
|
registerLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = await AfterSaleRegisterApi.getAfterSaleRegisterPage(registerQueryParams.value)
|
||||||
|
registerList.value = data.list
|
||||||
|
registerTotal.value = data.total
|
||||||
|
} finally {
|
||||||
|
registerLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索售后登记 */
|
||||||
|
const handleRegisterQuery = () => {
|
||||||
|
registerQueryParams.value.pageNo = 1
|
||||||
|
getRegisterList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置售后登记查询 */
|
||||||
|
const resetRegisterQuery = () => {
|
||||||
|
registerQueryParams.value = {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
applyStatus: 2,
|
||||||
|
afterSaleType: undefined,
|
||||||
|
orderId: undefined
|
||||||
|
}
|
||||||
|
getRegisterList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选中售后登记 */
|
||||||
|
const handleRegisterSelected = (row: AfterSaleRegister) => {
|
||||||
|
formData.value.afterSaleId = row.id
|
||||||
|
selectedRegisterLabel.value = `登记 #${row.id}(订单 ${row.orderId || '-'})`
|
||||||
|
selectedRegisterId.value = row.id
|
||||||
|
registerSelectorVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 格式化售后类型 */
|
||||||
|
const formatAfterSaleType = (value?: number) => {
|
||||||
|
if (!value) return '-'
|
||||||
|
const match = afterSaleTypeOptions.find(item => item.value === value)
|
||||||
|
return match?.label || String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const userStore = useUserStoreWithOut()
|
||||||
|
const currentUserNickname = computed(() => userStore.getUser?.nickname || '')
|
||||||
|
const nowString = () => dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
const formData = ref<Partial<AfterSaleProcess>>({
|
||||||
|
id: undefined,
|
||||||
|
afterSaleId: undefined,
|
||||||
|
processType: undefined,
|
||||||
|
processCount: undefined,
|
||||||
|
processResult: undefined,
|
||||||
|
processEvidence: '',
|
||||||
|
relatedOrderId: undefined,
|
||||||
|
processStatus: undefined,
|
||||||
|
processFailReason: undefined,
|
||||||
|
processUser: undefined,
|
||||||
|
processTime: undefined,
|
||||||
|
finishTime: undefined,
|
||||||
|
userRating: undefined
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
afterSaleId: [{ required: true, message: '售后登记不能为空', trigger: 'change' }],
|
||||||
|
processType: [{ required: true, message: '处理类型不能为空', trigger: 'change' }],
|
||||||
|
processResult: [{ required: true, message: '处理结果不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
// 同步处理凭证列表和表单数据
|
||||||
|
watch(
|
||||||
|
() => formData.value.processEvidence,
|
||||||
|
(newVal: string) => {
|
||||||
|
processEvidenceList.value = newVal ? newVal.split(',').filter(url => url.trim()) : []
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
processEvidenceList,
|
||||||
|
(newList: string[]) => {
|
||||||
|
formData.value.processEvidence = newList?.length ? newList.join(',') : ''
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
formData.value = await AfterSaleProcessApi.getAfterSaleProcess(id)
|
||||||
|
// 如果有售后登记ID,获取登记信息显示
|
||||||
|
if (formData.value.afterSaleId) {
|
||||||
|
const register = await AfterSaleRegisterApi.getAfterSaleRegister(formData.value.afterSaleId)
|
||||||
|
selectedRegisterLabel.value = `登记 #${register.id}(订单 ${register.orderId || '-'})`
|
||||||
|
selectedRegisterId.value = register.id
|
||||||
|
}
|
||||||
|
ensureDefaultsForExisting()
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单
|
||||||
|
await formRef.value.validate()
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
let data = formData.value as unknown as AfterSaleProcess
|
||||||
|
|
||||||
|
// 如果上传了处理凭证,自动设置为处理完成状态
|
||||||
|
if (data.processEvidence && data.processEvidence.trim() !== '') {
|
||||||
|
data = {
|
||||||
|
...data,
|
||||||
|
processStatus: 2, // 处理完成
|
||||||
|
finishTime: nowString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await AfterSaleProcessApi.createAfterSaleProcess(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await AfterSaleProcessApi.updateAfterSaleProcess(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
afterSaleId: undefined,
|
||||||
|
processType: undefined,
|
||||||
|
processCount: undefined,
|
||||||
|
processResult: undefined,
|
||||||
|
processEvidence: '',
|
||||||
|
relatedOrderId: undefined,
|
||||||
|
processStatus: 1, // 新增时默认为处理中状态
|
||||||
|
processFailReason: undefined,
|
||||||
|
processUser: currentUserNickname.value || undefined,
|
||||||
|
processTime: nowString(),
|
||||||
|
finishTime: undefined,
|
||||||
|
userRating: undefined
|
||||||
|
}
|
||||||
|
selectedRegisterLabel.value = ''
|
||||||
|
selectedRegisterId.value = undefined
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const ensureDefaultsForExisting = () => {
|
||||||
|
if (!formData.value.processUser) {
|
||||||
|
formData.value.processUser = currentUserNickname.value || undefined
|
||||||
|
}
|
||||||
|
if (!formData.value.processTime) {
|
||||||
|
formData.value.processTime = nowString()
|
||||||
|
}
|
||||||
|
if (formData.value.processStatus === 2 && !formData.value.finishTime) {
|
||||||
|
formData.value.finishTime = nowString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
405
src/views/erp/aftersale/aftersaleprocess/index.vue
Normal file
405
src/views/erp/aftersale/aftersaleprocess/index.vue
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-form-item label="售后登记号" prop="afterSaleId">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.afterSaleId"
|
||||||
|
placeholder="请输入售后登记号"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理类型" prop="processType">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.processType"
|
||||||
|
placeholder="请选择处理类型"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in processTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理结果" prop="processResult">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.processResult"
|
||||||
|
placeholder="请输入处理结果"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理状态" prop="processStatus">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.processStatus"
|
||||||
|
placeholder="请选择处理状态"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in processStatusOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理人" prop="processUser">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.processUser"
|
||||||
|
placeholder="请输入处理人"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="处理完成时间" prop="finishTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.finishTime"
|
||||||
|
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
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="openForm('create')"
|
||||||
|
v-hasPermi="['erp:after-sale-process:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['erp:after-sale-process:export']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:disabled="isEmpty(checkedIds)"
|
||||||
|
@click="handleDeleteBatch"
|
||||||
|
v-hasPermi="['erp:after-sale-process:delete']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table
|
||||||
|
row-key="id"
|
||||||
|
v-loading="loading"
|
||||||
|
:data="list"
|
||||||
|
:stripe="true"
|
||||||
|
:show-overflow-tooltip="true"
|
||||||
|
@selection-change="handleRowCheckboxChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column label="序号" align="center" type="index" width="80" />
|
||||||
|
<el-table-column
|
||||||
|
label="处理类型"
|
||||||
|
align="center"
|
||||||
|
prop="processType"
|
||||||
|
:formatter="formatProcessType"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="处理状态"
|
||||||
|
align="center"
|
||||||
|
prop="processStatus"
|
||||||
|
:formatter="formatProcessStatus"
|
||||||
|
/>
|
||||||
|
<el-table-column label="处理人" align="center" prop="processUser" />
|
||||||
|
<el-table-column label="用户评价" align="center" prop="userRating">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tooltip :content="`${scope.row.userRating || 0} 分`" placement="top">
|
||||||
|
<el-icon size="20" :style="{ color: getRatingColor(scope.row.userRating) }">
|
||||||
|
<Star />
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column
|
||||||
|
label="创建时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="180px"
|
||||||
|
/>
|
||||||
|
<el-table-column label="操作" align="center" min-width="200px">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" @click="openDetail(scope.row)">详情</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sale-process:update']"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="success"
|
||||||
|
@click="handleProcessComplete(scope.row)"
|
||||||
|
v-if="scope.row.processStatus === 1"
|
||||||
|
v-hasPermi="['erp:after-sale-process:update']"
|
||||||
|
>
|
||||||
|
处理完成
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sale-process:delete']"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination
|
||||||
|
:total="total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<AfterSaleProcessForm ref="formRef" @success="getList" />
|
||||||
|
|
||||||
|
<!-- 详情弹窗 -->
|
||||||
|
<el-dialog v-model="detailVisible" title="售后处理详情" width="560px">
|
||||||
|
<el-descriptions v-if="detailRow" :column="2" border>
|
||||||
|
<el-descriptions-item label="售后登记号">{{ detailRow.afterSaleId }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理类型">{{ formatProcessType(detailRow, null as any, detailRow.processType as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理数量">{{ detailRow.processCount ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="业务订单号">{{ detailRow.relatedOrderId ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理结果">{{ detailRow.processResult ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="失败原因">{{ detailRow.processFailReason ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理状态">{{ formatProcessStatus(detailRow, null as any, detailRow.processStatus as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理人">{{ detailRow.processUser ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理时间">{{ dateFormatter(detailRow, null as any, detailRow.processTime as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="处理完成时间">{{ dateFormatter(detailRow, null as any, detailRow.finishTime as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="用户评价">
|
||||||
|
<el-tooltip :content="`${detailRow.userRating || 0} 分`" placement="top">
|
||||||
|
<el-icon size="20" :style="{ color: getRatingColor(detailRow.userRating) }">
|
||||||
|
<Star />
|
||||||
|
</el-icon>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建时间">{{ dateFormatter(detailRow, null as any, (detailRow as any).createTime) }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="detailVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
import { AfterSaleProcessApi, AfterSaleProcess } from '@/api/erp/aftersale/aftersaleprocess'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
import AfterSaleProcessForm from './AfterSaleProcessForm.vue'
|
||||||
|
import { Star } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
|
const processTypeOptions = [
|
||||||
|
{ label: '退货入库', value: 1 },
|
||||||
|
{ label: '换货出库', value: 2 },
|
||||||
|
{ label: '维修处理', value: 3 },
|
||||||
|
{ label: '退款处理', value: 4 },
|
||||||
|
{ label: '取消处理', value: 5 },
|
||||||
|
{ label: '其他', value: 6 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const processStatusOptions = [
|
||||||
|
{ label: '处理中', value: 1 },
|
||||||
|
{ label: '处理完成', value: 2 },
|
||||||
|
{ label: '处理失败', value: 3 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const optionLabel = (options: { label: string; value: number }[], value?: number | string) => {
|
||||||
|
if (value === undefined || value === null || value === '') return '-'
|
||||||
|
const match = options.find((item) => item.value === value || item.value === Number(value))
|
||||||
|
return match?.label ?? String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatProcessType = (_row: AfterSaleProcess, _column: any, value: number) =>
|
||||||
|
optionLabel(processTypeOptions, value)
|
||||||
|
const formatProcessStatus = (_row: AfterSaleProcess, _column: any, value: number) =>
|
||||||
|
optionLabel(processStatusOptions, value)
|
||||||
|
|
||||||
|
/** ERP 售后处理 列表 */
|
||||||
|
defineOptions({ name: 'AfterSaleProcess' })
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const userStore = useUserStoreWithOut()
|
||||||
|
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref<AfterSaleProcess[]>([]) // 列表的数据
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
afterSaleId: undefined,
|
||||||
|
processType: undefined,
|
||||||
|
processCount: undefined,
|
||||||
|
processResult: undefined,
|
||||||
|
processEvidence: undefined,
|
||||||
|
relatedOrderId: undefined,
|
||||||
|
processStatus: undefined,
|
||||||
|
processFailReason: undefined,
|
||||||
|
processUser: undefined,
|
||||||
|
processTime: [],
|
||||||
|
finishTime: [],
|
||||||
|
createTime: [],
|
||||||
|
userRating: undefined
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await AfterSaleProcessApi.getAfterSaleProcessPage(queryParams)
|
||||||
|
list.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加/修改操作 */
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await AfterSaleProcessApi.deleteAfterSaleProcess(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理完成操作 */
|
||||||
|
const handleProcessComplete = async (row: AfterSaleProcess) => {
|
||||||
|
try {
|
||||||
|
// 处理完成的二次确认
|
||||||
|
await message.confirm('确认处理完成吗?')
|
||||||
|
// 更新状态为处理完成
|
||||||
|
const updateData = {
|
||||||
|
...row,
|
||||||
|
processStatus: 2,
|
||||||
|
processUser: userStore.getUser?.nickname || row.processUser,
|
||||||
|
finishTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
await AfterSaleProcessApi.updateAfterSaleProcess(updateData)
|
||||||
|
message.success('处理完成')
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量删除ERP 售后处理 */
|
||||||
|
const handleDeleteBatch = async () => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
await AfterSaleProcessApi.deleteAfterSaleProcessList(checkedIds.value);
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
await getList();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedIds = ref<number[]>([])
|
||||||
|
const handleRowCheckboxChange = (records: AfterSaleProcess[]) => {
|
||||||
|
checkedIds.value = records.map((item) => item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailVisible = ref(false)
|
||||||
|
const detailRow = ref<AfterSaleProcess | null>(null)
|
||||||
|
const openDetail = (row: AfterSaleProcess) => {
|
||||||
|
detailRow.value = row
|
||||||
|
detailVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await message.exportConfirm()
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await AfterSaleProcessApi.exportAfterSaleProcess(queryParams)
|
||||||
|
download.excel(data, 'ERP 售后处理.xls')
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据评分获取颜色 */
|
||||||
|
const getRatingColor = (rating?: number) => {
|
||||||
|
if (!rating || rating === 0) return '#cccccc' // 灰色表示无评价
|
||||||
|
if (rating < 4) return '#ff4d4f' // 红色表示差评
|
||||||
|
if (rating < 7) return '#ffa940' // 橙色表示一般
|
||||||
|
if (rating < 9) return '#52c41a' // 绿色表示好评
|
||||||
|
return '#1890ff' // 蓝色表示优秀
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,307 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="100px"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<el-form-item label="销售订单" prop="orderId">
|
||||||
|
<el-input
|
||||||
|
v-model="selectedOrderNo"
|
||||||
|
placeholder="请选择销售订单"
|
||||||
|
readonly
|
||||||
|
@click="openOrderSelector"
|
||||||
|
style="cursor: pointer"
|
||||||
|
>
|
||||||
|
<template #append>
|
||||||
|
<el-button :icon="Search" @click="openOrderSelector" />
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="售后类型" prop="afterSaleType">
|
||||||
|
<el-select v-model="formData.afterSaleType" placeholder="请选择售后类型" filterable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in afterSaleTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="申请原因" prop="applyReason">
|
||||||
|
<el-input v-model="formData.applyReason" placeholder="请输入申请原因" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="联系人" prop="contactName">
|
||||||
|
<el-input v-model="formData.contactName" placeholder="请输入联系人" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="联系电话" prop="contactPhone">
|
||||||
|
<el-input v-model="formData.contactPhone" placeholder="请输入联系电话" maxlength="20" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="申请人" prop="applicant">
|
||||||
|
<el-input v-model="formData.applicant" placeholder="请输入申请人" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- 销售订单选择弹窗 -->
|
||||||
|
<Dialog v-model="orderSelectorVisible" title="选择销售订单" width="70%" :appendToBody="true">
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form
|
||||||
|
ref="orderQueryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
:model="orderQueryParams"
|
||||||
|
class="-mb-15px"
|
||||||
|
label-width="100px"
|
||||||
|
>
|
||||||
|
<el-form-item label="订单单号" prop="no">
|
||||||
|
<el-input
|
||||||
|
v-model="orderQueryParams.no"
|
||||||
|
class="!w-200px"
|
||||||
|
clearable
|
||||||
|
placeholder="请输入订单单号"
|
||||||
|
@keyup.enter="handleOrderQuery"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button @click="handleOrderQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:search" />
|
||||||
|
搜索
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="resetOrderQuery">
|
||||||
|
<Icon class="mr-5px" icon="ep:refresh" />
|
||||||
|
重置
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<el-table v-loading="orderLoading" :data="orderList" show-overflow-tooltip>
|
||||||
|
<el-table-column label="#" width="55">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-radio :value="row.id" v-model="selectedOrderId" @change="handleOrderSelected(row)">
|
||||||
|
|
||||||
|
</el-radio>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="订单单号" align="center" prop="no" min-width="140" />
|
||||||
|
<el-table-column label="客户" align="center" prop="customerName" min-width="120" />
|
||||||
|
<el-table-column
|
||||||
|
label="订单时间"
|
||||||
|
align="center"
|
||||||
|
prop="orderTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="160px"
|
||||||
|
/>
|
||||||
|
</el-table>
|
||||||
|
<Pagination
|
||||||
|
v-model:limit="orderQueryParams.pageSize"
|
||||||
|
v-model:page="orderQueryParams.pageNo"
|
||||||
|
:total="orderTotal"
|
||||||
|
@pagination="getOrderList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Search } from '@element-plus/icons-vue'
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import { AfterSaleRegisterApi, AfterSaleRegister } from '@/api/erp/aftersale/aftersaleregister'
|
||||||
|
import { SaleOrderApi, SaleOrderVO } from '@/api/erp/sale/order'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
|
||||||
|
/** ERP 售后登记 表单 */
|
||||||
|
defineOptions({ name: 'AfterSaleRegisterForm' })
|
||||||
|
|
||||||
|
const afterSaleTypeOptions = [
|
||||||
|
{ label: '退货', value: 1 },
|
||||||
|
{ label: '换货', value: 2 },
|
||||||
|
{ label: '维修', value: 3 },
|
||||||
|
{ label: '退款', value: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const applyStatusOptions = [
|
||||||
|
{ label: '待审核', value: 1 },
|
||||||
|
{ label: '审核通过', value: 2 },
|
||||||
|
{ label: '审核驳回', value: 3 },
|
||||||
|
{ label: '已取消', value: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 销售订单选择器相关
|
||||||
|
const orderSelectorVisible = ref(false)
|
||||||
|
const selectedOrderId = ref<number>()
|
||||||
|
const selectedOrderNo = ref<string>('')
|
||||||
|
const orderLoading = ref(false)
|
||||||
|
const orderList = ref<SaleOrderVO[]>([])
|
||||||
|
const orderTotal = ref(0)
|
||||||
|
const orderQueryParams = ref({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
no: ''
|
||||||
|
})
|
||||||
|
const orderQueryFormRef = ref()
|
||||||
|
|
||||||
|
/** 打开订单选择器 */
|
||||||
|
const openOrderSelector = () => {
|
||||||
|
orderSelectorVisible.value = true
|
||||||
|
orderQueryParams.value = {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
no: ''
|
||||||
|
}
|
||||||
|
getOrderList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 查询订单列表 */
|
||||||
|
const getOrderList = async () => {
|
||||||
|
orderLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = await SaleOrderApi.getSaleOrderPage(orderQueryParams.value)
|
||||||
|
orderList.value = data.list
|
||||||
|
orderTotal.value = data.total
|
||||||
|
} finally {
|
||||||
|
orderLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索订单 */
|
||||||
|
const handleOrderQuery = () => {
|
||||||
|
orderQueryParams.value.pageNo = 1
|
||||||
|
getOrderList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置订单查询 */
|
||||||
|
const resetOrderQuery = () => {
|
||||||
|
orderQueryParams.value = {
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
no: ''
|
||||||
|
}
|
||||||
|
getOrderList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 选中订单 */
|
||||||
|
const handleOrderSelected = (row: SaleOrderVO) => {
|
||||||
|
formData.value.orderId = row.id
|
||||||
|
selectedOrderNo.value = row.no || ''
|
||||||
|
selectedOrderId.value = row.id
|
||||||
|
orderSelectorVisible.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const userStore = useUserStoreWithOut()
|
||||||
|
const currentUserNickname = computed(() => userStore.getUser?.nickname || '')
|
||||||
|
const nowString = () => dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
const formData = ref<Partial<AfterSaleRegister>>({
|
||||||
|
id: undefined,
|
||||||
|
orderId: undefined,
|
||||||
|
orderItemId: undefined,
|
||||||
|
afterSaleType: undefined,
|
||||||
|
applyReason: undefined,
|
||||||
|
contactName: undefined,
|
||||||
|
contactPhone: undefined,
|
||||||
|
applyStatus: undefined,
|
||||||
|
rejectReason: undefined,
|
||||||
|
applicant: undefined,
|
||||||
|
auditUser: undefined,
|
||||||
|
auditTime: undefined
|
||||||
|
})
|
||||||
|
const formRules = reactive({
|
||||||
|
afterSaleType: [{ required: true, message: '售后类型不能为空', trigger: 'change' }],
|
||||||
|
applyReason: [{ required: true, message: '申请原因不能为空', trigger: 'blur' }]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
formData.value = await AfterSaleRegisterApi.getAfterSaleRegister(id)
|
||||||
|
// 如果有订单ID,获取订单号显示
|
||||||
|
if (formData.value.orderId) {
|
||||||
|
const order = await SaleOrderApi.getSaleOrder(formData.value.orderId)
|
||||||
|
selectedOrderNo.value = order.no || ''
|
||||||
|
selectedOrderId.value = order.id
|
||||||
|
}
|
||||||
|
ensureDefaultsForExisting()
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单(当前无必填规则,直接通过)
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = formData.value as unknown as AfterSaleRegister
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await AfterSaleRegisterApi.createAfterSaleRegister(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await AfterSaleRegisterApi.updateAfterSaleRegister(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
orderId: undefined,
|
||||||
|
orderItemId: undefined,
|
||||||
|
afterSaleType: undefined,
|
||||||
|
applyReason: undefined,
|
||||||
|
contactName: undefined,
|
||||||
|
contactPhone: undefined,
|
||||||
|
applyStatus: 1, // 新增时默认为待审核状态
|
||||||
|
rejectReason: undefined,
|
||||||
|
applicant: currentUserNickname.value || undefined,
|
||||||
|
auditUser: undefined,
|
||||||
|
auditTime: undefined
|
||||||
|
}
|
||||||
|
selectedOrderNo.value = ''
|
||||||
|
selectedOrderId.value = undefined
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
const ensureDefaultsForExisting = () => {
|
||||||
|
if (!formData.value.applicant) {
|
||||||
|
formData.value.applicant = currentUserNickname.value || undefined
|
||||||
|
}
|
||||||
|
if (formData.value.applyStatus === 2) {
|
||||||
|
if (!formData.value.auditUser) {
|
||||||
|
formData.value.auditUser = currentUserNickname.value || undefined
|
||||||
|
}
|
||||||
|
if (!formData.value.auditTime) {
|
||||||
|
formData.value.auditTime = nowString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
391
src/views/erp/aftersale/aftersaleregister/index.vue
Normal file
391
src/views/erp/aftersale/aftersaleregister/index.vue
Normal file
@@ -0,0 +1,391 @@
|
|||||||
|
<template>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="120px"
|
||||||
|
>
|
||||||
|
<el-form-item label="销售订单" prop="orderId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.orderId"
|
||||||
|
placeholder="请选择销售订单"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in saleOrderOptions"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.no"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="售后类型" prop="afterSaleType">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.afterSaleType"
|
||||||
|
placeholder="请选择售后类型"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in afterSaleTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="申请原因" prop="applyReason">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.applyReason"
|
||||||
|
placeholder="请输入申请原因"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="申请状态" prop="applyStatus">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.applyStatus"
|
||||||
|
placeholder="请选择申请状态"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in applyStatusOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="申请人" prop="applicant">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.applicant"
|
||||||
|
placeholder="请输入申请人"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="审核人" prop="auditUser">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.auditUser"
|
||||||
|
placeholder="请输入审核人"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</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
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="openForm('create')"
|
||||||
|
v-hasPermi="['erp:after-sale-register:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['erp:after-sale-register:export']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
:disabled="isEmpty(checkedIds)"
|
||||||
|
@click="handleDeleteBatch"
|
||||||
|
v-hasPermi="['erp:after-sale-register:delete']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table
|
||||||
|
row-key="id"
|
||||||
|
v-loading="loading"
|
||||||
|
:data="list"
|
||||||
|
:stripe="true"
|
||||||
|
:show-overflow-tooltip="true"
|
||||||
|
@selection-change="handleRowCheckboxChange"
|
||||||
|
>
|
||||||
|
<el-table-column type="selection" width="55" />
|
||||||
|
<el-table-column label="序号" align="center" type="index" width="80" />
|
||||||
|
<el-table-column
|
||||||
|
label="售后类型"
|
||||||
|
align="center"
|
||||||
|
prop="afterSaleType"
|
||||||
|
:formatter="formatAfterSaleType"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="申请状态"
|
||||||
|
align="center"
|
||||||
|
prop="applyStatus"
|
||||||
|
:formatter="formatApplyStatus"
|
||||||
|
/>
|
||||||
|
<el-table-column label="申请人" align="center" prop="applicant" />
|
||||||
|
<el-table-column label="审核人" align="center" prop="auditUser" />
|
||||||
|
<el-table-column
|
||||||
|
label="创建时间"
|
||||||
|
align="center"
|
||||||
|
prop="createTime"
|
||||||
|
:formatter="dateFormatter"
|
||||||
|
width="180px"
|
||||||
|
/>
|
||||||
|
<el-table-column label="操作" align="center" min-width="200px">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" @click="openDetail(scope.row)">详情</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sale-register:update']"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="success"
|
||||||
|
@click="handleAudit(scope.row)"
|
||||||
|
v-if="scope.row.applyStatus === 1"
|
||||||
|
v-hasPermi="['erp:after-sale-register:update']"
|
||||||
|
>
|
||||||
|
审核通过
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sale-register:delete']"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination
|
||||||
|
:total="total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<AfterSaleRegisterForm ref="formRef" @success="getList" />
|
||||||
|
|
||||||
|
<!-- 详情弹窗 -->
|
||||||
|
<el-dialog v-model="detailVisible" title="售后登记详情" width="560px">
|
||||||
|
<el-descriptions v-if="detailRow" :column="2" border>
|
||||||
|
<el-descriptions-item label="销售订单">{{ detailRow.orderId }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="售后类型">{{ formatAfterSaleType(detailRow, null as any, detailRow.afterSaleType as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="申请原因">{{ detailRow.applyReason ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="联系人">{{ detailRow.contactName ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="联系电话">{{ detailRow.contactPhone ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="申请状态">{{ formatApplyStatus(detailRow, null as any, detailRow.applyStatus as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="驳回原因">{{ detailRow.rejectReason ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="申请人">{{ detailRow.applicant ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="审核人">{{ detailRow.auditUser ?? '-' }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="审核时间">{{ dateFormatter(detailRow, null as any, detailRow.auditTime as any) }}</el-descriptions-item>
|
||||||
|
<el-descriptions-item label="创建时间">{{ dateFormatter(detailRow, null as any, (detailRow as any).createTime) }}</el-descriptions-item>
|
||||||
|
</el-descriptions>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="detailVisible = false">关闭</el-button>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
import { AfterSaleRegisterApi, AfterSaleRegister } from '@/api/erp/aftersale/aftersaleregister'
|
||||||
|
import { SaleOrderApi, SaleOrderVO } from '@/api/erp/sale/order'
|
||||||
|
import { useUserStoreWithOut } from '@/store/modules/user'
|
||||||
|
import AfterSaleRegisterForm from './AfterSaleRegisterForm.vue'
|
||||||
|
|
||||||
|
const afterSaleTypeOptions = [
|
||||||
|
{ label: '退货', value: 1 },
|
||||||
|
{ label: '换货', value: 2 },
|
||||||
|
{ label: '维修', value: 3 },
|
||||||
|
{ label: '退款', value: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const applyStatusOptions = [
|
||||||
|
{ label: '待审核', value: 1 },
|
||||||
|
{ label: '审核通过', value: 2 },
|
||||||
|
{ label: '审核驳回', value: 3 },
|
||||||
|
{ label: '已取消', value: 4 }
|
||||||
|
]
|
||||||
|
|
||||||
|
const optionLabel = (options: { label: string; value: number }[], value?: number | string) => {
|
||||||
|
if (value === undefined || value === null || value === '') return '-'
|
||||||
|
const match = options.find((item) => item.value === value || item.value === Number(value))
|
||||||
|
return match?.label ?? String(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const formatAfterSaleType = (_row: AfterSaleRegister, _column: any, value: number) =>
|
||||||
|
optionLabel(afterSaleTypeOptions, value)
|
||||||
|
const formatApplyStatus = (_row: AfterSaleRegister, _column: any, value: number) =>
|
||||||
|
optionLabel(applyStatusOptions, value)
|
||||||
|
|
||||||
|
const saleOrderOptions = ref<SaleOrderVO[]>([])
|
||||||
|
const fetchSaleOrderOptions = async () => {
|
||||||
|
const data = await SaleOrderApi.getSaleOrderPage({ pageNo: 1, pageSize: 100 })
|
||||||
|
saleOrderOptions.value = data.list || []
|
||||||
|
}
|
||||||
|
|
||||||
|
/** ERP 售后登记 列表 */
|
||||||
|
defineOptions({ name: 'AfterSaleRegister' })
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const userStore = useUserStoreWithOut()
|
||||||
|
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref<AfterSaleRegister[]>([]) // 列表的数据
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
orderId: undefined,
|
||||||
|
orderItemId: undefined,
|
||||||
|
afterSaleType: undefined,
|
||||||
|
applyReason: undefined,
|
||||||
|
contactName: undefined,
|
||||||
|
contactPhone: undefined,
|
||||||
|
applyStatus: undefined,
|
||||||
|
rejectReason: undefined,
|
||||||
|
applicant: undefined,
|
||||||
|
auditUser: undefined,
|
||||||
|
auditTime: [],
|
||||||
|
createTime: []
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await AfterSaleRegisterApi.getAfterSaleRegisterPage(queryParams)
|
||||||
|
list.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加/修改操作 */
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await AfterSaleRegisterApi.deleteAfterSaleRegister(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 审核通过操作 */
|
||||||
|
const handleAudit = async (row: AfterSaleRegister) => {
|
||||||
|
try {
|
||||||
|
// 审核确认
|
||||||
|
await message.confirm('确认审核通过该售后登记吗?')
|
||||||
|
// 更新状态为审核通过
|
||||||
|
const updateData = {
|
||||||
|
...row,
|
||||||
|
applyStatus: 2,
|
||||||
|
auditUser: userStore.getUser?.nickname || '',
|
||||||
|
auditTime: dayjs().format('YYYY-MM-DD HH:mm:ss')
|
||||||
|
}
|
||||||
|
await AfterSaleRegisterApi.updateAfterSaleRegister(updateData)
|
||||||
|
message.success('审核通过成功')
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量删除ERP 售后登记 */
|
||||||
|
const handleDeleteBatch = async () => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
await AfterSaleRegisterApi.deleteAfterSaleRegisterList(checkedIds.value);
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
await getList();
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedIds = ref<number[]>([])
|
||||||
|
const handleRowCheckboxChange = (records: AfterSaleRegister[]) => {
|
||||||
|
checkedIds.value = records.map((item) => item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailVisible = ref(false)
|
||||||
|
const detailRow = ref<AfterSaleRegister | null>(null)
|
||||||
|
const openDetail = (row: AfterSaleRegister) => {
|
||||||
|
detailRow.value = row
|
||||||
|
detailVisible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await message.exportConfirm()
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await AfterSaleRegisterApi.exportAfterSaleRegister(queryParams)
|
||||||
|
download.excel(data, 'ERP 售后登记.xls')
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
fetchSaleOrderOptions()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
267
src/views/erp/aftersale/aftersalesvisit/AfterSalesVisitForm.vue
Normal file
267
src/views/erp/aftersale/aftersalesvisit/AfterSalesVisitForm.vue
Normal file
@@ -0,0 +1,267 @@
|
|||||||
|
<template>
|
||||||
|
<Dialog :title="dialogTitle" v-model="dialogVisible" width="600px">
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-width="90px"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<!-- 客户基本信息 -->
|
||||||
|
<el-divider content-position="left">客户信息</el-divider>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="客户名称" prop="customerName">
|
||||||
|
<el-select
|
||||||
|
v-model="formData.customerName"
|
||||||
|
placeholder="请选择客户"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
@change="handleCustomerChange"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="customer in customerList"
|
||||||
|
:key="customer.id"
|
||||||
|
:label="customer.name"
|
||||||
|
:value="customer.name"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="联系方式" prop="contactInfo">
|
||||||
|
<el-input v-model="formData.contactInfo" placeholder="请输入联系方式" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="客户类型" prop="customerType">
|
||||||
|
<el-select v-model="formData.customerType" placeholder="请选择客户类型" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in customerTypeOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="客户用途" prop="customerUsage">
|
||||||
|
<el-input v-model="formData.customerUsage" placeholder="请输入客户用途" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 产品信息 -->
|
||||||
|
<el-divider content-position="left">产品信息</el-divider>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="产品名称" prop="productName">
|
||||||
|
<el-input v-model="formData.productName" placeholder="请输入产品名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="产品评价" prop="productEvaluation">
|
||||||
|
<el-input
|
||||||
|
v-model="formData.productEvaluation"
|
||||||
|
placeholder="请输入产品评价"
|
||||||
|
type="textarea"
|
||||||
|
:rows="2"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<!-- 服务评价 -->
|
||||||
|
<el-divider content-position="left">服务评价</el-divider>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="采购意愿" prop="repurchaseIntention">
|
||||||
|
<el-select v-model="formData.repurchaseIntention" placeholder="请选择采购意愿" clearable>
|
||||||
|
<el-option
|
||||||
|
v-for="item in repurchaseOptions"
|
||||||
|
:key="item.value"
|
||||||
|
:label="item.label"
|
||||||
|
:value="item.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="服务评分" prop="serviceRating">
|
||||||
|
<el-rate
|
||||||
|
v-model="formData.serviceRating"
|
||||||
|
:max="5"
|
||||||
|
:allow-half="false"
|
||||||
|
:show-text="false"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm" :loading="formLoading">
|
||||||
|
确定
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { AfterSalesVisitApi, AfterSalesVisit } from '@/api/erp/aftersale/aftersalesvisit'
|
||||||
|
import { CustomerApi, CustomerVO } from '@/api/erp/sale/customer'
|
||||||
|
|
||||||
|
/** 售后回访 表单 */
|
||||||
|
defineOptions({ name: 'AfterSalesVisitForm' })
|
||||||
|
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
const dialogVisible = ref(false) // 弹窗的是否展示
|
||||||
|
const dialogTitle = ref('') // 弹窗的标题
|
||||||
|
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||||
|
const formType = ref('') // 表单的类型:create - 新增;update - 修改
|
||||||
|
const formData = ref({
|
||||||
|
id: undefined,
|
||||||
|
customerName: undefined,
|
||||||
|
contactInfo: undefined,
|
||||||
|
customerUsage: undefined,
|
||||||
|
productName: undefined,
|
||||||
|
productEvaluation: undefined,
|
||||||
|
customerType: undefined,
|
||||||
|
repurchaseIntention: undefined,
|
||||||
|
serviceRating: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
// 客户类型选项
|
||||||
|
const customerTypeOptions = [
|
||||||
|
{ value: 'large_food_factory', label: '大型食品厂' },
|
||||||
|
{ value: 'small_food_factory', label: '中小型加工厂' },
|
||||||
|
{ value: 'distributor', label: '经销商' },
|
||||||
|
{ value: 'catering_chain', label: '餐饮连锁' },
|
||||||
|
{ value: 'government_unit', label: '机关单位' },
|
||||||
|
{ value: 'other', label: '其他' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 重复采购意愿选项
|
||||||
|
const repurchaseOptions = [
|
||||||
|
{ value: 'definitely', label: '一定会' },
|
||||||
|
{ value: 'probably', label: '应该会' },
|
||||||
|
{ value: 'uncertain', label: '不确定' },
|
||||||
|
{ value: 'unlikely', label: '可能不会' },
|
||||||
|
{ value: 'never', label: '肯定不会' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const formRules = reactive({
|
||||||
|
customerName: [
|
||||||
|
{ required: true, message: '客户名称不能为空', trigger: 'blur' },
|
||||||
|
{ min: 2, max: 50, message: '客户名称长度应在2-50字符之间', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
contactInfo: [
|
||||||
|
{ required: true, message: '联系方式不能为空', trigger: 'blur' },
|
||||||
|
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号码', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
productName: [
|
||||||
|
{ required: true, message: '产品名称不能为空', trigger: 'blur' },
|
||||||
|
{ min: 1, max: 100, message: '产品名称长度应在1-100字符之间', trigger: 'blur' }
|
||||||
|
],
|
||||||
|
customerType: [{ required: true, message: '请选择客户类型', trigger: 'change' }],
|
||||||
|
repurchaseIntention: [{ required: true, message: '请选择采购意愿', trigger: 'change' }],
|
||||||
|
serviceRating: [
|
||||||
|
{ required: true, message: '请给出服务评分', trigger: 'change' },
|
||||||
|
{ type: 'number', min: 0, max: 5, message: '评分应在0-5之间', trigger: 'change' }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
const formRef = ref() // 表单 Ref
|
||||||
|
const customerList = ref<CustomerVO[]>([]) // 客户列表
|
||||||
|
|
||||||
|
/** 加载客户列表 */
|
||||||
|
const loadCustomerList = async () => {
|
||||||
|
try {
|
||||||
|
const data = await CustomerApi.getCustomerSimpleList()
|
||||||
|
customerList.value = data
|
||||||
|
} catch (error) {
|
||||||
|
console.error('加载客户列表失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 处理客户选择 */
|
||||||
|
const handleCustomerChange = (customerName: string) => {
|
||||||
|
if (!customerName) {
|
||||||
|
formData.value.contactInfo = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const selectedCustomer = customerList.value.find(customer => customer.name === customerName)
|
||||||
|
if (selectedCustomer) {
|
||||||
|
// 自动填充联系方式,优先使用手机号,其次电话
|
||||||
|
formData.value.contactInfo = selectedCustomer.mobile || selectedCustomer.telephone || ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 打开弹窗 */
|
||||||
|
const open = async (type: string, id?: number) => {
|
||||||
|
dialogVisible.value = true
|
||||||
|
dialogTitle.value = t('action.' + type)
|
||||||
|
formType.value = type
|
||||||
|
resetForm()
|
||||||
|
// 修改时,设置数据
|
||||||
|
if (id) {
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
formData.value = await AfterSalesVisitApi.getAfterSalesVisit(id)
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
||||||
|
|
||||||
|
/** 提交表单 */
|
||||||
|
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
||||||
|
const submitForm = async () => {
|
||||||
|
// 校验表单
|
||||||
|
await formRef.value.validate()
|
||||||
|
// 提交请求
|
||||||
|
formLoading.value = true
|
||||||
|
try {
|
||||||
|
const data = formData.value as unknown as AfterSalesVisit
|
||||||
|
if (formType.value === 'create') {
|
||||||
|
await AfterSalesVisitApi.createAfterSalesVisit(data)
|
||||||
|
message.success(t('common.createSuccess'))
|
||||||
|
} else {
|
||||||
|
await AfterSalesVisitApi.updateAfterSalesVisit(data)
|
||||||
|
message.success(t('common.updateSuccess'))
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
|
// 发送操作成功的事件
|
||||||
|
emit('success')
|
||||||
|
} finally {
|
||||||
|
formLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置表单 */
|
||||||
|
const resetForm = () => {
|
||||||
|
formData.value = {
|
||||||
|
id: undefined,
|
||||||
|
customerName: undefined,
|
||||||
|
contactInfo: undefined,
|
||||||
|
customerUsage: undefined,
|
||||||
|
productName: undefined,
|
||||||
|
productEvaluation: undefined,
|
||||||
|
customerType: undefined,
|
||||||
|
repurchaseIntention: undefined,
|
||||||
|
serviceRating: undefined
|
||||||
|
}
|
||||||
|
formRef.value?.resetFields()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 */
|
||||||
|
onMounted(() => {
|
||||||
|
loadCustomerList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
306
src/views/erp/aftersale/aftersalesvisit/index.vue
Normal file
306
src/views/erp/aftersale/aftersalesvisit/index.vue
Normal file
@@ -0,0 +1,306 @@
|
|||||||
|
<template>
|
||||||
|
<!-- 统计卡片 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4 mb-4">
|
||||||
|
<SummaryCard title="总回访数" :value="total" icon="ep:user" icon-color="text-blue-600" icon-bg-color="bg-blue-100" />
|
||||||
|
<SummaryCard title="平均评分" :value="avgRating" :decimals="1" icon="ep:star" icon-color="text-yellow-600"
|
||||||
|
icon-bg-color="bg-yellow-100" />
|
||||||
|
<SummaryCard title="今日新增" :value="todayCount" icon="ep:calendar" icon-color="text-green-600"
|
||||||
|
icon-bg-color="bg-green-100" />
|
||||||
|
<SummaryCard title="五星评分" :value="fiveStarCount" icon="ep:star" icon-color="text-yellow-600"
|
||||||
|
icon-bg-color="bg-yellow-100" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="80px">
|
||||||
|
<el-form-item label="客户名称" prop="customerName">
|
||||||
|
<el-input v-model="queryParams.customerName" placeholder="请输入客户名称" clearable @keyup.enter="handleQuery"
|
||||||
|
class="!w-200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="联系方式" prop="contactInfo">
|
||||||
|
<el-input v-model="queryParams.contactInfo" placeholder="请输入联系方式" clearable @keyup.enter="handleQuery"
|
||||||
|
class="!w-200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="产品名称" prop="productName">
|
||||||
|
<el-input v-model="queryParams.productName" placeholder="请输入产品名称" clearable @keyup.enter="handleQuery"
|
||||||
|
class="!w-200px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="客户类型" prop="customerType">
|
||||||
|
<el-select v-model="queryParams.customerType" placeholder="请选择客户类型" clearable class="!w-200px">
|
||||||
|
<el-option v-for="item in customerTypeOptions" :key="item.value" :label="item.label" :value="item.value" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="采购意愿" prop="repurchaseIntention">
|
||||||
|
<el-select v-model="queryParams.repurchaseIntention" placeholder="请选择采购意愿" clearable class="!w-200px">
|
||||||
|
<el-option v-for="item in repurchaseOptions" :key="item.value" :label="item.label" :value="item.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 type="primary" @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-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- 操作按钮 -->
|
||||||
|
<div class="flex flex-wrap gap-2 mb-4">
|
||||||
|
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:after-sales-visit:create']">
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button type="success" plain @click="handleExport" :loading="exportLoading"
|
||||||
|
v-hasPermi="['erp:after-sales-visit:export']">
|
||||||
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
|
</el-button>
|
||||||
|
<el-button type="danger" plain :disabled="isEmpty(checkedIds)" @click="handleDeleteBatch"
|
||||||
|
v-hasPermi="['erp:after-sales-visit:delete']">
|
||||||
|
<Icon icon="ep:delete" class="mr-5px" /> 批量删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table row-key="id" v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true"
|
||||||
|
@selection-change="handleRowCheckboxChange" class="mb-4">
|
||||||
|
<el-table-column type="selection" width="50" align="center" />
|
||||||
|
<el-table-column label="客户名称" min-width="120" prop="customerName" show-overflow-tooltip />
|
||||||
|
<el-table-column label="联系方式" min-width="120" prop="contactInfo" show-overflow-tooltip />
|
||||||
|
<el-table-column label="产品名称" min-width="120" prop="productName" show-overflow-tooltip />
|
||||||
|
<el-table-column label="客户类型" min-width="140" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="getCustomerTypeColor(scope.row.customerType)">
|
||||||
|
{{ getCustomerTypeLabel(scope.row.customerType) }}
|
||||||
|
</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="采购意愿" min-width="100" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<span :class="getRepurchaseColor(scope.row.repurchaseIntention)">
|
||||||
|
{{ getRepurchaseLabel(scope.row.repurchaseIntention) }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="服务评分" width="120" align="center">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-rate :model-value="scope.row.serviceRating" disabled :max="5" show-score text-color="#ff9900"
|
||||||
|
score-template="{value}星" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="创建时间" width="160" align="center" prop="createTime" :formatter="dateFormatter" />
|
||||||
|
<el-table-column label="操作" width="140" align="center" fixed="right">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" size="small" @click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sales-visit:update']">
|
||||||
|
<Icon icon="ep:edit" class="mr-1" />编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button link type="danger" size="small" @click="handleDelete(scope.row.id)"
|
||||||
|
v-hasPermi="['erp:after-sales-visit:delete']">
|
||||||
|
<Icon icon="ep:delete" class="mr-1" />删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<AfterSalesVisitForm ref="formRef" @success="getList" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { isEmpty } from '@/utils/is'
|
||||||
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
|
import download from '@/utils/download'
|
||||||
|
import SummaryCard from '@/components/SummaryCard/index.vue'
|
||||||
|
import { AfterSalesVisitApi, AfterSalesVisit } from '@/api/erp/aftersale/aftersalesvisit'
|
||||||
|
import AfterSalesVisitForm from './AfterSalesVisitForm.vue'
|
||||||
|
|
||||||
|
/** 售后回访 列表 */
|
||||||
|
defineOptions({ name: 'AfterSalesVisit' })
|
||||||
|
|
||||||
|
const message = useMessage() // 消息弹窗
|
||||||
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
|
const loading = ref(true) // 列表的加载中
|
||||||
|
const list = ref<AfterSalesVisit[]>([]) // 列表的数据
|
||||||
|
const total = ref(0) // 列表的总页数
|
||||||
|
const activeNames = ref(['search']) // 折叠面板默认展开
|
||||||
|
|
||||||
|
// 客户类型选项
|
||||||
|
const customerTypeOptions = [
|
||||||
|
{ value: 'large_food_factory', label: '大型食品厂' },
|
||||||
|
{ value: 'small_food_factory', label: '中小型加工厂' },
|
||||||
|
{ value: 'distributor', label: '经销商' },
|
||||||
|
{ value: 'catering_chain', label: '餐饮连锁' },
|
||||||
|
{ value: 'government_unit', label: '机关单位' },
|
||||||
|
{ value: 'other', label: '其他' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 重复采购意愿选项
|
||||||
|
const repurchaseOptions = [
|
||||||
|
{ value: 'definitely', label: '一定会' },
|
||||||
|
{ value: 'probably', label: '应该会' },
|
||||||
|
{ value: 'uncertain', label: '不确定' },
|
||||||
|
{ value: 'unlikely', label: '可能不会' },
|
||||||
|
{ value: 'never', label: '肯定不会' }
|
||||||
|
]
|
||||||
|
const queryParams = reactive({
|
||||||
|
pageNo: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
customerName: undefined,
|
||||||
|
contactInfo: undefined,
|
||||||
|
customerUsage: undefined,
|
||||||
|
productName: undefined,
|
||||||
|
productEvaluation: undefined,
|
||||||
|
customerType: undefined,
|
||||||
|
repurchaseIntention: undefined,
|
||||||
|
serviceRating: undefined,
|
||||||
|
createTime: []
|
||||||
|
})
|
||||||
|
const queryFormRef = ref() // 搜索的表单
|
||||||
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
|
||||||
|
/** 查询列表 */
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const data = await AfterSalesVisitApi.getAfterSalesVisitPage(queryParams)
|
||||||
|
list.value = data.list
|
||||||
|
total.value = data.total
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 搜索按钮操作 */
|
||||||
|
const handleQuery = () => {
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 重置按钮操作 */
|
||||||
|
const resetQuery = () => {
|
||||||
|
queryFormRef.value.resetFields()
|
||||||
|
// 重置评分查询条件
|
||||||
|
queryParams.serviceRating = undefined
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 添加/修改操作 */
|
||||||
|
const formRef = ref()
|
||||||
|
const openForm = (type: string, id?: number) => {
|
||||||
|
formRef.value.open(type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 删除按钮操作 */
|
||||||
|
const handleDelete = async (id: number) => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
// 发起删除
|
||||||
|
await AfterSalesVisitApi.deleteAfterSalesVisit(id)
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
// 刷新列表
|
||||||
|
await getList()
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 批量删除售后回访 */
|
||||||
|
const handleDeleteBatch = async () => {
|
||||||
|
try {
|
||||||
|
// 删除的二次确认
|
||||||
|
await message.delConfirm()
|
||||||
|
await AfterSalesVisitApi.deleteAfterSalesVisitList(checkedIds.value);
|
||||||
|
message.success(t('common.delSuccess'))
|
||||||
|
await getList();
|
||||||
|
} catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
const checkedIds = ref<number[]>([])
|
||||||
|
const handleRowCheckboxChange = (records: AfterSalesVisit[]) => {
|
||||||
|
checkedIds.value = records.map((item) => item.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
// 导出的二次确认
|
||||||
|
await message.exportConfirm()
|
||||||
|
// 发起导出
|
||||||
|
exportLoading.value = true
|
||||||
|
const data = await AfterSalesVisitApi.exportAfterSalesVisit(queryParams)
|
||||||
|
download.excel(data, '售后回访.xls')
|
||||||
|
} catch {
|
||||||
|
} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 统计数据计算 */
|
||||||
|
const avgRating = computed(() => {
|
||||||
|
if (list.value.length === 0) return 0
|
||||||
|
const sum = list.value.reduce((acc, item) => acc + (item.serviceRating || 0), 0)
|
||||||
|
return sum / list.value.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const todayCount = computed(() => {
|
||||||
|
const today = new Date().toISOString().split('T')[0]
|
||||||
|
return list.value.filter(item => {
|
||||||
|
const createDate = new Date(item.createTime).toISOString().split('T')[0]
|
||||||
|
return createDate === today
|
||||||
|
}).length
|
||||||
|
})
|
||||||
|
|
||||||
|
const fiveStarCount = computed(() => {
|
||||||
|
return list.value.filter(item => (item.serviceRating || 0) === 5).length
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 格式化函数 */
|
||||||
|
const getCustomerTypeLabel = (value: string) => {
|
||||||
|
const option = customerTypeOptions.find(item => item.value === value)
|
||||||
|
return option ? option.label : value
|
||||||
|
}
|
||||||
|
|
||||||
|
const getCustomerTypeColor = (value: string) => {
|
||||||
|
const colorMap = {
|
||||||
|
'large_food_factory': 'success',
|
||||||
|
'small_food_factory': 'info',
|
||||||
|
'distributor': 'warning',
|
||||||
|
'catering_chain': 'danger',
|
||||||
|
'government_unit': 'primary',
|
||||||
|
'other': ''
|
||||||
|
}
|
||||||
|
return colorMap[value as keyof typeof colorMap] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRepurchaseLabel = (value: string) => {
|
||||||
|
const option = repurchaseOptions.find(item => item.value === value)
|
||||||
|
return option ? option.label : value
|
||||||
|
}
|
||||||
|
|
||||||
|
const getRepurchaseColor = (value: string) => {
|
||||||
|
const colorMap = {
|
||||||
|
'definitely': 'text-green-600 font-medium',
|
||||||
|
'probably': 'text-blue-600',
|
||||||
|
'uncertain': 'text-yellow-600',
|
||||||
|
'unlikely': 'text-orange-600',
|
||||||
|
'never': 'text-red-600'
|
||||||
|
}
|
||||||
|
return colorMap[value as keyof typeof colorMap] || ''
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 初始化 **/
|
||||||
|
onMounted(() => {
|
||||||
|
getList()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,134 +1,78 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mobile-approved">
|
<!-- 移动端布局 -->
|
||||||
<!-- 顶部操作栏 -->
|
<div v-if="isMobile" class="mobile-approved">
|
||||||
<div class="mobile-header">
|
<div class="mobile-header">
|
||||||
<div class="mobile-header__search">
|
<div class="mobile-header__search">
|
||||||
<el-select
|
<el-select v-model="queryParams.bizTableName" placeholder="业务类型" clearable style="width: 100%" @change="handleQuery"><el-option v-for="item in bizTableOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select>
|
||||||
v-model="queryParams.bizTableName"
|
|
||||||
placeholder="业务类型"
|
|
||||||
clearable
|
|
||||||
style="width: 100%"
|
|
||||||
@change="handleQuery"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in bizTableOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-header__actions">
|
<div class="mobile-header__actions"><el-button :icon="Filter" circle @click="filterVisible = true" /></div>
|
||||||
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 卡片列表 -->
|
|
||||||
<div class="mobile-list" v-loading="loading">
|
<div class="mobile-list" v-loading="loading">
|
||||||
<div v-if="list.length === 0 && !loading" class="mobile-empty">
|
<div v-if="list.length === 0 && !loading" class="mobile-empty"><el-empty description="暂无已审批记录" /></div>
|
||||||
<el-empty description="暂无已审批记录" />
|
<div v-for="item in list" :key="item.id" class="mobile-card" @click="handleViewRecords(item)">
|
||||||
</div>
|
<div class="mobile-card__header"><span class="mobile-card__no">{{ item.bizId }}</span><el-tag :type="getResultTagType(item.approvalResult)" size="small">{{ getApprovalResultText(item.approvalResult) }}</el-tag></div>
|
||||||
<div
|
|
||||||
v-for="item in list"
|
|
||||||
:key="item.id"
|
|
||||||
class="mobile-card"
|
|
||||||
@click="handleViewRecords(item)"
|
|
||||||
>
|
|
||||||
<div class="mobile-card__header">
|
|
||||||
<span class="mobile-card__no">{{ item.bizId }}</span>
|
|
||||||
<el-tag :type="getResultTagType(item.approvalResult)" size="small">
|
|
||||||
{{ getApprovalResultText(item.approvalResult) }}
|
|
||||||
</el-tag>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__body">
|
<div class="mobile-card__body">
|
||||||
<div class="mobile-card__row">
|
<div class="mobile-card__row"><span class="mobile-card__label">业务类型</span><span class="mobile-card__value">{{ getBizTableLabel(item.bizTableName) }}</span></div>
|
||||||
<span class="mobile-card__label">业务类型</span>
|
<div class="mobile-card__row"><span class="mobile-card__label">申请人</span><span class="mobile-card__value">{{ item.applicantName || '-' }}</span></div>
|
||||||
<span class="mobile-card__value">{{ getBizTableLabel(item.bizTableName) }}</span>
|
<div class="mobile-card__row"><span class="mobile-card__label">审批层级</span><span class="mobile-card__value">第{{ item.approvalLevel }}级</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">审批意见</span><span class="mobile-card__value mobile-card__value--ellipsis">{{ item.comment || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">审批时间</span><span class="mobile-card__value">{{ formatDate2(item.approvalTime) }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card__row">
|
<div class="mobile-card__footer"><el-button size="small" type="info" @click.stop="handleViewRecords(item)">审批记录</el-button></div>
|
||||||
<span class="mobile-card__label">申请人</span>
|
|
||||||
<span class="mobile-card__value">{{ item.applicantName || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__row">
|
|
||||||
<span class="mobile-card__label">审批层级</span>
|
|
||||||
<span class="mobile-card__value">第{{ item.approvalLevel }}级</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__row">
|
|
||||||
<span class="mobile-card__label">审批意见</span>
|
|
||||||
<span class="mobile-card__value mobile-card__value--ellipsis">{{ item.comment || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__row">
|
|
||||||
<span class="mobile-card__label">审批时间</span>
|
|
||||||
<span class="mobile-card__value">{{ formatDate2(item.approvalTime) }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card__footer">
|
<div class="mobile-pagination" v-if="total > 0"><Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" /></div>
|
||||||
<el-button size="small" type="info" @click.stop="handleViewRecords(item)">审批记录</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 分页 -->
|
|
||||||
<div class="mobile-pagination" v-if="total > 0">
|
|
||||||
<Pagination
|
|
||||||
:total="total"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
:page-sizes="[10, 20]"
|
|
||||||
layout="total, prev, pager, next"
|
|
||||||
:pager-count="5"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 筛选抽屉 -->
|
|
||||||
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="50%">
|
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="50%">
|
||||||
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
<el-form-item label="审批结果" prop="approvalResult">
|
<el-form-item label="审批结果" prop="approvalResult"><el-select v-model="queryParams.approvalResult" placeholder="请选择审批结果" clearable style="width: 100%"><el-option label="已通过" :value="1" /><el-option label="已驳回" :value="2" /><el-option label="已转审" :value="3" /></el-select></el-form-item>
|
||||||
<el-select
|
<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')]" style="width: 100%" /></el-form-item>
|
||||||
v-model="queryParams.approvalResult"
|
|
||||||
placeholder="请选择审批结果"
|
|
||||||
clearable
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option label="已通过" :value="1" />
|
|
||||||
<el-option label="已驳回" :value="2" />
|
|
||||||
<el-option label="已转审" :value="3" />
|
|
||||||
</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')]"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer><el-button @click="resetQuery">重置</el-button><el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button></template>
|
||||||
<el-button @click="resetQuery">重置</el-button>
|
|
||||||
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
|
|
||||||
</template>
|
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
<!-- 审批记录对话框 -->
|
|
||||||
<ApprovalRecordsDialog ref="approvalDialogRef" />
|
<ApprovalRecordsDialog ref="approvalDialogRef" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
|
<el-form-item label="业务类型" prop="bizTableName"><el-select v-model="queryParams.bizTableName" placeholder="请选择业务类型" clearable class="!w-240px"><el-option v-for="item in bizTableOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="审批结果" prop="approvalResult"><el-select v-model="queryParams.approvalResult" placeholder="请选择审批结果" clearable class="!w-240px"><el-option label="已通过" :value="1" /><el-option label="已驳回" :value="2" /><el-option label="已转审" :value="3" /></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-240px" /></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-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
<el-table-column label="业务单号" align="center" prop="bizId" min-width="120" />
|
||||||
|
<el-table-column label="业务类型" align="center" prop="bizTableName" min-width="150"><template #default="scope">{{ getBizTableLabel(scope.row.bizTableName) }}</template></el-table-column>
|
||||||
|
<el-table-column label="申请人" align="center" prop="applicantName" min-width="100" />
|
||||||
|
<el-table-column label="审批层级" align="center" prop="approvalLevel" min-width="100"><template #default="scope">第{{ scope.row.approvalLevel }}级</template></el-table-column>
|
||||||
|
<el-table-column label="审批结果" align="center" prop="approvalResult" min-width="100"><template #default="scope"><el-tag :type="getResultTagType(scope.row.approvalResult)">{{ getApprovalResultText(scope.row.approvalResult) }}</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="审批意见" align="center" prop="comment" min-width="150" />
|
||||||
|
<el-table-column label="审批时间" align="center" prop="approvalTime" :formatter="dateFormatter" width="180px" sortable />
|
||||||
|
<el-table-column label="操作" align="center" fixed="right" width="120"><template #default="scope"><el-button link type="info" @click="handleViewRecords(scope.row)">审批记录</el-button></template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
<ApprovalRecordsDialog ref="approvalDialogRef" />
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { Filter } from '@element-plus/icons-vue'
|
import { Filter } from '@element-plus/icons-vue'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate, dateFormatter } from '@/utils/formatTime'
|
||||||
import { ApprovalRecordApi, ApprovalRecordVO } from '@/api/erp/approval'
|
import { ApprovalRecordApi, ApprovalRecordVO } from '@/api/erp/approval'
|
||||||
import { ApprovalRecordsDialog } from '@/components/Approval'
|
import { ApprovalRecordsDialog } from '@/components/Approval'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'ErpApprovalApproved' })
|
defineOptions({ name: 'ErpApprovalApproved' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const list = ref<ApprovalRecordVO[]>([])
|
const list = ref<ApprovalRecordVO[]>([])
|
||||||
|
|||||||
@@ -1,68 +1,23 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mobile-pending">
|
<!-- 移动端布局 -->
|
||||||
<!-- 顶部操作栏 -->
|
<div v-if="isMobile" class="mobile-pending">
|
||||||
<div class="mobile-header">
|
<div class="mobile-header">
|
||||||
<div class="mobile-header__search">
|
<div class="mobile-header__search">
|
||||||
<el-select
|
<el-select v-model="queryParams.bizTableName" placeholder="业务类型" clearable style="width: 100%" @change="handleQuery"><el-option v-for="item in bizTableOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select>
|
||||||
v-model="queryParams.bizTableName"
|
|
||||||
placeholder="业务类型"
|
|
||||||
clearable
|
|
||||||
style="width: 100%"
|
|
||||||
@change="handleQuery"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in bizTableOptions"
|
|
||||||
:key="item.value"
|
|
||||||
:label="item.label"
|
|
||||||
:value="item.value"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-header__actions">
|
<div class="mobile-header__actions"><el-button :icon="Refresh" circle @click="resetQuery" /></div>
|
||||||
<el-button :icon="Refresh" circle @click="resetQuery" />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 卡片列表 -->
|
|
||||||
<div class="mobile-list" v-loading="loading">
|
<div class="mobile-list" v-loading="loading">
|
||||||
<div v-if="filteredList.length === 0 && !loading" class="mobile-empty">
|
<div v-if="filteredList.length === 0 && !loading" class="mobile-empty"><el-empty description="暂无待审批记录" /></div>
|
||||||
<el-empty description="暂无待审批记录" />
|
<div v-for="item in filteredList" :key="item.id" class="mobile-card" @click="handleProcess(item)">
|
||||||
</div>
|
<div class="mobile-card__header"><span class="mobile-card__no">{{ item.bizId }}</span><el-tag type="warning" size="small">待审批</el-tag></div>
|
||||||
<div
|
|
||||||
v-for="item in filteredList"
|
|
||||||
:key="item.id"
|
|
||||||
class="mobile-card"
|
|
||||||
@click="handleProcess(item)"
|
|
||||||
>
|
|
||||||
<div class="mobile-card__header">
|
|
||||||
<span class="mobile-card__no">{{ item.bizId }}</span>
|
|
||||||
<el-tag type="warning" size="small">待审批</el-tag>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__body">
|
<div class="mobile-card__body">
|
||||||
<div class="mobile-card__row">
|
<div class="mobile-card__row"><span class="mobile-card__label">业务类型</span><span class="mobile-card__value">{{ getBizTableLabel(item.bizTableName) }}</span></div>
|
||||||
<span class="mobile-card__label">业务类型</span>
|
<div class="mobile-card__row"><span class="mobile-card__label">申请人</span><span class="mobile-card__value">{{ item.applicantName || '-' }}</span></div>
|
||||||
<span class="mobile-card__value">{{ getBizTableLabel(item.bizTableName) }}</span>
|
<div class="mobile-card__row"><span class="mobile-card__label">审批层级</span><span class="mobile-card__value">第{{ item.approvalLevel }}级</span></div>
|
||||||
</div>
|
<div class="mobile-card__row" v-if="item.assignerName"><span class="mobile-card__label">指定者</span><span class="mobile-card__value">{{ item.assignerName }}</span></div>
|
||||||
<div class="mobile-card__row">
|
<div class="mobile-card__row" v-if="item.assignReason"><span class="mobile-card__label">指定原因</span><span class="mobile-card__value mobile-card__value--ellipsis">{{ item.assignReason }}</span></div>
|
||||||
<span class="mobile-card__label">申请人</span>
|
<div class="mobile-card__row"><span class="mobile-card__label">提交时间</span><span class="mobile-card__value">{{ formatDate2(item.createTime) }}</span></div>
|
||||||
<span class="mobile-card__value">{{ item.applicantName || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__row">
|
|
||||||
<span class="mobile-card__label">审批层级</span>
|
|
||||||
<span class="mobile-card__value">第{{ item.approvalLevel }}级</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__row" v-if="item.assignerName">
|
|
||||||
<span class="mobile-card__label">指定者</span>
|
|
||||||
<span class="mobile-card__value">{{ item.assignerName }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__row" v-if="item.assignReason">
|
|
||||||
<span class="mobile-card__label">指定原因</span>
|
|
||||||
<span class="mobile-card__value mobile-card__value--ellipsis">{{ item.assignReason }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__row">
|
|
||||||
<span class="mobile-card__label">提交时间</span>
|
|
||||||
<span class="mobile-card__value">{{ formatDate2(item.createTime) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card__footer">
|
<div class="mobile-card__footer">
|
||||||
<el-button size="small" type="primary" @click.stop="handleProcess(item)">处理审批</el-button>
|
<el-button size="small" type="primary" @click.stop="handleProcess(item)">处理审批</el-button>
|
||||||
@@ -70,25 +25,49 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 处理审批对话框 -->
|
|
||||||
<ProcessApprovalDialog ref="processApprovalDialogRef" @success="getList" />
|
<ProcessApprovalDialog ref="processApprovalDialogRef" @success="getList" />
|
||||||
|
|
||||||
<!-- 审批记录对话框 -->
|
|
||||||
<ApprovalRecordsDialog ref="approvalDialogRef" />
|
<ApprovalRecordsDialog ref="approvalDialogRef" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
|
<el-form-item label="业务类型" prop="bizTableName"><el-select v-model="queryParams.bizTableName" placeholder="请选择业务类型" clearable class="!w-240px" @change="handleQuery"><el-option v-for="item in bizTableOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></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-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="filteredList" :stripe="true" :show-overflow-tooltip="true">
|
||||||
|
<el-table-column label="业务单号" align="center" prop="bizId" min-width="120" />
|
||||||
|
<el-table-column label="业务类型" align="center" prop="bizTableName" min-width="150"><template #default="scope">{{ getBizTableLabel(scope.row.bizTableName) }}</template></el-table-column>
|
||||||
|
<el-table-column label="申请人" align="center" prop="applicantName" min-width="100" />
|
||||||
|
<el-table-column label="审批层级" align="center" prop="approvalLevel" min-width="100"><template #default="scope">第{{ scope.row.approvalLevel }}级</template></el-table-column>
|
||||||
|
<el-table-column label="指定者" align="center" prop="assignerName" min-width="100" />
|
||||||
|
<el-table-column label="指定原因" align="center" prop="assignReason" min-width="150" />
|
||||||
|
<el-table-column label="提交时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" sortable />
|
||||||
|
<el-table-column label="操作" align="center" fixed="right" width="200"><template #default="scope"><el-button link type="primary" @click="handleProcess(scope.row)">处理审批</el-button><el-button link type="info" @click="handleViewRecords(scope.row)">审批记录</el-button></template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</ContentWrap>
|
||||||
|
<ProcessApprovalDialog ref="processApprovalDialogRef" @success="getList" />
|
||||||
|
<ApprovalRecordsDialog ref="approvalDialogRef" />
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, onMounted } from 'vue'
|
import { ref, computed, onMounted } from 'vue'
|
||||||
import { Refresh } from '@element-plus/icons-vue'
|
import { Refresh } from '@element-plus/icons-vue'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate, dateFormatter } from '@/utils/formatTime'
|
||||||
import { ApprovalRecordApi, ApprovalRecordVO } from '@/api/erp/approval'
|
import { ApprovalRecordApi, ApprovalRecordVO } from '@/api/erp/approval'
|
||||||
import { ProcessApprovalDialog, ApprovalRecordsDialog } from '@/components/Approval'
|
import { ProcessApprovalDialog, ApprovalRecordsDialog } from '@/components/Approval'
|
||||||
import { useUserStore } from '@/store/modules/user'
|
import { useUserStore } from '@/store/modules/user'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'ErpApprovalPending' })
|
defineOptions({ name: 'ErpApprovalPending' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
const list = ref<ApprovalRecordVO[]>([])
|
const list = ref<ApprovalRecordVO[]>([])
|
||||||
|
|||||||
@@ -1,35 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<!-- 移动端布局 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
:rules="formRules"
|
<div class="mobile-form-section">
|
||||||
label-width="100px"
|
<el-form-item label="名称" prop="name"><el-input v-model="formData.name" placeholder="请输入名称" /></el-form-item>
|
||||||
v-loading="formLoading"
|
<el-form-item label="编码" prop="no"><el-input v-model="formData.no" placeholder="请输入编码" /></el-form-item>
|
||||||
>
|
<el-form-item label="备注" prop="remark"><el-input v-model="formData.remark" placeholder="请输入备注" /></el-form-item>
|
||||||
<el-form-item label="名称" prop="name">
|
<el-form-item label="状态" prop="status"><el-radio-group v-model="formData.status"><el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio></el-radio-group></el-form-item>
|
||||||
<el-input v-model="formData.name" placeholder="请输入名称" />
|
<el-form-item label="排序" prop="sort"><el-input v-model="formData.sort" placeholder="请输入排序" /></el-form-item>
|
||||||
</el-form-item>
|
</div>
|
||||||
<el-form-item label="编码" prop="no">
|
</el-form>
|
||||||
<el-input v-model="formData.no" placeholder="请输入编码" />
|
<div class="mobile-form__footer">
|
||||||
</el-form-item>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
<el-input v-model="formData.remark" placeholder="请输入备注" />
|
</div>
|
||||||
</el-form-item>
|
</div>
|
||||||
<el-form-item label="状态" prop="status">
|
</el-drawer>
|
||||||
<el-radio-group v-model="formData.status">
|
|
||||||
<el-radio
|
<!-- PC端布局 -->
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible">
|
||||||
:key="dict.value"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
|
||||||
:value="dict.value"
|
<el-form-item label="名称" prop="name"><el-input v-model="formData.name" placeholder="请输入名称" /></el-form-item>
|
||||||
>
|
<el-form-item label="编码" prop="no"><el-input v-model="formData.no" placeholder="请输入编码" /></el-form-item>
|
||||||
{{ dict.label }}
|
<el-form-item label="备注" prop="remark"><el-input v-model="formData.remark" placeholder="请输入备注" /></el-form-item>
|
||||||
</el-radio>
|
<el-form-item label="状态" prop="status"><el-radio-group v-model="formData.status"><el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio></el-radio-group></el-form-item>
|
||||||
</el-radio-group>
|
<el-form-item label="排序" prop="sort"><el-input v-model="formData.sort" placeholder="请输入排序" /></el-form-item>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="排序" prop="sort">
|
|
||||||
<el-input v-model="formData.sort" placeholder="请输入排序" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
@@ -38,12 +34,17 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 结算 表单 */
|
/** ERP 结算 表单 */
|
||||||
defineOptions({ name: 'AccountForm' })
|
defineOptions({ name: 'AccountForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -122,3 +123,9 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form { padding: 12px; }
|
||||||
|
.mobile-form-section { background: #fff; border-radius: 10px; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||||
|
.mobile-form__footer { position: sticky; bottom: 0; background: #fff; padding: 12px 16px; padding-bottom: calc(12px + env(safe-area-inset-bottom)); border-top: 1px solid #eee; display: flex; justify-content: flex-end; gap: 12px; margin: 12px -12px -12px; .el-button { flex: 1; height: 40px; font-size: 15px; } }
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,70 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert
|
<!-- 移动端布局 -->
|
||||||
title="【财务】采购付款、销售收款"
|
<div v-if="isMobile" class="mobile-account">
|
||||||
url="https://doc.iocoder.cn/sale/finance-payment-receipt/"
|
<div class="mobile-header">
|
||||||
/>
|
<div class="mobile-header__search"><el-input v-model="queryParams.name" placeholder="搜索账户名称" clearable @keyup.enter="handleQuery" :prefix-icon="Search" /></div>
|
||||||
|
<div class="mobile-header__actions">
|
||||||
|
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
||||||
|
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:account:create']" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-quick-actions"><el-button size="small" type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:account:export']">导出</el-button></div>
|
||||||
|
<div class="mobile-list" v-loading="loading">
|
||||||
|
<div v-if="list.length === 0 && !loading" class="mobile-empty"><el-empty description="暂无账户" /></div>
|
||||||
|
<div v-for="item in list" :key="item.id" class="mobile-card" @click="openForm('update', item.id)">
|
||||||
|
<div class="mobile-card__header">
|
||||||
|
<span class="mobile-card__name">{{ item.name }}</span>
|
||||||
|
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="item.status" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__body">
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">编码</span><span class="mobile-card__value">{{ item.no || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">备注</span><span class="mobile-card__value">{{ item.remark || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">排序</span><span class="mobile-card__value">{{ item.sort }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">默认</span><el-switch v-model="item.defaultStatus" :active-value="true" :inactive-value="false" @click.stop @change="handleDefaultStatusChange(item)" /></div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__footer">
|
||||||
|
<el-button size="small" type="primary" @click.stop="openForm('update', item.id)" v-hasPermi="['erp:account:update']">编辑</el-button>
|
||||||
|
<el-button size="small" type="danger" @click.stop="handleDelete(item.id)" v-hasPermi="['erp:account:delete']">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-pagination" v-if="total > 0"><Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" /></div>
|
||||||
|
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="50%">
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
|
<el-form-item label="名称" prop="name"><el-input v-model="queryParams.name" placeholder="请输入名称" clearable style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="编码" prop="no"><el-input v-model="queryParams.no" placeholder="请输入编码" clearable style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input v-model="queryParams.remark" placeholder="请输入备注" clearable style="width:100%" /></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
<el-form
|
<el-form-item label="名称" prop="name"><el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter="handleQuery" class="!w-240px" /></el-form-item>
|
||||||
class="-mb-15px"
|
<el-form-item label="编码" prop="no"><el-input v-model="queryParams.no" placeholder="请输入编码" clearable @keyup.enter="handleQuery" class="!w-240px" /></el-form-item>
|
||||||
:model="queryParams"
|
<el-form-item label="备注" prop="remark"><el-input v-model="queryParams.remark" placeholder="请输入备注" clearable @keyup.enter="handleQuery" class="!w-240px" /></el-form-item>
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="名称" prop="name">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.name"
|
|
||||||
placeholder="请输入名称"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="编码" prop="no">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.no"
|
|
||||||
placeholder="请输入编码"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.remark"
|
|
||||||
placeholder="请输入备注"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
<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="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||||
<el-button
|
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:account:create']"><Icon icon="ep:plus" class="mr-5px" /> 新增</el-button>
|
||||||
type="primary"
|
<el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:account:export']"><Icon icon="ep:download" class="mr-5px" /> 导出</el-button>
|
||||||
plain
|
|
||||||
@click="openForm('create')"
|
|
||||||
v-hasPermi="['erp:account:create']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
plain
|
|
||||||
@click="handleExport"
|
|
||||||
:loading="exportLoading"
|
|
||||||
v-hasPermi="['erp:account:export']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @row-click="handleRowClick">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @row-click="handleRowClick">
|
||||||
<el-table-column label="名称" align="center" prop="name" />
|
<el-table-column label="名称" align="center" prop="name" />
|
||||||
@@ -115,29 +107,30 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
<Pagination
|
|
||||||
:total="total"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<AccountForm ref="formRef" @success="getList" />
|
<AccountForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
||||||
import AccountForm from './AccountForm.vue'
|
import AccountForm from './AccountForm.vue'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
import { Search, Plus, Filter } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
/** ERP 结算账户 列表 */
|
/** ERP 结算账户 列表 */
|
||||||
defineOptions({ name: 'ErpAccount' })
|
defineOptions({ name: 'ErpAccount' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
@@ -154,6 +147,7 @@ const queryParams = reactive({
|
|||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const exportLoading = ref(false) // 导出的加载中
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
const filterVisible = ref(false) // 筛选抽屉
|
||||||
|
|
||||||
/** 查询列表 */
|
/** 查询列表 */
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
@@ -175,7 +169,13 @@ const handleQuery = () => {
|
|||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value?.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 筛选确认 */
|
||||||
|
const handleFilterConfirm = () => {
|
||||||
|
filterVisible.value = false
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,3 +254,22 @@ onMounted(() => {
|
|||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-account { padding: 12px; background: #f5f5f5; min-height: 100vh; }
|
||||||
|
.mobile-header { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
|
||||||
|
.mobile-header__search { flex: 1; }
|
||||||
|
.mobile-header__actions { display: flex; gap: 4px; }
|
||||||
|
.mobile-quick-actions { display: flex; gap: 8px; margin-bottom: 12px; }
|
||||||
|
.mobile-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.mobile-empty { padding: 40px 0; }
|
||||||
|
.mobile-card { background: #fff; border-radius: 10px; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||||
|
.mobile-card__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||||
|
.mobile-card__name { font-weight: 600; font-size: 15px; color: #303133; }
|
||||||
|
.mobile-card__body { font-size: 13px; }
|
||||||
|
.mobile-card__row { display: flex; justify-content: space-between; align-items: center; padding: 3px 0; }
|
||||||
|
.mobile-card__label { color: #909399; flex-shrink: 0; margin-right: 12px; }
|
||||||
|
.mobile-card__value { color: #606266; text-align: right; }
|
||||||
|
.mobile-card__footer { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; padding-top: 10px; border-top: 1px solid #f0f0f0; }
|
||||||
|
.mobile-pagination { margin-top: 12px; display: flex; justify-content: center; :deep(.el-pagination) { flex-wrap: wrap; justify-content: center; } }
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="700px">
|
<!-- 移动端布局 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
:rules="formRules"
|
<div class="mobile-form__section">
|
||||||
label-width="120px"
|
<div class="mobile-form__section-title">银行流水信息</div>
|
||||||
v-loading="formLoading"
|
<el-form-item label="交易流水号" prop="transactionNo"><el-input v-model="formData.transactionNo" placeholder="请输入交易流水号" /></el-form-item>
|
||||||
>
|
<el-form-item label="结算账户" prop="accountId"><el-select v-model="formData.accountId" placeholder="请选择结算账户" style="width:100%" @change="handleAccountChange"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="账户号" prop="accountNo"><el-input v-model="formData.accountNo" placeholder="请输入账户号" /></el-form-item>
|
||||||
|
<el-form-item label="交易机构" prop="transactionInstitution"><el-input v-model="formData.transactionInstitution" placeholder="请输入交易机构" /></el-form-item>
|
||||||
|
<el-form-item label="交易金额" prop="transactionAmount"><el-input-number v-model="formData.transactionAmount" :precision="2" :min="0" placeholder="请输入交易金额" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="交易类型" prop="transactionType"><el-radio-group v-model="formData.transactionType"><el-radio :value="1">收入</el-radio><el-radio :value="2">支出</el-radio></el-radio-group></el-form-item>
|
||||||
|
<el-form-item label="交易对手账户" prop="counterpartyAccount"><el-input v-model="formData.counterpartyAccount" placeholder="请输入交易对手账户" /></el-form-item>
|
||||||
|
<el-form-item label="交易对手户名" prop="counterpartyName"><el-input v-model="formData.counterpartyName" placeholder="请输入交易对手户名" /></el-form-item>
|
||||||
|
<el-form-item label="交易对手银行" prop="counterpartyBank"><el-input v-model="formData.counterpartyBank" placeholder="请输入交易对手银行" /></el-form-item>
|
||||||
|
<el-form-item label="操作柜员" prop="operatorTeller"><el-input v-model="formData.operatorTeller" placeholder="请输入操作柜员" /></el-form-item>
|
||||||
|
<el-form-item label="交易时间" prop="transactionTime"><el-date-picker v-model="formData.transactionTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择交易时间" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="摘要" prop="summary"><el-input v-model="formData.summary" placeholder="请输入摘要" /></el-form-item>
|
||||||
|
<el-form-item label="备注信息" prop="remark"><el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false" style="flex:1">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" style="flex:2">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible" width="700px">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" v-loading="formLoading">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<el-form-item label="交易流水号" prop="transactionNo">
|
<el-form-item label="交易流水号" prop="transactionNo">
|
||||||
@@ -113,12 +138,17 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { BankTransactionApi, BankTransactionVO } from '@/api/erp/finance/bank'
|
import { BankTransactionApi, BankTransactionVO } from '@/api/erp/finance/bank'
|
||||||
import { AccountApi } from '@/api/erp/finance/account'
|
import { AccountApi } from '@/api/erp/finance/account'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 银行流水 表单 */
|
/** ERP 银行流水 表单 */
|
||||||
defineOptions({ name: 'BankTransactionForm' })
|
defineOptions({ name: 'BankTransactionForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,52 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="900px">
|
<!-- 移动端布局 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
v-loading="formLoading"
|
<el-form ref="formRef" :model="formData" :rules="formRules" :disabled="isDetail" label-position="top">
|
||||||
:model="formData"
|
<div class="mobile-form__section">
|
||||||
:rules="formRules"
|
<div class="mobile-form__section-title">基本信息</div>
|
||||||
:disabled="isDetail"
|
<el-form-item label="凭证编号" prop="voucherNo"><el-input v-model="formData.voucherNo" disabled placeholder="系统自动生成" /></el-form-item>
|
||||||
label-width="100px"
|
<el-form-item v-if="!isDetail" label="制单日期" prop="voucherDate"><el-date-picker v-model="formData.voucherDate" type="date" placeholder="选择制单日期" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item>
|
||||||
>
|
<el-form-item label="系统名" prop="systemName"><el-input v-model="formData.systemName" placeholder="请输入系统名" /></el-form-item>
|
||||||
|
<el-form-item label="摘要" prop="summary"><el-input v-model="formData.summary" placeholder="请输入摘要" @input="syncSummaryToItems" /></el-form-item>
|
||||||
|
<el-form-item v-if="formType !== 'create'" label="审核状态" prop="status"><dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="formData.status" /></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input v-model="formData.remark" type="textarea" :rows="2" placeholder="请输入备注" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">凭证明细</div>
|
||||||
|
<div v-for="(item, index) in formData.items" :key="index" class="mobile-item-card">
|
||||||
|
<div class="mobile-item-card__header">
|
||||||
|
<span class="mobile-item-card__index">#{{ index + 1 }}</span>
|
||||||
|
<span class="mobile-item-card__name">{{ item.subjectName || '未选择科目' }}</span>
|
||||||
|
<el-button v-if="!isDetail" :disabled="isDetail" @click="removeItem(index)" link type="danger" size="small">删除</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-card__body">
|
||||||
|
<el-form-item label="摘要"><el-input v-model="item.summary" placeholder="请输入摘要" :disabled="isDetail" /></el-form-item>
|
||||||
|
<el-form-item label="科目名称"><el-tree-select v-model="item.subjectId" :data="subjectTree" :props="{ label: 'name', value: 'id', children: 'children' }" placeholder="请选择科目名称" check-strictly filterable :disabled="isDetail" style="width:100%" @change="(val) => handleSubjectChange(item, val)" /></el-form-item>
|
||||||
|
<el-form-item label="借方金额"><el-input-number v-model="item.debitAmount" :precision="2" :min="0" :controls="false" placeholder="借方金额" :disabled="isDetail || (item.creditAmount > 0)" style="width:100%" @change="(val) => handleDebitChange(item, val)" /></el-form-item>
|
||||||
|
<el-form-item label="贷方金额"><el-input-number v-model="item.creditAmount" :precision="2" :min="0" :controls="false" placeholder="贷方金额" :disabled="isDetail || (item.debitAmount > 0)" style="width:100%" @change="(val) => handleCreditChange(item, val)" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-add" v-if="!isDetail"><el-button @click="addItem" round>+ 添加明细</el-button></div>
|
||||||
|
<div class="mobile-item-summary">
|
||||||
|
<div class="mobile-item-summary__row"><span>借方合计</span><span class="text-red-500">{{ formatAmount(debitTotal) }}</span></div>
|
||||||
|
<div class="mobile-item-summary__row"><span>贷方合计</span><span class="text-blue-500">{{ formatAmount(creditTotal) }}</span></div>
|
||||||
|
</div>
|
||||||
|
<el-alert v-if="!isBalanced && formData.items.length > 0" title="借贷不平衡,请检查金额" type="warning" :closable="false" class="mt-10px" />
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false" style="flex:1">取 消</el-button>
|
||||||
|
<el-button v-if="!isDetail" type="primary" :disabled="formLoading" @click="submitForm" style="flex:2">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else v-model="dialogVisible" :title="dialogTitle" width="900px">
|
||||||
|
<el-form ref="formRef" v-loading="formLoading" :model="formData" :rules="formRules" :disabled="isDetail" label-width="100px">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8">
|
||||||
<el-form-item label="凭证编号" prop="voucherNo">
|
<el-form-item label="凭证编号" prop="voucherNo">
|
||||||
@@ -146,13 +185,18 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
import * as BookkeepingVoucherApi from '@/api/erp/finance/bookkeeping'
|
import * as BookkeepingVoucherApi from '@/api/erp/finance/bookkeeping'
|
||||||
import { SubjectApi } from '@/api/erp/finance/subject'
|
import { SubjectApi } from '@/api/erp/finance/subject'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'BookkeepingVoucherForm' })
|
defineOptions({ name: 'BookkeepingVoucherForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,32 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<!-- 移动端布局 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
:rules="formRules"
|
<div class="mobile-form__section">
|
||||||
label-width="120px"
|
<div class="mobile-form__section-title">会计期间信息</div>
|
||||||
v-loading="formLoading"
|
<el-form-item label="期间编码" prop="periodCode"><el-input v-model="formData.periodCode" placeholder="请输入期间编码(yyyyMM格式)" /></el-form-item>
|
||||||
>
|
<el-form-item label="期间名称" prop="periodName"><el-input v-model="formData.periodName" placeholder="请输入期间名称" /></el-form-item>
|
||||||
|
<el-form-item label="会计年度" prop="fiscalYear"><el-input v-model="formData.fiscalYear" placeholder="请输入会计年度" /></el-form-item>
|
||||||
|
<el-form-item label="开始日期" prop="startDate"><el-date-picker v-model="formData.startDate" type="date" placeholder="请选择开始日期" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="结束日期" prop="endDate"><el-date-picker v-model="formData.endDate" type="date" placeholder="请选择结束日期" value-format="YYYY-MM-DD" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status"><el-radio-group v-model="formData.status"><el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio></el-radio-group></el-form-item>
|
||||||
|
<el-form-item label="是否当前期间" prop="isCurrent"><el-switch v-model="formData.isCurrent" active-text="是" inactive-text="否" /></el-form-item>
|
||||||
|
<el-form-item label="描述" prop="description"><el-input v-model="formData.description" type="textarea" placeholder="请输入描述" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false" style="flex:1">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" style="flex:2">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" v-loading="formLoading">
|
||||||
<el-form-item label="期间编码" prop="periodCode">
|
<el-form-item label="期间编码" prop="periodCode">
|
||||||
<el-input v-model="formData.periodCode" placeholder="请输入期间编码(yyyyMM格式)" />
|
<el-input v-model="formData.periodCode" placeholder="请输入期间编码(yyyyMM格式)" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
@@ -63,12 +83,17 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { FiscalPeriodApi, FiscalPeriodVO } from '@/api/erp/finance/fiscal-period'
|
import { FiscalPeriodApi, FiscalPeriodVO } from '@/api/erp/finance/fiscal-period'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 会计期间 表单 */
|
/** ERP 会计期间 表单 */
|
||||||
defineOptions({ name: 'FiscalPeriodForm' })
|
defineOptions({ name: 'FiscalPeriodForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,35 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<!-- 移动端布局 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
:rules="formRules"
|
<div class="mobile-form__section">
|
||||||
label-width="110px"
|
<div class="mobile-form__section-title">发票信息</div>
|
||||||
v-loading="formLoading"
|
<el-form-item label="发票日期" prop="invoiceDate"><el-date-picker v-model="formData.invoiceDate" type="date" value-format="x" placeholder="选择发票日期" style="width:100%" /></el-form-item>
|
||||||
>
|
<el-form-item label="发票类别" prop="invoiceCategory"><el-select v-model="formData.invoiceCategory" placeholder="请选择发票类别" style="width:100%"><el-option v-for="dict in getStrDictOptions(DICT_TYPE.ERP_FINANCE_INVOICE_INOUT_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="发票类型" prop="invoiceType"><el-select v-model="formData.invoiceType" placeholder="请选择发票类型" style="width:100%"><el-option v-for="dict in getStrDictOptions(DICT_TYPE.ERP_FINANCE_INVOICE_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="会计科目" prop="accountSubjects" required><el-select v-model="formData.accountSubjects" placeholder="请选择会计科目" style="width:100%"><el-option v-for="dict in getStrDictOptions(DICT_TYPE.ERP_FINANCE_SUBJECT_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="发票号码" prop="invoiceNo"><el-input v-model="formData.invoiceNo" placeholder="发票号码" /></el-form-item>
|
||||||
|
<el-form-item label="发票方" prop="counterpartyName"><el-input v-model="formData.counterpartyName" placeholder="客户/供应商名称" /></el-form-item>
|
||||||
|
<el-form-item label="金额(不含税)" prop="amountWithoutTax"><el-input v-model="formData.amountWithoutTax" placeholder="不含税金额" /></el-form-item>
|
||||||
|
<el-form-item label="税率(%)" prop="taxRate"><el-input v-model="formData.taxRate" placeholder="税率 %" /></el-form-item>
|
||||||
|
<el-form-item label="发票附件" prop="attachment"><UploadFile v-model="formData.attachment" :limit="1" :fileType="['pdf', 'png', 'jpg', 'jpeg']" :fileSize="10" /></el-form-item>
|
||||||
|
<el-form-item label="发票状态" prop="invoiceStatus"><el-radio-group v-model="formData.invoiceStatus"><el-radio label="待认证">待认证</el-radio><el-radio label="已认证">已认证</el-radio></el-radio-group></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false" style="flex:1">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" style="flex:2">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="110px" v-loading="formLoading">
|
||||||
<el-row :gutter="16">
|
<el-row :gutter="16">
|
||||||
<el-col :span="12">
|
<el-col :span="12">
|
||||||
<!-- <el-form-item label="发票日期" prop="invoiceDate">
|
<!-- <el-form-item label="发票日期" prop="invoiceDate">
|
||||||
@@ -172,15 +195,20 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { InvoiceApi, Invoice } from '@/api/erp/finance/invoice'
|
import { InvoiceApi, Invoice } from '@/api/erp/finance/invoice'
|
||||||
import UploadFile from '@/components/UploadFile/src/UploadFile.vue'
|
import UploadFile from '@/components/UploadFile/src/UploadFile.vue'
|
||||||
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getStrDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
import { SubjectApi } from '@/api/erp/finance/subject'
|
import { SubjectApi } from '@/api/erp/finance/subject'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** 财务发票主 表单 */
|
/** 财务发票主 表单 */
|
||||||
defineOptions({ name: 'InvoiceForm' })
|
defineOptions({ name: 'InvoiceForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<!-- 移动端布局 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
:rules="formRules"
|
<div class="mobile-form__section">
|
||||||
label-width="100px"
|
<div class="mobile-form__section-title">应付单信息</div>
|
||||||
v-loading="formLoading"
|
<el-form-item label="采购入库订单" prop="purchaseInNo"><el-input v-model="formData.purchaseInNo" readonly placeholder="请选择采购入库订单"><template #append><el-button @click="openPurchaseInSelect"><Icon icon="ep:search" /></el-button></template></el-input></el-form-item>
|
||||||
>
|
<el-form-item label="应付单号" prop="apoCode"><el-input v-model="formData.apoCode" placeholder="请输入应付单号" /></el-form-item>
|
||||||
|
<el-form-item label="供应商" prop="supplier"><el-input v-model="formData.supplier" placeholder="请输入供应商" /></el-form-item>
|
||||||
|
<el-form-item label="单据日期" prop="billDate"><el-date-picker v-model="formData.billDate" type="date" value-format="x" placeholder="选择单据日期" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="已付金额" prop="paymentPrice"><el-input v-model="formData.paymentPrice" placeholder="请输入已付金额" /></el-form-item>
|
||||||
|
<el-form-item label="总金额" prop="totalAmount"><el-input v-model="formData.totalAmount" placeholder="请输入总金额" /></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input v-model="formData.remark" placeholder="请输入备注" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false" style="flex:1">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" style="flex:2">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
|
||||||
<el-form-item label="采购入库订单" prop="purchaseInNo">
|
<el-form-item label="采购入库订单" prop="purchaseInNo">
|
||||||
<el-input v-model="formData.purchaseInNo" readonly placeholder="请选择采购入库订单">
|
<el-input v-model="formData.purchaseInNo" readonly placeholder="请选择采购入库订单">
|
||||||
<template #append>
|
<template #append>
|
||||||
@@ -94,15 +113,20 @@
|
|||||||
<PurchaseInPaymentEnableList ref="purchaseInSelectRef" @success="onPurchaseInSelected" />
|
<PurchaseInPaymentEnableList ref="purchaseInSelectRef" @success="onPurchaseInSelected" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { PayableOrderApi, PayableOrder } from '@/api/erp/finance/payableorder'
|
import { PayableOrderApi, PayableOrder } from '@/api/erp/finance/payableorder'
|
||||||
import PurchaseInPaymentEnableList from '@/views/erp/purchase/in/components/PurchaseInPaymentEnableList.vue'
|
import PurchaseInPaymentEnableList from '@/views/erp/purchase/in/components/PurchaseInPaymentEnableList.vue'
|
||||||
import { PurchaseInVO } from '@/api/erp/purchase/in'
|
import { PurchaseInVO } from '@/api/erp/purchase/in'
|
||||||
import { formatToDate } from '@/utils/dateUtil'
|
import { formatToDate } from '@/utils/dateUtil'
|
||||||
import { SubjectApi, SubjectVO } from '@/api/erp/finance/subject'
|
import { SubjectApi, SubjectVO } from '@/api/erp/finance/subject'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** 应付单 表单 */
|
/** 应付单 表单 */
|
||||||
defineOptions({ name: 'PayableOrderForm' })
|
defineOptions({ name: 'PayableOrderForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
|||||||
@@ -1,156 +1,63 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1080">
|
<!-- 移动端布局 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" :disabled="disabled">
|
||||||
:rules="formRules"
|
<div class="mobile-form__section">
|
||||||
label-width="100px"
|
<div class="mobile-form__section-title">基本信息</div>
|
||||||
v-loading="formLoading"
|
<el-form-item label="付款单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item>
|
||||||
:disabled="disabled"
|
<el-form-item label="付款时间" prop="paymentTime"><el-date-picker v-model="formData.paymentTime" type="date" value-format="x" placeholder="选择付款时间" style="width:100%" /></el-form-item>
|
||||||
>
|
<el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable placeholder="请选择供应商" style="width:100%"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="发票" prop="invoiceNo"><el-input v-model="invoiceDisplay" readonly placeholder="请选择发票"><template #append><el-button @click="openInvoiceSelect"><Icon icon="ep:search" /></el-button></template></el-input></el-form-item>
|
||||||
|
<el-form-item label="财务人员" prop="financeUserId"><el-select v-model="formData.financeUserId" clearable filterable placeholder="请选择财务人员" style="width:100%"><el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="2" placeholder="请输入备注" /></el-form-item>
|
||||||
|
<el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">采购入库、退货单</div>
|
||||||
|
<FinancePaymentItemForm ref="itemFormRef" :supplier-id="formData.supplierId" :items="formData.items" :disabled="disabled" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">结算信息</div>
|
||||||
|
<el-form-item label="付款账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" style="width:100%"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="合计付款" prop="totalPrice"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
|
<el-form-item label="优惠金额" prop="discountPrice"><el-input-number v-model="formData.discountPrice" controls-position="right" :precision="4" placeholder="请输入优惠金额" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="实际付款"><el-input disabled v-model="formData.paymentPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible" width="1080">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading" :disabled="disabled">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8"><el-form-item label="付款单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item></el-col>
|
||||||
<el-form-item label="付款单号" prop="no">
|
<el-col :span="8"><el-form-item label="付款时间" prop="paymentTime"><el-date-picker v-model="formData.paymentTime" type="date" value-format="x" placeholder="选择付款时间" class="!w-1/1" /></el-form-item></el-col>
|
||||||
<el-input disabled v-model="formData.no" placeholder="保存时自动生成" />
|
<el-col :span="8"><el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable placeholder="请选择供应商" class="!w-1/1"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
</el-form-item>
|
<el-col :span="8"><el-form-item label="发票" prop="invoiceNo"><el-input v-model="invoiceDisplay" readonly placeholder="请选择发票"><template #append><el-button @click="openInvoiceSelect"><Icon icon="ep:search" /> 选择</el-button></template></el-input></el-form-item></el-col>
|
||||||
</el-col>
|
<el-col :span="8"><el-form-item label="财务人员" prop="financeUserId"><el-select v-model="formData.financeUserId" clearable filterable placeholder="请选择财务人员" class="!w-1/1"><el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="16"><el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="1" placeholder="请输入备注" /></el-form-item></el-col>
|
||||||
<el-form-item label="付款时间" prop="paymentTime">
|
<el-col :span="8"><el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item></el-col>
|
||||||
<el-date-picker
|
|
||||||
v-model="formData.paymentTime"
|
|
||||||
type="date"
|
|
||||||
value-format="x"
|
|
||||||
placeholder="选择付款时间"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="供应商" prop="supplierId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.supplierId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择供应商"
|
|
||||||
class="!w-1/1"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in supplierList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="发票" prop="invoiceNo">
|
|
||||||
<el-input v-model="invoiceDisplay" readonly placeholder="请选择发票">
|
|
||||||
<template #append>
|
|
||||||
<el-button @click="openInvoiceSelect">
|
|
||||||
<Icon icon="ep:search" /> 选择
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="财务人员" prop="financeUserId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.financeUserId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择财务人员"
|
|
||||||
class="!w-1/1"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in userList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.nickname"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="16">
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
v-model="formData.remark"
|
|
||||||
:rows="1"
|
|
||||||
placeholder="请输入备注"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="附件" prop="fileUrl">
|
|
||||||
<UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
<!-- 子表的表单 -->
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
||||||
<el-tab-pane label="采购入库、退货单" name="item">
|
<el-tab-pane label="采购入库、退货单" name="item"><FinancePaymentItemForm ref="itemFormRef" :supplier-id="formData.supplierId" :items="formData.items" :disabled="disabled" /></el-tab-pane>
|
||||||
<FinancePaymentItemForm
|
|
||||||
ref="itemFormRef"
|
|
||||||
:supplier-id="formData.supplierId"
|
|
||||||
:items="formData.items"
|
|
||||||
:disabled="disabled"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8"><el-form-item label="付款账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" class="!w-1/1"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
<el-form-item label="付款账户" prop="accountId">
|
<el-col :span="8"><el-form-item label="合计付款" prop="totalPrice"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
<el-select
|
<el-col :span="8"><el-form-item label="优惠金额" prop="discountPrice"><el-input-number v-model="formData.discountPrice" controls-position="right" :precision="4" placeholder="请输入优惠金额" class="!w-1/1" /></el-form-item></el-col>
|
||||||
v-model="formData.accountId"
|
<el-col :span="8"><el-form-item label="实际付款"><el-input disabled v-model="formData.paymentPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择结算账户"
|
|
||||||
class="!w-1/1"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in accountList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="合计付款" prop="totalPrice">
|
|
||||||
<el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="优惠金额" prop="discountPrice">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.discountPrice"
|
|
||||||
controls-position="right"
|
|
||||||
:precision="4"
|
|
||||||
placeholder="请输入优惠金额"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="实际付款">
|
|
||||||
<el-input
|
|
||||||
disabled
|
|
||||||
v-model="formData.paymentPrice"
|
|
||||||
:formatter="erpPriceInputFormatter"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
确 定
|
|
||||||
</el-button>
|
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
@@ -164,6 +71,7 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { FinancePaymentApi, FinancePaymentVO } from '@/api/erp/finance/payment'
|
import { FinancePaymentApi, FinancePaymentVO } from '@/api/erp/finance/payment'
|
||||||
import FinancePaymentItemForm from './components/FinancePaymentItemForm.vue'
|
import FinancePaymentItemForm from './components/FinancePaymentItemForm.vue'
|
||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
@@ -172,10 +80,14 @@ import * as UserApi from '@/api/system/user'
|
|||||||
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
||||||
import InvoiceSelectList from '@/views/erp/finance/invoice/components/InvoiceSelectList.vue'
|
import InvoiceSelectList from '@/views/erp/finance/invoice/components/InvoiceSelectList.vue'
|
||||||
import { Invoice } from '@/api/erp/finance/invoice'
|
import { Invoice } from '@/api/erp/finance/invoice'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 付款单表单 */
|
/** ERP 付款单表单 */
|
||||||
defineOptions({ name: 'FinancePaymentForm' })
|
defineOptions({ name: 'FinancePaymentForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fetchInvoicePage: (params: any) => Promise<any>
|
fetchInvoicePage: (params: any) => Promise<any>
|
||||||
}>()
|
}>()
|
||||||
@@ -330,3 +242,10 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form { padding: 0 4px; }
|
||||||
|
.mobile-form__section { background: #fff; border-radius: 10px; padding: 14px; margin-bottom: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||||
|
.mobile-form__section-title { font-size: 15px; font-weight: 600; color: #303133; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; }
|
||||||
|
.mobile-form__footer { position: sticky; bottom: 0; background: #fff; padding: 12px 16px; padding-bottom: calc(12px + env(safe-area-inset-bottom)); border-top: 1px solid #eee; display: flex; justify-content: flex-end; gap: 12px; z-index: 10; margin: 0 -4px; .el-button { flex: 1; height: 40px; font-size: 15px; } }
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,13 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form
|
<!-- 移动端布局 -->
|
||||||
ref="formRef"
|
<el-form v-if="isMobile" ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-position="top" :inline-message="true" :disabled="disabled">
|
||||||
:model="formData"
|
<div class="mobile-item-list">
|
||||||
:rules="formRules"
|
<div v-for="(row, $index) in formData" :key="$index" class="mobile-item-card">
|
||||||
v-loading="formLoading"
|
<div class="mobile-item-card__header">
|
||||||
label-width="0px"
|
<span class="mobile-item-card__index">#{{ $index + 1 }}</span>
|
||||||
:inline-message="true"
|
<span class="mobile-item-card__name">{{ row.bizNo || '未选择单据' }}</span>
|
||||||
:disabled="disabled"
|
<el-button :disabled="disabled" @click="handleDelete($index)" link type="danger" size="small">删除</el-button>
|
||||||
>
|
</div>
|
||||||
|
<div class="mobile-item-card__body">
|
||||||
|
<div class="mobile-item-card__info-row"><span class="mobile-item-card__info-label">应付金额</span><span class="mobile-item-card__info-value">{{ erpPriceInputFormatter(row.totalPrice) }}</span></div>
|
||||||
|
<div class="mobile-item-card__info-row"><span class="mobile-item-card__info-label">已付金额</span><span class="mobile-item-card__info-value">{{ erpPriceInputFormatter(row.paidPrice) }}</span></div>
|
||||||
|
<el-form-item label="本次付款" :prop="`${$index}.paymentPrice`"><el-input-number v-model="row.paymentPrice" controls-position="right" :precision="4" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="备注" :prop="`${$index}.remark`"><el-input v-model="row.remark" placeholder="请输入备注" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-summary" v-if="formData.length > 0">
|
||||||
|
<div class="mobile-item-summary__row"><span>应付合计</span><span>{{ erpPriceInputFormatter(summaryData.totalPrice) }}</span></div>
|
||||||
|
<div class="mobile-item-summary__row"><span>已付合计</span><span>{{ erpPriceInputFormatter(summaryData.paidPrice) }}</span></div>
|
||||||
|
<div class="mobile-item-summary__row mobile-item-summary__row--total"><span>本次付款合计</span><span>{{ erpPriceInputFormatter(summaryData.paymentPrice) }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-add" v-if="!disabled">
|
||||||
|
<el-button @click="handleOpenPurchaseIn" round>+ 添加采购入库单</el-button>
|
||||||
|
<el-button @click="handleOpenPurchaseReturn" round>+ 添加采购退货单</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<el-form v-if="!isMobile" ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="0px" :inline-message="true" :disabled="disabled">
|
||||||
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px">
|
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px">
|
||||||
<el-table-column label="序号" type="index" align="center" width="60" />
|
<el-table-column label="序号" type="index" align="center" width="60" />
|
||||||
<el-table-column label="采购单据编号" min-width="200">
|
<el-table-column label="采购单据编号" min-width="200">
|
||||||
@@ -69,7 +90,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-row justify="center" class="mt-3" v-if="!disabled">
|
<el-row v-if="!isMobile && !disabled" justify="center" class="mt-3">
|
||||||
<el-button @click="handleOpenPurchaseIn" round>+ 添加采购入库单</el-button>
|
<el-button @click="handleOpenPurchaseIn" round>+ 添加采购入库单</el-button>
|
||||||
<el-button @click="handleOpenPurchaseReturn" round>+ 添加采购退货单</el-button>
|
<el-button @click="handleOpenPurchaseReturn" round>+ 添加采购退货单</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -91,6 +112,7 @@
|
|||||||
<PurchaseReturnForm ref="purchaseReturnFormRef" />
|
<PurchaseReturnForm ref="purchaseReturnFormRef" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, watch } from 'vue'
|
||||||
import { ProductVO } from '@/api/erp/product/product'
|
import { ProductVO } from '@/api/erp/product/product'
|
||||||
import { erpPriceInputFormatter, getSumValue } from '@/utils'
|
import { erpPriceInputFormatter, getSumValue } from '@/utils'
|
||||||
import PurchaseInPaymentEnableList from '@/views/erp/purchase/in/components/PurchaseInPaymentEnableList.vue'
|
import PurchaseInPaymentEnableList from '@/views/erp/purchase/in/components/PurchaseInPaymentEnableList.vue'
|
||||||
@@ -100,6 +122,10 @@ import PurchaseReturnForm from '@/views/erp/purchase/return/PurchaseReturnForm.v
|
|||||||
import { PurchaseInVO } from '@/api/erp/purchase/in'
|
import { PurchaseInVO } from '@/api/erp/purchase/in'
|
||||||
import { ErpBizType } from '@/utils/constants'
|
import { ErpBizType } from '@/utils/constants'
|
||||||
import { PurchaseReturnVO } from '@/api/erp/purchase/return'
|
import { PurchaseReturnVO } from '@/api/erp/purchase/return'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
items: undefined
|
items: undefined
|
||||||
@@ -125,21 +151,25 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 合计 */
|
/** 合计 - 移动端 */
|
||||||
|
const summaryData = computed(() => {
|
||||||
|
return {
|
||||||
|
totalPrice: getSumValue(formData.value.map((item) => Number(item.totalPrice))),
|
||||||
|
paidPrice: getSumValue(formData.value.map((item) => Number(item.paidPrice))),
|
||||||
|
paymentPrice: getSumValue(formData.value.map((item) => Number(item.paymentPrice)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 合计 - PC端 */
|
||||||
const getSummaries = (param: SummaryMethodProps) => {
|
const getSummaries = (param: SummaryMethodProps) => {
|
||||||
const { columns, data } = param
|
const { columns, data } = param
|
||||||
const sums: string[] = []
|
const sums: string[] = []
|
||||||
columns.forEach((column, index: number) => {
|
columns.forEach((column, index: number) => {
|
||||||
if (index === 0) {
|
if (index === 0) { sums[index] = '合计'; return }
|
||||||
sums[index] = '合计'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (['totalPrice', 'paidPrice', 'paymentPrice'].includes(column.property)) {
|
if (['totalPrice', 'paidPrice', 'paymentPrice'].includes(column.property)) {
|
||||||
const sum = getSumValue(data.map((item) => Number(item[column.property])))
|
const sum = getSumValue(data.map((item) => Number(item[column.property])))
|
||||||
sums[index] = erpPriceInputFormatter(sum)
|
sums[index] = erpPriceInputFormatter(sum)
|
||||||
} else {
|
} else { sums[index] = '' }
|
||||||
sums[index] = ''
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return sums
|
return sums
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,63 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert
|
<!-- 移动端布局 -->
|
||||||
title="【财务】采购付款、销售收款"
|
<div v-if="isMobile" class="mobile-payment">
|
||||||
url="https://doc.iocoder.cn/sale/finance-payment-receipt/"
|
<div class="mobile-header">
|
||||||
/>
|
<div class="mobile-header__search"><el-input v-model="queryParams.no" placeholder="搜索付款单号" clearable @keyup.enter="handleQuery" :prefix-icon="Search" /></div>
|
||||||
|
<div class="mobile-header__actions">
|
||||||
|
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
||||||
|
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:finance-payment:create']" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-header__quick-filter">
|
||||||
|
<div class="quick-filter-item" :class="{ active: queryParams.status === undefined }" @click="handleQuickFilter(undefined)">全部</div>
|
||||||
|
<div class="quick-filter-item" :class="{ active: queryParams.status === 10 }" @click="handleQuickFilter(10)">待审核</div>
|
||||||
|
<div class="quick-filter-item" :class="{ active: queryParams.status === 20 }" @click="handleQuickFilter(20)">已审核</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-list" v-loading="loading">
|
||||||
|
<div v-if="list.length === 0 && !loading" class="mobile-empty"><el-empty description="暂无付款记录" /></div>
|
||||||
|
<div v-for="item in list" :key="item.id" class="mobile-card" @click="handleRowClickMobile(item)">
|
||||||
|
<div class="mobile-card__header">
|
||||||
|
<span class="mobile-card__no">{{ item.no }}</span>
|
||||||
|
<dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="item.status" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__body">
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">供应商</span><span class="mobile-card__value">{{ item.supplierName || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">付款时间</span><span class="mobile-card__value">{{ formatDate2(item.paymentTime) }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">财务人员</span><span class="mobile-card__value">{{ item.financeUserName || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__nums">
|
||||||
|
<div class="mobile-card__num-item"><div class="mobile-card__num-val">{{ erpPriceTableColumnFormatter(null,null,item.totalPrice,null) }}</div><div class="mobile-card__num-label">合计付款</div></div>
|
||||||
|
<div class="mobile-card__num-item"><div class="mobile-card__num-val mobile-card__num-val--price">{{ erpPriceTableColumnFormatter(null,null,item.paymentPrice,null) }}</div><div class="mobile-card__num-label">实际付款</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__footer">
|
||||||
|
<el-button size="small" @click.stop="openForm('detail', item.id)" v-hasPermi="['erp:finance-payment:query']">详情</el-button>
|
||||||
|
<el-button size="small" type="primary" @click.stop="openForm('update', item.id)" v-hasPermi="['erp:finance-payment:update']" :disabled="item.status === 20">编辑</el-button>
|
||||||
|
<el-button size="small" type="primary" @click.stop="handleUpdateStatus(item.id, 20)" v-hasPermi="['erp:finance-payment:update-status']" v-if="item.status === 10">审批</el-button>
|
||||||
|
<el-button size="small" type="danger" @click.stop="handleUpdateStatus(item.id, 10)" v-hasPermi="['erp:finance-payment:update-status']" v-else>反审批</el-button>
|
||||||
|
<el-button size="small" type="danger" @click.stop="handleDelete([item.id])" v-hasPermi="['erp:finance-payment:delete']">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-pagination" v-if="total > 0"><Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" /></div>
|
||||||
|
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="70%">
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
|
<el-form-item label="供应商" prop="supplierId"><el-select v-model="queryParams.supplierId" clearable filterable placeholder="请选择供应商" style="width:100%"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="付款时间" prop="paymentTime"><el-date-picker v-model="queryParams.paymentTime" 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')]" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="付款账户" prop="accountId"><el-select v-model="queryParams.accountId" clearable filterable placeholder="请选择付款账户" style="width:100%"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status"><el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width:100%"><el-option v-for="dict in getIntDictOptions(DICT_TYPE.ERP_AUDIT_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value" /></el-select></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
<el-form
|
<el-form-item label="付款单号" prop="no"><el-input v-model="queryParams.no" placeholder="请输入付款单号" clearable @keyup.enter="handleQuery" class="!w-240px" /></el-form-item>
|
||||||
class="-mb-15px"
|
|
||||||
:model="queryParams"
|
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="付款单号" prop="no">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.no"
|
|
||||||
placeholder="请输入付款单号"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="付款时间" prop="paymentTime">
|
<el-form-item label="付款时间" prop="paymentTime">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="queryParams.paymentTime"
|
v-model="queryParams.paymentTime"
|
||||||
@@ -252,22 +288,18 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
<Pagination
|
|
||||||
:total="total"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<FinancePaymentForm ref="formRef" :fetch-invoice-page="fetchInvoicePage" @success="getList" />
|
<FinancePaymentForm ref="formRef" :fetch-invoice-page="fetchInvoicePage" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { dateFormatter2 } from '@/utils/formatTime'
|
import { dateFormatter2, formatDate2 } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { FinancePaymentApi, FinancePaymentVO } from '@/api/erp/finance/payment'
|
import { FinancePaymentApi, FinancePaymentVO } from '@/api/erp/finance/payment'
|
||||||
import FinancePaymentForm from './FinancePaymentForm.vue'
|
import FinancePaymentForm from './FinancePaymentForm.vue'
|
||||||
@@ -277,9 +309,14 @@ import * as UserApi from '@/api/system/user'
|
|||||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
import { Search, Plus, Filter } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
/** ERP 付款单列表 */
|
/** ERP 付款单列表 */
|
||||||
defineOptions({ name: 'ErpPurchaseOrder' })
|
defineOptions({ name: 'ErpFinancePayment' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
@@ -302,6 +339,7 @@ const queryParams = reactive({
|
|||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const exportLoading = ref(false) // 导出的加载中
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
const filterVisible = ref(false) // 筛选抽屉
|
||||||
const supplierList = ref<SupplierVO[]>([]) // 供应商列表
|
const supplierList = ref<SupplierVO[]>([]) // 供应商列表
|
||||||
const userList = ref<UserVO[]>([]) // 用户列表
|
const userList = ref<UserVO[]>([]) // 用户列表
|
||||||
const accountList = ref<AccountVO[]>([]) // 账户列表
|
const accountList = ref<AccountVO[]>([]) // 账户列表
|
||||||
@@ -329,10 +367,29 @@ const handleQuery = () => {
|
|||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value?.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 筛选确认 */
|
||||||
|
const handleFilterConfirm = () => {
|
||||||
|
filterVisible.value = false
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 快捷筛选 */
|
||||||
|
const handleQuickFilter = (status: number | undefined) => {
|
||||||
|
queryParams.status = status
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 移动端行点击 */
|
||||||
|
const handleRowClickMobile = (row: FinancePaymentVO) => {
|
||||||
|
if (row.status === 20) openForm('detail', row.id)
|
||||||
|
else if (row.status === 10) openForm('update', row.id)
|
||||||
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
@@ -420,6 +477,30 @@ onMounted(async () => {
|
|||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
accountList.value = await AccountApi.getAccountSimpleList()
|
accountList.value = await AccountApi.getAccountSimpleList()
|
||||||
})
|
})
|
||||||
// TODO 芋艿:可优化功能:列表界面,支持导入
|
|
||||||
// TODO 芋艿:可优化功能:详情界面,支持打印
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-payment { padding: 12px; background: #f5f5f5; min-height: 100vh; }
|
||||||
|
.mobile-header { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
|
||||||
|
.mobile-header__search { flex: 1; }
|
||||||
|
.mobile-header__actions { display: flex; gap: 4px; }
|
||||||
|
.mobile-header__quick-filter { display: flex; gap: 12px; margin: 8px 0; }
|
||||||
|
.quick-filter-item { padding: 4px 12px; font-size: 14px; border-radius: 20px; cursor: pointer; color: #909399; background: transparent; transition: all 0.2s; }
|
||||||
|
.quick-filter-item.active { color: #fff; background: #409eff; }
|
||||||
|
.mobile-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.mobile-empty { padding: 40px 0; }
|
||||||
|
.mobile-card { background: #fff; border-radius: 10px; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||||
|
.mobile-card__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||||
|
.mobile-card__no { font-weight: 600; font-size: 15px; color: #303133; }
|
||||||
|
.mobile-card__body { font-size: 13px; }
|
||||||
|
.mobile-card__row { display: flex; justify-content: space-between; padding: 3px 0; }
|
||||||
|
.mobile-card__label { color: #909399; flex-shrink: 0; margin-right: 12px; }
|
||||||
|
.mobile-card__value { color: #606266; text-align: right; }
|
||||||
|
.mobile-card__nums { display: flex; justify-content: space-around; margin-top: 10px; padding: 10px 0; border-top: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0; }
|
||||||
|
.mobile-card__num-item { text-align: center; }
|
||||||
|
.mobile-card__num-val { font-size: 15px; font-weight: 600; color: #303133; }
|
||||||
|
.mobile-card__num-val--price { color: #e6a23c; }
|
||||||
|
.mobile-card__num-label { font-size: 11px; color: #909399; margin-top: 2px; }
|
||||||
|
.mobile-card__footer { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
||||||
|
.mobile-pagination { margin-top: 12px; display: flex; justify-content: center; :deep(.el-pagination) { flex-wrap: wrap; justify-content: center; } }
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,167 +1,72 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1080">
|
<!-- 移动端布局 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" :disabled="disabled">
|
||||||
:rules="formRules"
|
<div class="mobile-form__section">
|
||||||
label-width="100px"
|
<div class="mobile-form__section-title">基本信息</div>
|
||||||
v-loading="formLoading"
|
<el-form-item label="收款单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item>
|
||||||
:disabled="disabled"
|
<el-form-item label="收款时间" prop="receiptTime"><el-date-picker v-model="formData.receiptTime" type="date" value-format="x" placeholder="选择收款时间" style="width:100%" /></el-form-item>
|
||||||
>
|
<el-form-item label="客户" prop="customerId"><el-select v-model="formData.customerId" clearable filterable placeholder="请选择客户" style="width:100%"><el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="发票" prop="invoiceNo"><el-input v-model="invoiceDisplay" readonly placeholder="请选择发票"><template #append><el-button @click="openInvoiceSelect"><Icon icon="ep:search" /></el-button></template></el-input></el-form-item>
|
||||||
|
<el-form-item label="财务人员" prop="financeUserId"><el-select v-model="formData.financeUserId" clearable filterable placeholder="请选择财务人员" style="width:100%"><el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="2" placeholder="请输入备注" /></el-form-item>
|
||||||
|
<el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">销售出库、退货单</div>
|
||||||
|
<FinanceReceiptItemForm ref="itemFormRef" :customer-id="formData.customerId" :items="formData.items" :disabled="disabled" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">结算信息</div>
|
||||||
|
<el-form-item label="收款账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" style="width:100%"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="合计收款" prop="totalPrice"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
|
<el-form-item label="优惠金额" prop="discountPrice"><el-input-number v-model="formData.discountPrice" controls-position="right" :precision="4" placeholder="请输入优惠金额" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="实际收款"><el-input disabled v-model="formData.receiptPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible" width="1080">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading" :disabled="disabled">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8"><el-form-item label="收款单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item></el-col>
|
||||||
<el-form-item label="收款单号" prop="no">
|
<el-col :span="8"><el-form-item label="收款时间" prop="receiptTime"><el-date-picker v-model="formData.receiptTime" type="date" value-format="x" placeholder="选择收款时间" class="!w-1/1" /></el-form-item></el-col>
|
||||||
<el-input disabled v-model="formData.no" placeholder="保存时自动生成" />
|
<el-col :span="8"><el-form-item label="客户" prop="customerId"><el-select v-model="formData.customerId" clearable filterable placeholder="请选择客户" class="!w-1/1"><el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
</el-form-item>
|
<el-col :span="8"><el-form-item label="发票" prop="invoiceNo"><el-input v-model="invoiceDisplay" readonly placeholder="请选择发票"><template #append><el-button @click="openInvoiceSelect"><Icon icon="ep:search" /> 选择</el-button></template></el-input></el-form-item></el-col>
|
||||||
</el-col>
|
<el-col :span="8"><el-form-item label="财务人员" prop="financeUserId"><el-select v-model="formData.financeUserId" clearable filterable placeholder="请选择财务人员" class="!w-1/1"><el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="16"><el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="1" placeholder="请输入备注" /></el-form-item></el-col>
|
||||||
<el-form-item label="收款时间" prop="receiptTime">
|
<el-col :span="8"><el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item></el-col>
|
||||||
<el-date-picker
|
|
||||||
v-model="formData.receiptTime"
|
|
||||||
type="date"
|
|
||||||
value-format="x"
|
|
||||||
placeholder="选择收款时间"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="客户" prop="customerId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.customerId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择客户"
|
|
||||||
class="!w-1/1"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in customerList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="发票" prop="invoiceNo">
|
|
||||||
<el-input v-model="invoiceDisplay" readonly placeholder="请选择发票">
|
|
||||||
<template #append>
|
|
||||||
<el-button @click="openInvoiceSelect"> <Icon icon="ep:search" /> 选择 </el-button>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="财务人员" prop="financeUserId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.financeUserId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择财务人员"
|
|
||||||
class="!w-1/1"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in userList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.nickname"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="16">
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
v-model="formData.remark"
|
|
||||||
:rows="1"
|
|
||||||
placeholder="请输入备注"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="附件" prop="fileUrl">
|
|
||||||
<UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
<!-- 子表的表单 -->
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
||||||
<el-tab-pane label="采购入库、退货单" name="item">
|
<el-tab-pane label="销售出库、退货单" name="item"><FinanceReceiptItemForm ref="itemFormRef" :customer-id="formData.customerId" :items="formData.items" :disabled="disabled" /></el-tab-pane>
|
||||||
<FinanceReceiptItemForm
|
|
||||||
ref="itemFormRef"
|
|
||||||
:customer-id="formData.customerId"
|
|
||||||
:items="formData.items"
|
|
||||||
:disabled="disabled"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8"><el-form-item label="收款账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" class="!w-1/1"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
<el-form-item label="收款账户" prop="accountId">
|
<el-col :span="8"><el-form-item label="合计收款" prop="totalPrice"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
<el-select
|
<el-col :span="8"><el-form-item label="优惠金额" prop="discountPrice"><el-input-number v-model="formData.discountPrice" controls-position="right" :precision="4" placeholder="请输入优惠金额" class="!w-1/1" /></el-form-item></el-col>
|
||||||
v-model="formData.accountId"
|
<el-col :span="8"><el-form-item label="实际收款"><el-input disabled v-model="formData.receiptPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择结算账户"
|
|
||||||
class="!w-1/1"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in accountList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="合计收款" prop="totalPrice">
|
|
||||||
<el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="优惠金额" prop="discountPrice">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.discountPrice"
|
|
||||||
controls-position="right"
|
|
||||||
:precision="4"
|
|
||||||
placeholder="请输入优惠金额"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="实际收款">
|
|
||||||
<el-input
|
|
||||||
disabled
|
|
||||||
v-model="formData.receiptPrice"
|
|
||||||
:formatter="erpPriceInputFormatter"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
确 定
|
|
||||||
</el-button>
|
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<!-- 发票选择弹窗 -->
|
<!-- 发票选择弹窗 -->
|
||||||
<InvoiceSelectList
|
<InvoiceSelectList ref="invoiceSelectRef" :invoice-category="2" :fetch-invoice-page="props.fetchInvoicePage" @success="handleInvoiceSelect" />
|
||||||
ref="invoiceSelectRef"
|
|
||||||
:invoice-category="2"
|
|
||||||
:fetch-invoice-page="props.fetchInvoicePage"
|
|
||||||
@success="handleInvoiceSelect"
|
|
||||||
/>
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { FinanceReceiptApi, FinanceReceiptVO } from '@/api/erp/finance/receipt'
|
import { FinanceReceiptApi, FinanceReceiptVO } from '@/api/erp/finance/receipt'
|
||||||
import FinanceReceiptItemForm from './components/FinanceReceiptItemForm.vue'
|
import FinanceReceiptItemForm from './components/FinanceReceiptItemForm.vue'
|
||||||
import { erpPriceInputFormatter } from '@/utils'
|
import { erpPriceInputFormatter } from '@/utils'
|
||||||
@@ -170,10 +75,14 @@ import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
|||||||
import { CustomerApi, CustomerVO } from '@/api/erp/sale/customer'
|
import { CustomerApi, CustomerVO } from '@/api/erp/sale/customer'
|
||||||
import InvoiceSelectList from '@/views/erp/finance/invoice/components/InvoiceSelectList.vue'
|
import InvoiceSelectList from '@/views/erp/finance/invoice/components/InvoiceSelectList.vue'
|
||||||
import { Invoice } from '@/api/erp/finance/invoice'
|
import { Invoice } from '@/api/erp/finance/invoice'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 收款单表单 */
|
/** ERP 收款单表单 */
|
||||||
defineOptions({ name: 'FinanceReceiptForm' })
|
defineOptions({ name: 'FinanceReceiptForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
fetchInvoicePage: (params: any) => Promise<any>
|
fetchInvoicePage: (params: any) => Promise<any>
|
||||||
}>()
|
}>()
|
||||||
@@ -324,3 +233,10 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form { padding: 0 4px; }
|
||||||
|
.mobile-form__section { background: #fff; border-radius: 10px; padding: 14px; margin-bottom: 12px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||||
|
.mobile-form__section-title { font-size: 15px; font-weight: 600; color: #303133; margin-bottom: 12px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; }
|
||||||
|
.mobile-form__footer { position: sticky; bottom: 0; background: #fff; padding: 12px 16px; padding-bottom: calc(12px + env(safe-area-inset-bottom)); border-top: 1px solid #eee; display: flex; justify-content: flex-end; gap: 12px; z-index: 10; margin: 0 -4px; .el-button { flex: 1; height: 40px; font-size: 15px; } }
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,13 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form
|
<!-- 移动端布局 -->
|
||||||
ref="formRef"
|
<el-form v-if="isMobile" ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-position="top" :inline-message="true" :disabled="disabled">
|
||||||
:model="formData"
|
<div class="mobile-item-list">
|
||||||
:rules="formRules"
|
<div v-for="(row, $index) in formData" :key="$index" class="mobile-item-card">
|
||||||
v-loading="formLoading"
|
<div class="mobile-item-card__header">
|
||||||
label-width="0px"
|
<span class="mobile-item-card__index">#{{ $index + 1 }}</span>
|
||||||
:inline-message="true"
|
<span class="mobile-item-card__name">{{ row.bizNo || '未选择单据' }}</span>
|
||||||
:disabled="disabled"
|
<el-button :disabled="disabled" @click="handleDelete($index)" link type="danger" size="small">删除</el-button>
|
||||||
>
|
</div>
|
||||||
|
<div class="mobile-item-card__body">
|
||||||
|
<div class="mobile-item-card__info-row"><span class="mobile-item-card__info-label">应收金额</span><span class="mobile-item-card__info-value">{{ erpPriceInputFormatter(row.totalPrice) }}</span></div>
|
||||||
|
<div class="mobile-item-card__info-row"><span class="mobile-item-card__info-label">已收金额</span><span class="mobile-item-card__info-value">{{ erpPriceInputFormatter(row.receiptedPrice) }}</span></div>
|
||||||
|
<el-form-item label="本次收款" :prop="`${$index}.receiptPrice`"><el-input-number v-model="row.receiptPrice" controls-position="right" :precision="4" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="备注" :prop="`${$index}.remark`"><el-input v-model="row.remark" placeholder="请输入备注" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-summary" v-if="formData.length > 0">
|
||||||
|
<div class="mobile-item-summary__row"><span>应收合计</span><span>{{ erpPriceInputFormatter(summaryData.totalPrice) }}</span></div>
|
||||||
|
<div class="mobile-item-summary__row"><span>已收合计</span><span>{{ erpPriceInputFormatter(summaryData.receiptedPrice) }}</span></div>
|
||||||
|
<div class="mobile-item-summary__row mobile-item-summary__row--total"><span>本次收款合计</span><span>{{ erpPriceInputFormatter(summaryData.receiptPrice) }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-item-add" v-if="!disabled">
|
||||||
|
<el-button @click="handleOpenSaleOut" round>+ 添加销售出库单</el-button>
|
||||||
|
<el-button @click="handleOpenSaleReturn" round>+ 添加销售退货单</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<el-form v-if="!isMobile" ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="0px" :inline-message="true" :disabled="disabled">
|
||||||
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px">
|
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px">
|
||||||
<el-table-column label="序号" type="index" align="center" width="60" />
|
<el-table-column label="序号" type="index" align="center" width="60" />
|
||||||
<el-table-column label="销售单据编号" min-width="200">
|
<el-table-column label="销售单据编号" min-width="200">
|
||||||
@@ -69,7 +90,7 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-form>
|
</el-form>
|
||||||
<el-row justify="center" class="mt-3" v-if="!disabled">
|
<el-row v-if="!isMobile && !disabled" justify="center" class="mt-3">
|
||||||
<el-button @click="handleOpenSaleOut" round>+ 添加销售出库单</el-button>
|
<el-button @click="handleOpenSaleOut" round>+ 添加销售出库单</el-button>
|
||||||
<el-button @click="handleOpenSaleReturn" round>+ 添加销售退货单</el-button>
|
<el-button @click="handleOpenSaleReturn" round>+ 添加销售退货单</el-button>
|
||||||
</el-row>
|
</el-row>
|
||||||
@@ -85,6 +106,7 @@
|
|||||||
<SaleReturnForm ref="saleReturnFormRef" />
|
<SaleReturnForm ref="saleReturnFormRef" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, watch } from 'vue'
|
||||||
import { ProductVO } from '@/api/erp/product/product'
|
import { ProductVO } from '@/api/erp/product/product'
|
||||||
import { erpPriceInputFormatter, getSumValue } from '@/utils'
|
import { erpPriceInputFormatter, getSumValue } from '@/utils'
|
||||||
import SaleOutReceiptEnableList from '@/views/erp/sale/out/components/SaleOutReceiptEnableList.vue'
|
import SaleOutReceiptEnableList from '@/views/erp/sale/out/components/SaleOutReceiptEnableList.vue'
|
||||||
@@ -94,6 +116,10 @@ import SaleReturnForm from '@/views/erp/sale/return/SaleReturnForm.vue'
|
|||||||
import { SaleOutVO } from '@/api/erp/sale/out'
|
import { SaleOutVO } from '@/api/erp/sale/out'
|
||||||
import { ErpBizType } from '@/utils/constants'
|
import { ErpBizType } from '@/utils/constants'
|
||||||
import { SaleReturnVO } from '@/api/erp/sale/return'
|
import { SaleReturnVO } from '@/api/erp/sale/return'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
items: undefined
|
items: undefined
|
||||||
@@ -119,21 +145,25 @@ watch(
|
|||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 合计 */
|
/** 合计 - 移动端 */
|
||||||
|
const summaryData = computed(() => {
|
||||||
|
return {
|
||||||
|
totalPrice: getSumValue(formData.value.map((item) => Number(item.totalPrice))),
|
||||||
|
receiptedPrice: getSumValue(formData.value.map((item) => Number(item.receiptedPrice))),
|
||||||
|
receiptPrice: getSumValue(formData.value.map((item) => Number(item.receiptPrice)))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/** 合计 - PC端 */
|
||||||
const getSummaries = (param: SummaryMethodProps) => {
|
const getSummaries = (param: SummaryMethodProps) => {
|
||||||
const { columns, data } = param
|
const { columns, data } = param
|
||||||
const sums: string[] = []
|
const sums: string[] = []
|
||||||
columns.forEach((column, index: number) => {
|
columns.forEach((column, index: number) => {
|
||||||
if (index === 0) {
|
if (index === 0) { sums[index] = '合计'; return }
|
||||||
sums[index] = '合计'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (['totalPrice', 'receiptedPrice', 'receiptPrice'].includes(column.property)) {
|
if (['totalPrice', 'receiptedPrice', 'receiptPrice'].includes(column.property)) {
|
||||||
const sum = getSumValue(data.map((item) => Number(item[column.property])))
|
const sum = getSumValue(data.map((item) => Number(item[column.property])))
|
||||||
sums[index] = erpPriceInputFormatter(sum)
|
sums[index] = erpPriceInputFormatter(sum)
|
||||||
} else {
|
} else { sums[index] = '' }
|
||||||
sums[index] = ''
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
return sums
|
return sums
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,27 +1,63 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert
|
<!-- 移动端布局 -->
|
||||||
title="【财务】采购付款、销售收款"
|
<div v-if="isMobile" class="mobile-receipt">
|
||||||
url="https://doc.iocoder.cn/sale/finance-payment-receipt/"
|
<div class="mobile-header">
|
||||||
/>
|
<div class="mobile-header__search"><el-input v-model="queryParams.no" placeholder="搜索收款单号" clearable @keyup.enter="handleQuery" :prefix-icon="Search" /></div>
|
||||||
|
<div class="mobile-header__actions">
|
||||||
|
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
||||||
|
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:finance-receipt:create']" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-header__quick-filter">
|
||||||
|
<div class="quick-filter-item" :class="{ active: queryParams.status === undefined }" @click="handleQuickFilter(undefined)">全部</div>
|
||||||
|
<div class="quick-filter-item" :class="{ active: queryParams.status === 10 }" @click="handleQuickFilter(10)">待审核</div>
|
||||||
|
<div class="quick-filter-item" :class="{ active: queryParams.status === 20 }" @click="handleQuickFilter(20)">已审核</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-list" v-loading="loading">
|
||||||
|
<div v-if="list.length === 0 && !loading" class="mobile-empty"><el-empty description="暂无收款记录" /></div>
|
||||||
|
<div v-for="item in list" :key="item.id" class="mobile-card" @click="handleRowClickMobile(item)">
|
||||||
|
<div class="mobile-card__header">
|
||||||
|
<span class="mobile-card__no">{{ item.no }}</span>
|
||||||
|
<dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="item.status" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__body">
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">客户</span><span class="mobile-card__value">{{ item.customerName || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">收款时间</span><span class="mobile-card__value">{{ formatDate2(item.receiptTime) }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">财务人员</span><span class="mobile-card__value">{{ item.financeUserName || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__nums">
|
||||||
|
<div class="mobile-card__num-item"><div class="mobile-card__num-val">{{ erpPriceTableColumnFormatter(null,null,item.totalPrice,null) }}</div><div class="mobile-card__num-label">合计收款</div></div>
|
||||||
|
<div class="mobile-card__num-item"><div class="mobile-card__num-val mobile-card__num-val--price">{{ erpPriceTableColumnFormatter(null,null,item.receiptPrice,null) }}</div><div class="mobile-card__num-label">实际收款</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__footer">
|
||||||
|
<el-button size="small" @click.stop="openForm('detail', item.id)" v-hasPermi="['erp:finance-receipt:query']">详情</el-button>
|
||||||
|
<el-button size="small" type="primary" @click.stop="openForm('update', item.id)" v-hasPermi="['erp:finance-receipt:update']" :disabled="item.status === 20">编辑</el-button>
|
||||||
|
<el-button size="small" type="primary" @click.stop="handleUpdateStatus(item.id, 20)" v-hasPermi="['erp:finance-receipt:update-status']" v-if="item.status === 10">审批</el-button>
|
||||||
|
<el-button size="small" type="danger" @click.stop="handleUpdateStatus(item.id, 10)" v-hasPermi="['erp:finance-receipt:update-status']" v-else>反审批</el-button>
|
||||||
|
<el-button size="small" type="danger" @click.stop="handleDelete([item.id])" v-hasPermi="['erp:finance-receipt:delete']">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-pagination" v-if="total > 0"><Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" /></div>
|
||||||
|
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="70%">
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
|
<el-form-item label="客户" prop="customerId"><el-select v-model="queryParams.customerId" clearable filterable placeholder="请选择客户" style="width:100%"><el-option v-for="item in customerList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="收款时间" prop="receiptTime"><el-date-picker v-model="queryParams.receiptTime" 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')]" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="收款账户" prop="accountId"><el-select v-model="queryParams.accountId" clearable filterable placeholder="请选择收款账户" style="width:100%"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status"><el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width:100%"><el-option v-for="dict in getIntDictOptions(DICT_TYPE.ERP_AUDIT_STATUS)" :key="dict.value" :label="dict.label" :value="dict.value" /></el-select></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
<el-form
|
<el-form-item label="收款单号" prop="no"><el-input v-model="queryParams.no" placeholder="请输入收款单号" clearable @keyup.enter="handleQuery" class="!w-240px" /></el-form-item>
|
||||||
class="-mb-15px"
|
|
||||||
:model="queryParams"
|
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="收款单号" prop="no">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.no"
|
|
||||||
placeholder="请输入收款单号"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="收款时间" prop="receiptTime">
|
<el-form-item label="收款时间" prop="receiptTime">
|
||||||
<el-date-picker
|
<el-date-picker
|
||||||
v-model="queryParams.receiptTime"
|
v-model="queryParams.receiptTime"
|
||||||
@@ -252,22 +288,18 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
<Pagination
|
|
||||||
:total="total"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<FinanceReceiptForm ref="formRef" :fetch-invoice-page="fetchInvoicePage" @success="getList" />
|
<FinanceReceiptForm ref="formRef" :fetch-invoice-page="fetchInvoicePage" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { dateFormatter2 } from '@/utils/formatTime'
|
import { dateFormatter2, formatDate2 } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { FinanceReceiptApi, FinanceReceiptVO } from '@/api/erp/finance/receipt'
|
import { FinanceReceiptApi, FinanceReceiptVO } from '@/api/erp/finance/receipt'
|
||||||
import FinanceReceiptForm from './FinanceReceiptForm.vue'
|
import FinanceReceiptForm from './FinanceReceiptForm.vue'
|
||||||
@@ -277,9 +309,14 @@ import * as UserApi from '@/api/system/user'
|
|||||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||||
import { CustomerApi, CustomerVO } from '@/api/erp/sale/customer'
|
import { CustomerApi, CustomerVO } from '@/api/erp/sale/customer'
|
||||||
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
import { Search, Plus, Filter } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
/** ERP 收款单列表 */
|
/** ERP 收款单列表 */
|
||||||
defineOptions({ name: 'ErpPurchaseOrder' })
|
defineOptions({ name: 'ErpFinanceReceipt' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
@@ -302,6 +339,7 @@ const queryParams = reactive({
|
|||||||
})
|
})
|
||||||
const queryFormRef = ref() // 搜索的表单
|
const queryFormRef = ref() // 搜索的表单
|
||||||
const exportLoading = ref(false) // 导出的加载中
|
const exportLoading = ref(false) // 导出的加载中
|
||||||
|
const filterVisible = ref(false) // 筛选抽屉
|
||||||
const customerList = ref<CustomerVO[]>([]) // 客户列表
|
const customerList = ref<CustomerVO[]>([]) // 客户列表
|
||||||
const userList = ref<UserVO[]>([]) // 用户列表
|
const userList = ref<UserVO[]>([]) // 用户列表
|
||||||
const accountList = ref<AccountVO[]>([]) // 账户列表
|
const accountList = ref<AccountVO[]>([]) // 账户列表
|
||||||
@@ -329,10 +367,29 @@ const handleQuery = () => {
|
|||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value?.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 筛选确认 */
|
||||||
|
const handleFilterConfirm = () => {
|
||||||
|
filterVisible.value = false
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 快捷筛选 */
|
||||||
|
const handleQuickFilter = (status: number | undefined) => {
|
||||||
|
queryParams.status = status
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 移动端行点击 */
|
||||||
|
const handleRowClickMobile = (row: FinanceReceiptVO) => {
|
||||||
|
if (row.status === 20) openForm('detail', row.id)
|
||||||
|
else if (row.status === 10) openForm('update', row.id)
|
||||||
|
}
|
||||||
|
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
const openForm = (type: string, id?: number) => {
|
const openForm = (type: string, id?: number) => {
|
||||||
@@ -420,6 +477,30 @@ onMounted(async () => {
|
|||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
accountList.value = await AccountApi.getAccountSimpleList()
|
accountList.value = await AccountApi.getAccountSimpleList()
|
||||||
})
|
})
|
||||||
// TODO 芋艿:可优化功能:列表界面,支持导入
|
|
||||||
// TODO 芋艿:可优化功能:详情界面,支持打印
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-receipt { padding: 12px; background: #f5f5f5; min-height: 100vh; }
|
||||||
|
.mobile-header { display: flex; gap: 8px; align-items: center; margin-bottom: 12px; }
|
||||||
|
.mobile-header__search { flex: 1; }
|
||||||
|
.mobile-header__actions { display: flex; gap: 4px; }
|
||||||
|
.mobile-header__quick-filter { display: flex; gap: 12px; margin: 8px 0; }
|
||||||
|
.quick-filter-item { padding: 4px 12px; font-size: 14px; border-radius: 20px; cursor: pointer; color: #909399; background: transparent; transition: all 0.2s; }
|
||||||
|
.quick-filter-item.active { color: #fff; background: #409eff; }
|
||||||
|
.mobile-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.mobile-empty { padding: 40px 0; }
|
||||||
|
.mobile-card { background: #fff; border-radius: 10px; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06); }
|
||||||
|
.mobile-card__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||||
|
.mobile-card__no { font-weight: 600; font-size: 15px; color: #303133; }
|
||||||
|
.mobile-card__body { font-size: 13px; }
|
||||||
|
.mobile-card__row { display: flex; justify-content: space-between; padding: 3px 0; }
|
||||||
|
.mobile-card__label { color: #909399; flex-shrink: 0; margin-right: 12px; }
|
||||||
|
.mobile-card__value { color: #606266; text-align: right; }
|
||||||
|
.mobile-card__nums { display: flex; justify-content: space-around; margin-top: 10px; padding: 10px 0; border-top: 1px solid #f0f0f0; border-bottom: 1px solid #f0f0f0; }
|
||||||
|
.mobile-card__num-item { text-align: center; }
|
||||||
|
.mobile-card__num-val { font-size: 15px; font-weight: 600; color: #303133; }
|
||||||
|
.mobile-card__num-val--price { color: #67c23a; }
|
||||||
|
.mobile-card__num-label { font-size: 11px; color: #909399; margin-top: 2px; }
|
||||||
|
.mobile-card__footer { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; }
|
||||||
|
.mobile-pagination { margin-top: 12px; display: flex; justify-content: center; :deep(.el-pagination) { flex-wrap: wrap; justify-content: center; } }
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,12 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<!-- 移动端布局 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
:rules="formRules"
|
<div class="mobile-form__section">
|
||||||
label-width="110px"
|
<div class="mobile-form__section-title">应收单信息</div>
|
||||||
v-loading="formLoading"
|
<el-form-item label="销售出库订单" prop="saleOrderNo"><el-input v-model="formData.saleOrderNo" readonly placeholder="请选择销售出库订单"><template #append><el-button @click="openSaleOutSelect"><Icon icon="ep:search" /></el-button></template></el-input></el-form-item>
|
||||||
>
|
<el-form-item label="应收单号" prop="aroCode"><el-input v-model="formData.aroCode" placeholder="请输入应收单号" /></el-form-item>
|
||||||
|
<el-form-item label="客户" prop="customer"><el-input v-model="formData.customer" placeholder="请选择客户" /></el-form-item>
|
||||||
|
<el-form-item label="单据日期" prop="billDate"><el-date-picker v-model="formData.billDate" type="date" value-format="x" placeholder="选择单据日期" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="已收金额" prop="receiptPrice"><el-input v-model="formData.receiptPrice" placeholder="请输入已收金额" /></el-form-item>
|
||||||
|
<el-form-item label="总金额" prop="totalAmount"><el-input v-model="formData.totalAmount" placeholder="请输入总金额" /></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input v-model="formData.remark" placeholder="请输入备注" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false" style="flex:1">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" style="flex:2">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="110px" v-loading="formLoading">
|
||||||
<el-form-item label="销售出库订单" prop="saleOrderNo">
|
<el-form-item label="销售出库订单" prop="saleOrderNo">
|
||||||
<el-input v-model="formData.saleOrderNo" readonly placeholder="请选择销售出库订单">
|
<el-input v-model="formData.saleOrderNo" readonly placeholder="请选择销售出库订单">
|
||||||
<template #append>
|
<template #append>
|
||||||
@@ -103,15 +122,20 @@
|
|||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { ReceivableOrderApi, ReceivableOrder } from '@/api/erp/finance/receivableorder'
|
import { ReceivableOrderApi, ReceivableOrder } from '@/api/erp/finance/receivableorder'
|
||||||
import SaleOutReceiptEnableList from '@/views/erp/sale/out/components/SaleOutReceiptEnableList.vue'
|
import SaleOutReceiptEnableList from '@/views/erp/sale/out/components/SaleOutReceiptEnableList.vue'
|
||||||
import { SaleOutVO } from '@/api/erp/sale/out'
|
import { SaleOutVO } from '@/api/erp/sale/out'
|
||||||
import { formatToDate } from '@/utils/dateUtil'
|
import { formatToDate } from '@/utils/dateUtil'
|
||||||
import { SubjectApi, SubjectVO } from '@/api/erp/finance/subject'
|
import { SubjectApi, SubjectVO } from '@/api/erp/finance/subject'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** 应收单 表单 */
|
/** 应收单 表单 */
|
||||||
defineOptions({ name: 'ReceivableOrderForm' })
|
defineOptions({ name: 'ReceivableOrderForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<!-- 移动端布局 -->
|
||||||
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">会计科目信息</div>
|
||||||
|
<el-form-item label="上级科目" prop="parentId"><el-tree-select v-model="formData.parentId" :data="subjectTree" placeholder="请选择上级科目" :props="{ label: 'name', value: 'id', children: 'children' }" check-strictly clearable style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="科目编码" prop="code"><el-input v-model="formData.code" placeholder="请输入科目编码" /></el-form-item>
|
||||||
|
<el-form-item label="科目名称" prop="name"><el-input v-model="formData.name" placeholder="请输入科目名称" /></el-form-item>
|
||||||
|
<el-form-item label="科目类型" prop="type"><el-select v-model="formData.type" placeholder="请选择科目类型" style="width:100%"><el-option v-for="dict in getIntDictOptions(DICT_TYPE.ERP_FINANCE_SUBJECT_TYPE)" :key="dict.value" :label="dict.label" :value="dict.value" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="余额方向" prop="direction"><el-select v-model="formData.direction" placeholder="请选择余额方向" style="width:100%"><el-option v-for="dict in getIntDictOptions(DICT_TYPE.ERP_FINANCE_SUBJECT_DIRECTION)" :key="dict.value" :label="dict.label" :value="dict.value" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status"><el-radio-group v-model="formData.status"><el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio></el-radio-group></el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sort"><el-input-number v-model="formData.sort" :min="0" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input v-model="formData.remark" type="textarea" placeholder="请输入备注" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
<template #footer>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false" style="flex:1">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" style="flex:2">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible">
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="formData"
|
:model="formData"
|
||||||
@@ -68,12 +94,17 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { SubjectApi, SubjectVO } from '@/api/erp/finance/subject'
|
import { SubjectApi, SubjectVO } from '@/api/erp/finance/subject'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 会计科目 表单 */
|
/** ERP 会计科目 表单 */
|
||||||
defineOptions({ name: 'SubjectForm' })
|
defineOptions({ name: 'SubjectForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,54 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<!-- 移动端使用抽屉 -->
|
||||||
<el-form
|
<el-drawer
|
||||||
ref="formRef"
|
v-if="isMobile"
|
||||||
:model="formData"
|
v-model="dialogVisible"
|
||||||
:rules="formRules"
|
:title="dialogTitle"
|
||||||
label-width="100px"
|
direction="rtl"
|
||||||
v-loading="formLoading"
|
size="100%"
|
||||||
|
:close-on-press-escape="true"
|
||||||
|
:destroy-on-close="true"
|
||||||
|
:append-to-body="true"
|
||||||
|
class="mobile-form-drawer"
|
||||||
>
|
>
|
||||||
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
|
<div class="mobile-form-section">
|
||||||
|
<div class="mobile-form-section__title">基础信息</div>
|
||||||
|
<el-form-item label="上级分类" prop="parentId">
|
||||||
|
<el-tree-select v-model="formData.parentId" :data="productCategoryTree" :props="defaultProps" check-strictly default-expand-all placeholder="请选择上级分类" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="分类名称" prop="name">
|
||||||
|
<el-input v-model="formData.name" placeholder="请输入分类名称" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="分类编码" prop="code">
|
||||||
|
<el-input v-model="formData.code" placeholder="请输入分类编码" clearable />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="排序" prop="sort">
|
||||||
|
<el-input-number v-model="formData.sort" placeholder="请输入排序" :precision="0" controls-position="right" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form-section">
|
||||||
|
<div class="mobile-form-section__title">状态信息</div>
|
||||||
|
<el-form-item label="开启状态" prop="status">
|
||||||
|
<el-radio-group v-model="formData.status">
|
||||||
|
<el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端使用对话框 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
|
||||||
<el-form-item label="上级编号" prop="parentId">
|
<el-form-item label="上级编号" prop="parentId">
|
||||||
<el-tree-select
|
<el-tree-select v-model="formData.parentId" :data="productCategoryTree" :props="defaultProps" check-strictly default-expand-all placeholder="请选择上级编号" />
|
||||||
v-model="formData.parentId"
|
|
||||||
:data="productCategoryTree"
|
|
||||||
:props="defaultProps"
|
|
||||||
check-strictly
|
|
||||||
default-expand-all
|
|
||||||
placeholder="请选择上级编号"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="名称" prop="name">
|
<el-form-item label="名称" prop="name">
|
||||||
<el-input v-model="formData.name" placeholder="请输入名称" />
|
<el-input v-model="formData.name" placeholder="请输入名称" />
|
||||||
@@ -28,13 +61,7 @@
|
|||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="状态" prop="status">
|
<el-form-item label="状态" prop="status">
|
||||||
<el-radio-group v-model="formData.status">
|
<el-radio-group v-model="formData.status">
|
||||||
<el-radio
|
<el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio>
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
|
||||||
:key="dict.value"
|
|
||||||
:value="dict.value"
|
|
||||||
>
|
|
||||||
{{ dict.label }}
|
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
</el-radio-group>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
@@ -45,14 +72,19 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
|
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
|
||||||
import { defaultProps, handleTree } from '@/utils/tree'
|
import { defaultProps, handleTree } from '@/utils/tree'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 产品分类 表单 */
|
/** ERP 产品分类 表单 */
|
||||||
defineOptions({ name: 'ProductCategoryForm' })
|
defineOptions({ name: 'ProductCategoryForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -143,3 +175,54 @@ const getProductCategoryTree = async () => {
|
|||||||
productCategoryTree.value.push(root)
|
productCategoryTree.value.push(root)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.mobile-form-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form-section__title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -12px -12px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
:deep(.el-input),
|
||||||
|
:deep(.el-select),
|
||||||
|
:deep(.el-input-number),
|
||||||
|
:deep(.el-tree-select) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,31 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert title="【产品】产品信息、分类、单位" url="https://doc.iocoder.cn/erp/product/" />
|
<!-- 移动端布局 -->
|
||||||
|
<div v-if="isMobile" class="mobile-product-category">
|
||||||
|
<!-- 搜索操作栏 -->
|
||||||
|
<div class="mobile-header">
|
||||||
|
<div class="mobile-header__search">
|
||||||
|
<el-input v-model="queryParams.name" placeholder="搜索分类名称" clearable @keyup.enter="handleQuery" :prefix-icon="Search" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-header__actions">
|
||||||
|
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
||||||
|
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:product-category:create']" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<ContentWrap>
|
<!-- 快捷操作 -->
|
||||||
<!-- 搜索工作栏 -->
|
<div class="mobile-quick-actions">
|
||||||
<el-form
|
<el-button size="small" type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:product-category:export']">导出</el-button>
|
||||||
class="-mb-15px"
|
<el-button size="small" type="danger" plain @click="toggleExpandAll">{{ isExpandAll ? '折叠' : '展开' }}</el-button>
|
||||||
:model="queryParams"
|
</div>
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
<!-- 卡片列表 -->
|
||||||
label-width="68px"
|
<div class="mobile-list" v-loading="loading">
|
||||||
>
|
<div v-if="list.length === 0 && !loading" class="mobile-empty">
|
||||||
<el-form-item label="分类名称" prop="name">
|
<el-empty description="暂无产品分类" />
|
||||||
<el-input
|
</div>
|
||||||
v-model="queryParams.name"
|
<div v-for="item in flatList" :key="item.id" class="mobile-card" @click="handleCardClick(item)" :style="{ marginLeft: (item.level || 0) * 12 + 'px' }">
|
||||||
placeholder="请输入分类名称"
|
<div class="mobile-card__header">
|
||||||
clearable
|
<span class="mobile-card__name">
|
||||||
@keyup.enter="handleQuery"
|
<span v-if="item.level > 0" class="mobile-card__indent">└─</span>
|
||||||
class="!w-240px"
|
{{ item.name || '-' }}
|
||||||
/>
|
</span>
|
||||||
</el-form-item>
|
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="item.status" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__body">
|
||||||
|
<div class="mobile-card__row">
|
||||||
|
<span class="mobile-card__label">编码</span>
|
||||||
|
<span class="mobile-card__value">{{ item.code || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__row">
|
||||||
|
<span class="mobile-card__label">排序</span>
|
||||||
|
<span class="mobile-card__value">{{ item.sort || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__row">
|
||||||
|
<span class="mobile-card__label">创建时间</span>
|
||||||
|
<span class="mobile-card__value">{{ formatDate(item.createTime) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__footer">
|
||||||
|
<el-button size="small" type="primary" @click.stop="openForm('update', item.id)" v-hasPermi="['erp:product-category:update']">编辑</el-button>
|
||||||
|
<el-button size="small" type="danger" @click.stop="handleDelete(item.id)" v-hasPermi="['erp:product-category:delete']">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选抽屉 -->
|
||||||
|
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="50%">
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
<el-form-item label="开启状态" prop="status">
|
<el-form-item label="开启状态" prop="status">
|
||||||
<el-select
|
<el-select v-model="queryParams.status" placeholder="请选择开启状态" clearable style="width:100%">
|
||||||
v-model="queryParams.status"
|
|
||||||
placeholder="请选择开启状态"
|
|
||||||
clearable
|
|
||||||
class="!w-240px"
|
|
||||||
>
|
|
||||||
<el-option
|
<el-option
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
:key="dict.value"
|
:key="dict.value"
|
||||||
@@ -34,24 +65,34 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
|
<el-form-item label="分类名称" prop="name">
|
||||||
|
<el-input v-model="queryParams.name" 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>
|
<el-form-item>
|
||||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
<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="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||||
<el-button
|
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:product-category:create']">
|
||||||
type="primary"
|
|
||||||
plain
|
|
||||||
@click="openForm('create')"
|
|
||||||
v-hasPermi="['erp:product-category:create']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:product-category:export']">
|
||||||
type="success"
|
|
||||||
plain
|
|
||||||
@click="handleExport"
|
|
||||||
:loading="exportLoading"
|
|
||||||
v-hasPermi="['erp:product-category:export']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button type="danger" plain @click="toggleExpandAll">
|
<el-button type="danger" plain @click="toggleExpandAll">
|
||||||
@@ -63,16 +104,7 @@
|
|||||||
|
|
||||||
<!-- 列表 -->
|
<!-- 列表 -->
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" row-key="id" :default-expand-all="isExpandAll" v-if="refreshTable" @row-click="handleRowClick">
|
||||||
v-loading="loading"
|
|
||||||
:data="list"
|
|
||||||
:stripe="true"
|
|
||||||
:show-overflow-tooltip="true"
|
|
||||||
row-key="id"
|
|
||||||
:default-expand-all="isExpandAll"
|
|
||||||
v-if="refreshTable"
|
|
||||||
@row-click="handleRowClick"
|
|
||||||
>
|
|
||||||
<el-table-column label="编码" align="center" prop="code" />
|
<el-table-column label="编码" align="center" prop="code" />
|
||||||
<el-table-column label="名称" align="center" prop="name" />
|
<el-table-column label="名称" align="center" prop="name" />
|
||||||
<el-table-column label="排序" align="center" prop="sort" />
|
<el-table-column label="排序" align="center" prop="sort" />
|
||||||
@@ -81,64 +113,45 @@
|
|||||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" sortable />
|
||||||
label="创建时间"
|
|
||||||
align="center"
|
|
||||||
prop="createTime"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
width="180px"
|
|
||||||
sortable
|
|
||||||
/>
|
|
||||||
<el-table-column label="操作" align="center">
|
<el-table-column label="操作" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:product-category:update']">编辑</el-button>
|
||||||
link
|
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:product-category:delete']">删除</el-button>
|
||||||
type="primary"
|
|
||||||
@click="openForm('update', scope.row.id)"
|
|
||||||
v-hasPermi="['erp:product-category:update']"
|
|
||||||
>
|
|
||||||
编辑
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
@click="handleDelete(scope.row.id)"
|
|
||||||
v-hasPermi="['erp:product-category:delete']"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
|
||||||
<Pagination
|
|
||||||
:total="total"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ProductCategoryForm ref="formRef" @success="getList" />
|
<ProductCategoryForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import { handleTree } from '@/utils/tree'
|
import { handleTree } from '@/utils/tree'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
|
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
|
||||||
import ProductCategoryForm from './ProductCategoryForm.vue'
|
import ProductCategoryForm from './ProductCategoryForm.vue'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 产品分类 列表 */
|
/** ERP 产品分类 列表 */
|
||||||
defineOptions({ name: 'ErpProductCategory' })
|
defineOptions({ name: 'ErpProductCategory' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ProductCategoryVO[]>([]) // 列表的数据
|
const list = ref<ProductCategoryVO[]>([]) // 列表的数据
|
||||||
|
const flatList = ref<ProductCategoryVO[]>([]) // 扁平化列表
|
||||||
|
const filterVisible = ref(false) // 筛选抽屉
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
name: undefined,
|
name: undefined,
|
||||||
status: undefined
|
status: undefined
|
||||||
@@ -152,11 +165,27 @@ const getList = async () => {
|
|||||||
try {
|
try {
|
||||||
const data = await ProductCategoryApi.getProductCategoryList(queryParams)
|
const data = await ProductCategoryApi.getProductCategoryList(queryParams)
|
||||||
list.value = handleTree(data, 'id', 'parentId')
|
list.value = handleTree(data, 'id', 'parentId')
|
||||||
|
flattenTree()
|
||||||
} finally {
|
} finally {
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 扁平化树形结构 */
|
||||||
|
const flattenTree = () => {
|
||||||
|
const result: any[] = []
|
||||||
|
const flatten = (nodes: any[], level = 0) => {
|
||||||
|
nodes.forEach(node => {
|
||||||
|
result.push({ ...node, level })
|
||||||
|
if (node.children && node.children.length > 0 && isExpandAll.value) {
|
||||||
|
flatten(node.children, level + 1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
flatten(list.value)
|
||||||
|
flatList.value = result
|
||||||
|
}
|
||||||
|
|
||||||
/** 搜索按钮操作 */
|
/** 搜索按钮操作 */
|
||||||
const handleQuery = () => {
|
const handleQuery = () => {
|
||||||
queryParams.pageNo = 1
|
queryParams.pageNo = 1
|
||||||
@@ -165,7 +194,13 @@ const handleQuery = () => {
|
|||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value?.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 筛选确认 */
|
||||||
|
const handleFilterConfirm = () => {
|
||||||
|
filterVisible.value = false
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -175,10 +210,16 @@ const openForm = (type: string, id?: number) => {
|
|||||||
formRef.value.open(type, id)
|
formRef.value.open(type, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 行点击操作 */
|
/** 卡片点击 */
|
||||||
const handleRowClick = (row: ProductCategoryVO) => {
|
const handleCardClick = (row: ProductCategoryVO) => {
|
||||||
openForm('update', row.id)
|
openForm('update', row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 格式化日期 */
|
||||||
|
const formatDate = (date: any) => {
|
||||||
|
if (!date) return '-'
|
||||||
|
return dateFormatter({ cellValue: date })
|
||||||
|
}
|
||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (id: number) => {
|
const handleDelete = async (id: number) => {
|
||||||
try {
|
try {
|
||||||
@@ -209,16 +250,53 @@ const handleExport = async () => {
|
|||||||
|
|
||||||
/** 展开/折叠操作 */
|
/** 展开/折叠操作 */
|
||||||
const isExpandAll = ref(true) // 是否展开,默认全部展开
|
const isExpandAll = ref(true) // 是否展开,默认全部展开
|
||||||
const refreshTable = ref(true) // 重新渲染表格状态
|
const refreshTable = ref(true) // PC端重新渲染表格状态
|
||||||
const toggleExpandAll = async () => {
|
const toggleExpandAll = async () => {
|
||||||
refreshTable.value = false
|
|
||||||
isExpandAll.value = !isExpandAll.value
|
isExpandAll.value = !isExpandAll.value
|
||||||
|
// 移动端
|
||||||
|
flattenTree()
|
||||||
|
// PC端
|
||||||
|
refreshTable.value = false
|
||||||
await nextTick()
|
await nextTick()
|
||||||
refreshTable.value = true
|
refreshTable.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** PC端行点击操作 */
|
||||||
|
const handleRowClick = (row: ProductCategoryVO) => {
|
||||||
|
openForm('update', row.id)
|
||||||
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-product-category {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.mobile-header {
|
||||||
|
display: flex; gap: 8px; align-items: center; margin-bottom: 12px;
|
||||||
|
&__search { flex: 1; }
|
||||||
|
&__actions { display: flex; gap: 4px; flex-shrink: 0; }
|
||||||
|
}
|
||||||
|
.mobile-quick-actions {
|
||||||
|
display: flex; gap: 8px; margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mobile-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.mobile-empty { padding: 40px 0; }
|
||||||
|
.mobile-card {
|
||||||
|
background: #fff; border-radius: 10px; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||||
|
&__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||||
|
&__name { font-weight: 600; font-size: 15px; color: #303133; }
|
||||||
|
&__indent { color: #909399; margin-right: 4px; }
|
||||||
|
&__body { font-size: 13px; }
|
||||||
|
&__row { display: flex; justify-content: space-between; padding: 3px 0; }
|
||||||
|
&__label { color: #909399; flex-shrink: 0; margin-right: 12px; }
|
||||||
|
&__value { color: #606266; text-align: right; }
|
||||||
|
&__footer { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; padding-top: 10px; border-top: 1px solid #f0f0f0; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,149 +1,66 @@
|
|||||||
<!-- ERP 产品的新增/修改 -->
|
|
||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<!-- 移动端使用抽屉 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
:rules="formRules"
|
<div class="mobile-form-section">
|
||||||
label-width="100px"
|
<div class="mobile-form-section__title">基础信息</div>
|
||||||
v-loading="formLoading"
|
<el-form-item label="产品名称" prop="name"><el-input v-model="formData.name" placeholder="请输入产品名称" clearable /></el-form-item>
|
||||||
>
|
<el-form-item label="产品条码" prop="barCode"><el-input v-model="formData.barCode" placeholder="请输入产品条码" clearable /></el-form-item>
|
||||||
|
<el-form-item label="产品分类" prop="categoryId"><el-tree-select v-model="formData.categoryId" :data="categoryList" :props="defaultProps" check-strictly default-expand-all placeholder="请选择产品分类" /></el-form-item>
|
||||||
|
<el-form-item label="产品单位" prop="unitId"><el-select v-model="formData.unitId" clearable placeholder="请选择产品单位"><el-option v-for="unit in unitList" :key="unit.id" :label="unit.name" :value="unit.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="产品规格" prop="standard"><el-input v-model="formData.standard" placeholder="请输入产品规格" clearable /></el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form-section">
|
||||||
|
<div class="mobile-form-section__title">价格信息</div>
|
||||||
|
<el-form-item label="采购价格(元)" prop="purchasePrice"><el-input-number v-model="formData.purchasePrice" placeholder="请输入采购价格" :min="0" :precision="4" controls-position="right" /></el-form-item>
|
||||||
|
<el-form-item label="销售价格(元)" prop="salePrice"><el-input-number v-model="formData.salePrice" placeholder="请输入销售价格" :min="0" :precision="4" controls-position="right" /></el-form-item>
|
||||||
|
<el-form-item label="货值(元)" prop="minPrice"><el-input-number v-model="formData.minPrice" placeholder="请输入最低价格" :min="0" :precision="4" controls-position="right" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form-section">
|
||||||
|
<div class="mobile-form-section__title">库存信息</div>
|
||||||
|
<el-form-item label="库存预警数量" prop="stockAlertCount"><el-input-number v-model="formData.stockAlertCount" placeholder="请输入库存预警数量" :min="0" :precision="2" controls-position="right" /></el-form-item>
|
||||||
|
<el-form-item label="保质期天数" prop="expiryDay"><el-input-number v-model="formData.expiryDay" placeholder="请输入保质期天数" :min="0" :precision="0" controls-position="right" /></el-form-item>
|
||||||
|
<el-form-item label="重量(kg)" prop="weight"><el-input-number v-model="formData.weight" placeholder="请输入重量" :min="0" controls-position="right" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form-section">
|
||||||
|
<div class="mobile-form-section__title">供应商信息</div>
|
||||||
|
<el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable placeholder="请选择供应商"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form-section">
|
||||||
|
<div class="mobile-form-section__title">其他信息</div>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" :rows="3" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form-section">
|
||||||
|
<div class="mobile-form-section__title">状态信息</div>
|
||||||
|
<el-form-item label="开启状态" prop="status"><el-radio-group v-model="formData.status"><el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio></el-radio-group></el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端使用对话框 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="12">
|
<el-col :span="12"><el-form-item label="名称" prop="name"><el-input v-model="formData.name" placeholder="请输入名称" /></el-form-item></el-col>
|
||||||
<el-form-item label="名称" prop="name">
|
<el-col :span="12"><el-form-item label="条码" prop="barCode"><el-input v-model="formData.barCode" placeholder="请输入条码" /></el-form-item></el-col>
|
||||||
<el-input v-model="formData.name" placeholder="请输入名称" />
|
<el-col :span="12"><el-form-item label="分类" prop="categoryId"><el-tree-select v-model="formData.categoryId" :data="categoryList" :props="defaultProps" check-strictly default-expand-all placeholder="请选择分类" class="w-1/1" /></el-form-item></el-col>
|
||||||
</el-form-item>
|
<el-col :span="12"><el-form-item label="单位" prop="unitId"><el-select v-model="formData.unitId" clearable placeholder="请选择单位" class="w-1/1"><el-option v-for="unit in unitList" :key="unit.id" :label="unit.name" :value="unit.id" /></el-select></el-form-item></el-col>
|
||||||
</el-col>
|
<el-col :span="12"><el-form-item label="状态" prop="status"><el-radio-group v-model="formData.status"><el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio></el-radio-group></el-form-item></el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12"><el-form-item label="规格" prop="standard"><el-input v-model="formData.standard" placeholder="请输入规格" /></el-form-item></el-col>
|
||||||
<el-form-item label="条码" prop="barCode">
|
<el-col :span="12"><el-form-item label="保质期天数" prop="expiryDay"><el-input-number v-model="formData.expiryDay" placeholder="请输入保质期天数" :min="0" :precision="0" class="!w-1/1" /></el-form-item></el-col>
|
||||||
<el-input v-model="formData.barCode" placeholder="请输入条码" />
|
<el-col :span="12"><el-form-item label="重量(kg)" prop="weight"><el-input-number v-model="formData.weight" placeholder="请输入重量(kg)" :min="0" class="!w-1/1" /></el-form-item></el-col>
|
||||||
</el-form-item>
|
<el-col :span="12"><el-form-item label="采购价格" prop="purchasePrice"><el-input-number v-model="formData.purchasePrice" placeholder="请输入采购价格,单位:元" :min="0" :precision="4" class="!w-1/1" /></el-form-item></el-col>
|
||||||
</el-col>
|
<el-col :span="12"><el-form-item label="销售价格" prop="salePrice"><el-input-number v-model="formData.salePrice" placeholder="请输入销售价格,单位:元" :min="0" :precision="4" class="!w-1/1" /></el-form-item></el-col>
|
||||||
<el-col :span="12">
|
<el-col :span="12"><el-form-item label="货值" prop="minPrice"><el-input-number v-model="formData.minPrice" placeholder="请输入最低价格,单位:元" :min="0" :precision="4" class="!w-1/1" /></el-form-item></el-col>
|
||||||
<el-form-item label="分类" prop="categoryId">
|
<el-col :span="12"><el-form-item label="库存预警" prop="stockAlertCount"><el-input-number v-model="formData.stockAlertCount" placeholder="请输入库存预警数量" :min="0" :precision="2" class="!w-1/1" /></el-form-item></el-col>
|
||||||
<el-tree-select
|
<el-col :span="12"><el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable placeholder="请选择供应商" class="w-1/1"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
v-model="formData.categoryId"
|
<el-col :span="24"><el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" /></el-form-item></el-col>
|
||||||
:data="categoryList"
|
|
||||||
:props="defaultProps"
|
|
||||||
check-strictly
|
|
||||||
default-expand-all
|
|
||||||
placeholder="请选择分类"
|
|
||||||
class="w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="单位" prop="unitId">
|
|
||||||
<el-select v-model="formData.unitId" clearable placeholder="请选择单位" class="w-1/1">
|
|
||||||
<el-option
|
|
||||||
v-for="unit in unitList"
|
|
||||||
:key="unit.id"
|
|
||||||
:label="unit.name"
|
|
||||||
:value="unit.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="状态" prop="status">
|
|
||||||
<el-radio-group v-model="formData.status">
|
|
||||||
<el-radio
|
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
|
||||||
:key="dict.value"
|
|
||||||
:value="dict.value"
|
|
||||||
>
|
|
||||||
{{ dict.label }}
|
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="规格" prop="standard">
|
|
||||||
<el-input v-model="formData.standard" placeholder="请输入规格" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="保质期天数" prop="expiryDay">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.expiryDay"
|
|
||||||
placeholder="请输入保质期天数"
|
|
||||||
:min="0"
|
|
||||||
:precision="0"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="重量(kg)" prop="weight">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.weight"
|
|
||||||
placeholder="请输入重量(kg)"
|
|
||||||
:min="0"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="采购价格" prop="purchasePrice">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.purchasePrice"
|
|
||||||
placeholder="请输入采购价格,单位:元"
|
|
||||||
:min="0"
|
|
||||||
:precision="4"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="销售价格" prop="salePrice">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.salePrice"
|
|
||||||
placeholder="请输入销售价格,单位:元"
|
|
||||||
:min="0"
|
|
||||||
:precision="4"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="货值" prop="minPrice">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.minPrice"
|
|
||||||
placeholder="请输入最低价格,单位:元"
|
|
||||||
:min="0"
|
|
||||||
:precision="4"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="库存预警" prop="stockAlertCount">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.stockAlertCount"
|
|
||||||
placeholder="请输入库存预警数量"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="12">
|
|
||||||
<el-form-item label="供应商" prop="supplierId">
|
|
||||||
<el-select v-model="formData.supplierId" clearable filterable placeholder="请选择供应商" class="w-1/1">
|
|
||||||
<el-option
|
|
||||||
v-for="item in supplierList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="24">
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -153,6 +70,7 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
|
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
|
||||||
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
|
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
|
||||||
@@ -160,10 +78,14 @@ import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
|||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
import { defaultProps, handleTree } from '@/utils/tree'
|
import { defaultProps, handleTree } from '@/utils/tree'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 产品 表单 */
|
/** ERP 产品 表单 */
|
||||||
defineOptions({ name: 'ProductForm' })
|
defineOptions({ name: 'ProductForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -269,3 +191,54 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.mobile-form-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form-section__title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -12px -12px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
:deep(.el-input),
|
||||||
|
:deep(.el-select),
|
||||||
|
:deep(.el-input-number),
|
||||||
|
:deep(.el-tree-select) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,61 +1,73 @@
|
|||||||
<!-- ERP 产品列表 -->
|
|
||||||
<template>
|
<template>
|
||||||
<doc-alert title="【产品】产品信息、分类、单位" url="https://doc.iocoder.cn/erp/product/" />
|
<!-- 移动端布局 -->
|
||||||
|
<div v-if="isMobile" class="mobile-product">
|
||||||
|
<div class="mobile-header">
|
||||||
|
<div class="mobile-header__search">
|
||||||
|
<el-input v-model="queryParams.name" placeholder="搜索产品名称" clearable @keyup.enter="handleQuery" :prefix-icon="Search" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-header__actions">
|
||||||
|
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
||||||
|
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:product:create']" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-quick-actions">
|
||||||
|
<el-button size="small" type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:product:export']">导出</el-button>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-list" v-loading="loading">
|
||||||
|
<div v-if="list.length === 0 && !loading" class="mobile-empty"><el-empty description="暂无产品" /></div>
|
||||||
|
<div v-for="item in list" :key="item.id" class="mobile-card" @click="handleCardClick(item)">
|
||||||
|
<div class="mobile-card__header">
|
||||||
|
<span class="mobile-card__name">{{ item.name || '-' }}</span>
|
||||||
|
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="item.status" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__body">
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">条码</span><span class="mobile-card__value">{{ item.barCode || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">规格</span><span class="mobile-card__value">{{ item.standard || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">分类</span><span class="mobile-card__value">{{ item.categoryName || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">单位</span><span class="mobile-card__value">{{ item.unitName || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">供应商</span><span class="mobile-card__value">{{ item.supplierName || '-' }}</span></div>
|
||||||
|
<div class="mobile-card__row"><span class="mobile-card__label">货值</span><span class="mobile-card__value">{{ formatPrice(item.minPrice) }}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__footer">
|
||||||
|
<el-button size="small" type="primary" @click.stop="openForm('update', item.id)" v-hasPermi="['erp:product:update']" :disabled="isProtectedProduct(item.name)">编辑</el-button>
|
||||||
|
<el-button size="small" type="danger" @click.stop="handleDelete(item.id)" v-hasPermi="['erp:product:delete']" :disabled="isProtectedProduct(item.name)">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-pagination" v-if="total > 0">
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" />
|
||||||
|
</div>
|
||||||
|
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="50%">
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
|
<el-form-item label="产品分类" prop="categoryId">
|
||||||
|
<el-tree-select v-model="queryParams.categoryId" :data="categoryList" :props="defaultProps" check-strictly default-expand-all placeholder="请选择分类" style="width:100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
<el-form
|
|
||||||
class="-mb-15px"
|
|
||||||
:model="queryParams"
|
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="名称" prop="name">
|
<el-form-item label="名称" prop="name">
|
||||||
<el-input
|
<el-input v-model="queryParams.name" placeholder="请输入名称" clearable @keyup.enter="handleQuery" class="!w-240px" />
|
||||||
v-model="queryParams.name"
|
|
||||||
placeholder="请输入名称"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="分类" prop="categoryId">
|
<el-form-item label="分类" prop="categoryId">
|
||||||
<el-tree-select
|
<el-tree-select v-model="queryParams.categoryId" :data="categoryList" :props="defaultProps" check-strictly default-expand-all placeholder="请输入分类" class="!w-240px" />
|
||||||
v-model="queryParams.categoryId"
|
|
||||||
:data="categoryList"
|
|
||||||
:props="defaultProps"
|
|
||||||
check-strictly
|
|
||||||
default-expand-all
|
|
||||||
placeholder="请输入分类"
|
|
||||||
class="!w-240px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
<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="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||||
<el-button
|
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:product:create']"><Icon icon="ep:plus" class="mr-5px" /> 新增</el-button>
|
||||||
type="primary"
|
<el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:product:export']"><Icon icon="ep:download" class="mr-5px" /> 导出</el-button>
|
||||||
plain
|
|
||||||
@click="openForm('create')"
|
|
||||||
v-hasPermi="['erp:product:create']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
plain
|
|
||||||
@click="handleExport"
|
|
||||||
:loading="exportLoading"
|
|
||||||
v-hasPermi="['erp:product:export']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @row-click="handleRowClick">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @row-click="handleRowClick">
|
||||||
<el-table-column label="条码" align="center" prop="barCode" />
|
<el-table-column label="条码" align="center" prop="barCode" />
|
||||||
@@ -64,93 +76,53 @@
|
|||||||
<el-table-column label="分类" align="center" prop="categoryName" />
|
<el-table-column label="分类" align="center" prop="categoryName" />
|
||||||
<el-table-column label="单位" align="center" prop="unitName" />
|
<el-table-column label="单位" align="center" prop="unitName" />
|
||||||
<el-table-column label="供应商" align="center" prop="supplierName" />
|
<el-table-column label="供应商" align="center" prop="supplierName" />
|
||||||
<!-- <el-table-column-->
|
<el-table-column label="货值" align="center" prop="minPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
<!-- label="采购价格"-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- prop="purchasePrice"-->
|
|
||||||
<!-- :formatter="erpPriceTableColumnFormatter"-->
|
|
||||||
<!-- />-->
|
|
||||||
<!-- <el-table-column-->
|
|
||||||
<!-- label="销售价格"-->
|
|
||||||
<!-- align="center"-->
|
|
||||||
<!-- prop="salePrice"-->
|
|
||||||
<!-- :formatter="erpPriceTableColumnFormatter"-->
|
|
||||||
<!-- />-->
|
|
||||||
<el-table-column
|
|
||||||
label="货值"
|
|
||||||
align="center"
|
|
||||||
prop="minPrice"
|
|
||||||
:formatter="erpPriceTableColumnFormatter"
|
|
||||||
/>
|
|
||||||
<el-table-column label="库存预警" align="center" prop="stockAlertCount" />
|
<el-table-column label="库存预警" align="center" prop="stockAlertCount" />
|
||||||
<el-table-column label="状态" align="center" prop="status">
|
<el-table-column label="状态" align="center" prop="status">
|
||||||
<template #default="scope">
|
<template #default="scope"><dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" /></template>
|
||||||
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="scope.row.status" />
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column
|
<el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" sortable />
|
||||||
label="创建时间"
|
|
||||||
align="center"
|
|
||||||
prop="createTime"
|
|
||||||
:formatter="dateFormatter"
|
|
||||||
width="180px"
|
|
||||||
sortable
|
|
||||||
/>
|
|
||||||
<el-table-column label="操作" align="center" width="110">
|
<el-table-column label="操作" align="center" width="110">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:product:update']" :disabled="isProtectedProduct(scope.row.name)">编辑</el-button>
|
||||||
link
|
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:product:delete']" :disabled="isProtectedProduct(scope.row.name)">删除</el-button>
|
||||||
type="primary"
|
|
||||||
@click="openForm('update', scope.row.id)"
|
|
||||||
v-hasPermi="['erp:product:update']"
|
|
||||||
:disabled="scope.row.name === '番茄' || scope.row.name === '甜菊糖' || scope.row.name === '番茄酱' ||scope.row.name === '甜叶菊'"
|
|
||||||
>
|
|
||||||
编辑
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
@click="handleDelete(scope.row.id)"
|
|
||||||
v-hasPermi="['erp:product:delete']"
|
|
||||||
:disabled="scope.row.name === '番茄' || scope.row.name === '甜菊糖' || scope.row.name === '番茄酱' ||scope.row.name === '甜叶菊'"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
<Pagination
|
|
||||||
:total="total"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ProductForm ref="formRef" @success="getList" />
|
<ProductForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
|
import { ProductCategoryApi, ProductCategoryVO } from '@/api/erp/product/category'
|
||||||
import ProductForm from './ProductForm.vue'
|
import ProductForm from './ProductForm.vue'
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
import { defaultProps, handleTree } from '@/utils/tree'
|
import { defaultProps, handleTree } from '@/utils/tree'
|
||||||
import { erpPriceTableColumnFormatter } from '@/utils'
|
import { erpPriceTableColumnFormatter } from '@/utils'
|
||||||
|
|
||||||
/** ERP 产品列表 */
|
/** ERP 产品列表 */
|
||||||
defineOptions({ name: 'ErpProduct' })
|
defineOptions({ name: 'ErpProduct' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ProductVO[]>([]) // 列表的数据
|
const list = ref<ProductVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
const filterVisible = ref(false) // 筛选抽屉
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@@ -181,16 +153,37 @@ const handleQuery = () => {
|
|||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value?.resetFields()
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
/** 行点击操作 */
|
|
||||||
const handleRowClick = (row: ProductVO) => {
|
/** 筛选确认 */
|
||||||
if (row.name === '番茄' || row.name === '甜菊糖' || row.name === '番茄酱' || row.name === '甜叶菊'){
|
const handleFilterConfirm = () => {
|
||||||
return;
|
filterVisible.value = false
|
||||||
}else {
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 判断是否为受保护产品 */
|
||||||
|
const isProtectedProduct = (name: string) => {
|
||||||
|
return name === '番茄' || name === '甜菊糖' || name === '番茄酱' || name === '甜叶菊'
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 卡片点击(移动端) */
|
||||||
|
const handleCardClick = (row: ProductVO) => {
|
||||||
|
if (isProtectedProduct(row.name)) return
|
||||||
openForm('update', row.id)
|
openForm('update', row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 行点击(PC端) */
|
||||||
|
const handleRowClick = (row: ProductVO) => {
|
||||||
|
if (isProtectedProduct(row.name)) return
|
||||||
|
openForm('update', row.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 格式化价格 */
|
||||||
|
const formatPrice = (price: any) => {
|
||||||
|
if (!price) return '-'
|
||||||
|
return erpPriceTableColumnFormatter({ cellValue: price })
|
||||||
}
|
}
|
||||||
/** 添加/修改操作 */
|
/** 添加/修改操作 */
|
||||||
const formRef = ref()
|
const formRef = ref()
|
||||||
@@ -234,3 +227,35 @@ onMounted(async () => {
|
|||||||
categoryList.value = handleTree(categoryData, 'id', 'parentId')
|
categoryList.value = handleTree(categoryData, 'id', 'parentId')
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-product {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.mobile-header {
|
||||||
|
display: flex; gap: 8px; align-items: center; margin-bottom: 12px;
|
||||||
|
&__search { flex: 1; }
|
||||||
|
&__actions { display: flex; gap: 4px; flex-shrink: 0; }
|
||||||
|
}
|
||||||
|
.mobile-quick-actions {
|
||||||
|
display: flex; gap: 8px; margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mobile-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.mobile-empty { padding: 40px 0; }
|
||||||
|
.mobile-card {
|
||||||
|
background: #fff; border-radius: 10px; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||||
|
&__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||||
|
&__name { font-weight: 600; font-size: 15px; color: #303133; }
|
||||||
|
&__body { font-size: 13px; }
|
||||||
|
&__row { display: flex; justify-content: space-between; padding: 3px 0; }
|
||||||
|
&__label { color: #909399; flex-shrink: 0; margin-right: 12px; }
|
||||||
|
&__value { color: #606266; text-align: right; }
|
||||||
|
&__footer { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; padding-top: 10px; border-top: 1px solid #f0f0f0; }
|
||||||
|
}
|
||||||
|
.mobile-pagination {
|
||||||
|
margin-top: 12px; display: flex; justify-content: center;
|
||||||
|
:deep(.el-pagination) { flex-wrap: wrap; justify-content: center; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible">
|
<!-- 移动端使用抽屉 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
:rules="formRules"
|
<div class="mobile-form-section">
|
||||||
label-width="100px"
|
<div class="mobile-form-section__title">基础信息</div>
|
||||||
v-loading="formLoading"
|
<el-form-item label="单位名称" prop="name"><el-input v-model="formData.name" placeholder="请输入单位名称" clearable /></el-form-item>
|
||||||
>
|
</div>
|
||||||
<el-form-item label="单位名字" prop="name">
|
<div class="mobile-form-section">
|
||||||
<el-input v-model="formData.name" placeholder="请输入单位名字" />
|
<div class="mobile-form-section__title">状态信息</div>
|
||||||
</el-form-item>
|
<el-form-item label="开启状态" prop="status"><el-radio-group v-model="formData.status"><el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio></el-radio-group></el-form-item>
|
||||||
<el-form-item label="单位状态" prop="status">
|
</div>
|
||||||
<el-radio-group v-model="formData.status">
|
</el-form>
|
||||||
<el-radio
|
<div class="mobile-form__footer">
|
||||||
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
:key="dict.value"
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
:value="dict.value"
|
</div>
|
||||||
>
|
</div>
|
||||||
{{ dict.label }}
|
</el-drawer>
|
||||||
</el-radio>
|
|
||||||
</el-radio-group>
|
<!-- PC端使用对话框 -->
|
||||||
</el-form-item>
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
|
||||||
|
<el-form-item label="单位名字" prop="name"><el-input v-model="formData.name" placeholder="请输入单位名字" /></el-form-item>
|
||||||
|
<el-form-item label="单位状态" prop="status"><el-radio-group v-model="formData.status"><el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio></el-radio-group></el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
<el-button @click="submitForm" type="primary" :disabled="formLoading">确 定</el-button>
|
||||||
@@ -29,13 +32,18 @@
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { ProductUnitApi } from '@/api/erp/product/unit'
|
import { ProductUnitApi } from '@/api/erp/product/unit'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 产品单位表单 */
|
/** ERP 产品单位表单 */
|
||||||
defineOptions({ name: 'ProductUnitForm' })
|
defineOptions({ name: 'ProductUnitForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -106,3 +114,52 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.mobile-form-section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form-section__title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + constant(safe-area-inset-bottom));
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -12px -12px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
:deep(.el-input),
|
||||||
|
:deep(.el-select) {
|
||||||
|
width: 100% !important;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,130 +1,91 @@
|
|||||||
<template>
|
<template>
|
||||||
<doc-alert title="【产品】产品信息、分类、单位" url="https://doc.iocoder.cn/erp/product/" />
|
<!-- 移动端布局 -->
|
||||||
|
<div v-if="isMobile" class="mobile-product-unit">
|
||||||
|
<div class="mobile-header">
|
||||||
|
<div class="mobile-header__search"><el-input v-model="queryParams.name" placeholder="搜索单位名称" clearable @keyup.enter="handleQuery" :prefix-icon="Search" /></div>
|
||||||
|
<div class="mobile-header__actions">
|
||||||
|
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
||||||
|
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:product-unit:create']" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-quick-actions"><el-button size="small" type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:product-unit:export']">导出</el-button></div>
|
||||||
|
<div class="mobile-list" v-loading="loading">
|
||||||
|
<div v-if="list.length === 0 && !loading" class="mobile-empty"><el-empty description="暂无产品单位" /></div>
|
||||||
|
<div v-for="item in list" :key="item.id" class="mobile-card" @click="handleCardClick(item)">
|
||||||
|
<div class="mobile-card__header"><span class="mobile-card__name">{{ item.name || '-' }}</span><dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="item.status" /></div>
|
||||||
|
<div class="mobile-card__body"><div class="mobile-card__row"><span class="mobile-card__label">创建时间</span><span class="mobile-card__value">{{ formatDate(item.createTime) }}</span></div></div>
|
||||||
|
<div class="mobile-card__footer">
|
||||||
|
<el-button size="small" type="primary" @click.stop="openForm('update', item.id)" v-hasPermi="['erp:product-unit:update']">编辑</el-button>
|
||||||
|
<el-button size="small" type="danger" @click.stop="handleDelete(item.id)" v-hasPermi="['erp:product-unit:delete']">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-pagination" v-if="total > 0"><Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" /></div>
|
||||||
|
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="50%">
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
|
<el-form-item label="单位状态" prop="status"><el-select v-model="queryParams.status" placeholder="请选择单位状态" clearable style="width:100%"><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>
|
||||||
|
<template #footer><el-button @click="resetQuery">重置</el-button><el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button></template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
<el-form
|
<el-form-item label="单位名字" prop="name"><el-input v-model="queryParams.name" placeholder="请输入单位名字" clearable @keyup.enter="handleQuery" class="!w-240px" /></el-form-item>
|
||||||
class="-mb-15px"
|
<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>
|
||||||
:model="queryParams"
|
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="单位名字" prop="name">
|
|
||||||
<el-input
|
|
||||||
v-model="queryParams.name"
|
|
||||||
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>
|
<el-form-item>
|
||||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
<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="resetQuery"><Icon icon="ep:refresh" class="mr-5px" /> 重置</el-button>
|
||||||
<el-button
|
<el-button type="primary" plain @click="openForm('create')" v-hasPermi="['erp:product-unit:create']"><Icon icon="ep:plus" class="mr-5px" /> 新增</el-button>
|
||||||
type="primary"
|
<el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:product-unit:export']"><Icon icon="ep:download" class="mr-5px" /> 导出</el-button>
|
||||||
plain
|
|
||||||
@click="openForm('create')"
|
|
||||||
v-hasPermi="['erp:product-unit:create']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
type="success"
|
|
||||||
plain
|
|
||||||
@click="handleExport"
|
|
||||||
:loading="exportLoading"
|
|
||||||
v-hasPermi="['erp:product-unit:export']"
|
|
||||||
>
|
|
||||||
<Icon icon="ep:download" class="mr-5px" /> 导出
|
|
||||||
</el-button>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</el-form>
|
</el-form>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<!-- 列表 -->
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @row-click="handleRowClick">
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @row-click="handleRowClick">
|
||||||
<el-table-column label="名字" align="center" prop="name" />
|
<el-table-column label="名字" align="center" prop="name" />
|
||||||
<el-table-column label="状态" align="center" prop="status">
|
<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>
|
||||||
<template #default="scope">
|
<el-table-column label="创建时间" align="center" prop="createTime" :formatter="dateFormatter" width="180px" sortable />
|
||||||
<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"
|
|
||||||
sortable
|
|
||||||
/>
|
|
||||||
<el-table-column label="操作" align="center">
|
<el-table-column label="操作" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<el-button
|
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:product-unit:update']">编辑</el-button>
|
||||||
link
|
<el-button link type="danger" @click="handleDelete(scope.row.id)" v-hasPermi="['erp:product-unit:delete']">删除</el-button>
|
||||||
type="primary"
|
|
||||||
@click="openForm('update', scope.row.id)"
|
|
||||||
v-hasPermi="['erp:product-unit:update']"
|
|
||||||
>
|
|
||||||
编辑
|
|
||||||
</el-button>
|
|
||||||
<el-button
|
|
||||||
link
|
|
||||||
type="danger"
|
|
||||||
@click="handleDelete(scope.row.id)"
|
|
||||||
v-hasPermi="['erp:product-unit:delete']"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</el-button>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
<Pagination
|
|
||||||
:total="total"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<ProductUnitForm ref="formRef" @success="getList" />
|
<ProductUnitForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
||||||
import { dateFormatter } from '@/utils/formatTime'
|
import { dateFormatter } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
|
import { ProductUnitApi, ProductUnitVO } from '@/api/erp/product/unit'
|
||||||
import ProductUnitForm from './ProductUnitForm.vue'
|
import ProductUnitForm from './ProductUnitForm.vue'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 产品单位列表 */
|
/** ERP 产品单位列表 */
|
||||||
defineOptions({ name: 'ErpProductUnit' })
|
defineOptions({ name: 'ErpProductUnit' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<ProductUnitVO[]>([]) // 列表的数据
|
const list = ref<ProductUnitVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
const filterVisible = ref(false) // 筛选抽屉
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@@ -154,7 +115,13 @@ const handleQuery = () => {
|
|||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value?.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 筛选确认 */
|
||||||
|
const handleFilterConfirm = () => {
|
||||||
|
filterVisible.value = false
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -177,11 +144,22 @@ const handleDelete = async (id: number) => {
|
|||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 行点击操作 */
|
/** 卡片点击(移动端) */
|
||||||
|
const handleCardClick = (row: ProductUnitVO) => {
|
||||||
|
openForm('update', row.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 行点击(PC端) */
|
||||||
const handleRowClick = (row: ProductUnitVO) => {
|
const handleRowClick = (row: ProductUnitVO) => {
|
||||||
openForm('update', row.id)
|
openForm('update', row.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 格式化日期 */
|
||||||
|
const formatDate = (date: any) => {
|
||||||
|
if (!date) return '-'
|
||||||
|
return dateFormatter({ cellValue: date })
|
||||||
|
}
|
||||||
|
|
||||||
/** 导出按钮操作 */
|
/** 导出按钮操作 */
|
||||||
const handleExport = async () => {
|
const handleExport = async () => {
|
||||||
try {
|
try {
|
||||||
@@ -202,3 +180,35 @@ onMounted(() => {
|
|||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-product-unit {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
.mobile-header {
|
||||||
|
display: flex; gap: 8px; align-items: center; margin-bottom: 12px;
|
||||||
|
&__search { flex: 1; }
|
||||||
|
&__actions { display: flex; gap: 4px; flex-shrink: 0; }
|
||||||
|
}
|
||||||
|
.mobile-quick-actions {
|
||||||
|
display: flex; gap: 8px; margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
.mobile-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.mobile-empty { padding: 40px 0; }
|
||||||
|
.mobile-card {
|
||||||
|
background: #fff; border-radius: 10px; padding: 14px; box-shadow: 0 1px 4px rgba(0,0,0,0.06);
|
||||||
|
&__header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px; }
|
||||||
|
&__name { font-weight: 600; font-size: 15px; color: #303133; }
|
||||||
|
&__body { font-size: 13px; }
|
||||||
|
&__row { display: flex; justify-content: space-between; padding: 3px 0; }
|
||||||
|
&__label { color: #909399; flex-shrink: 0; margin-right: 12px; }
|
||||||
|
&__value { color: #606266; text-align: right; }
|
||||||
|
&__footer { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 10px; padding-top: 10px; border-top: 1px solid #f0f0f0; }
|
||||||
|
}
|
||||||
|
.mobile-pagination {
|
||||||
|
margin-top: 12px; display: flex; justify-content: center;
|
||||||
|
:deep(.el-pagination) { flex-wrap: wrap; justify-content: center; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,73 +1,63 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog v-model="dialogVisible" :title="dialogTitle" width="100%" fullscreen class="mobile-eval-form-dialog">
|
<!-- 移动端布局 -->
|
||||||
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
<div class="mobile-form-wrapper" v-loading="formLoading">
|
<div class="mobile-form-wrapper" v-loading="formLoading">
|
||||||
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
<div class="mobile-form-section">
|
<div class="mobile-form-section">
|
||||||
<div class="mobile-form-section__title">基本信息</div>
|
<div class="mobile-form-section__title">基本信息</div>
|
||||||
<el-form-item label="供应商" prop="supplierName">
|
<el-form-item label="供应商" prop="supplierName"><el-input v-model="formData.supplierName" disabled /></el-form-item>
|
||||||
<el-input v-model="formData.supplierName" disabled />
|
<el-form-item label="订单单号" prop="orderNo"><el-input v-model="formData.orderNo" disabled /></el-form-item>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="订单单号" prop="orderNo">
|
|
||||||
<el-input v-model="formData.orderNo" disabled />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mobile-form-section">
|
<div class="mobile-form-section">
|
||||||
<div class="mobile-form-section__title">评分标准(满分10分)</div>
|
<div class="mobile-form-section__title">评分标准(满分10分)</div>
|
||||||
<el-form-item label="质量评分" prop="qualityScore">
|
<el-form-item label="质量评分" prop="qualityScore"><div class="rating-container"><el-rate v-model="formData.qualityScore" :max="10" show-score score-template="{value}分" allow-half /><div class="rating-desc">产品质量、规格符合度、缺陷率等</div></div></el-form-item>
|
||||||
<div class="rating-container">
|
<el-form-item label="服务评分" prop="serviceScore"><div class="rating-container"><el-rate v-model="formData.serviceScore" :max="10" show-score score-template="{value}分" allow-half /><div class="rating-desc">售前售后服务、响应速度、专业程度等</div></div></el-form-item>
|
||||||
<el-rate v-model="formData.qualityScore" :max="10" show-score score-template="{value}分" allow-half />
|
<el-form-item label="价格评分" prop="priceScore"><div class="rating-container"><el-rate v-model="formData.priceScore" :max="10" show-score score-template="{value}分" allow-half /><div class="rating-desc">价格合理性、性价比、优惠政策等</div></div></el-form-item>
|
||||||
<div class="rating-desc">产品质量、规格符合度、缺陷率等</div>
|
<el-form-item label="交付评分" prop="deliveryScore"><div class="rating-container"><el-rate v-model="formData.deliveryScore" :max="10" show-score score-template="{value}分" allow-half /><div class="rating-desc">交付及时性、包装质量、物流配送等</div></div></el-form-item>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="服务评分" prop="serviceScore">
|
|
||||||
<div class="rating-container">
|
|
||||||
<el-rate v-model="formData.serviceScore" :max="10" show-score score-template="{value}分" allow-half />
|
|
||||||
<div class="rating-desc">售前售后服务、响应速度、专业程度等</div>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="价格评分" prop="priceScore">
|
|
||||||
<div class="rating-container">
|
|
||||||
<el-rate v-model="formData.priceScore" :max="10" show-score score-template="{value}分" allow-half />
|
|
||||||
<div class="rating-desc">价格合理性、性价比、优惠政策等</div>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="交付评分" prop="deliveryScore">
|
|
||||||
<div class="rating-container">
|
|
||||||
<el-rate v-model="formData.deliveryScore" :max="10" show-score score-template="{value}分" allow-half />
|
|
||||||
<div class="rating-desc">交付及时性、包装质量、物流配送等</div>
|
|
||||||
</div>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mobile-form-section">
|
<div class="mobile-form-section">
|
||||||
<div class="mobile-form-section__title">综合评分</div>
|
<div class="mobile-form-section__title">综合评分</div>
|
||||||
<div class="total-score">
|
<div class="total-score"><span class="score-value">{{ totalScore.toFixed(1) }}分</span><span class="score-level">{{ getScoreLevel(totalScore) }}</span></div>
|
||||||
<span class="score-value">{{ totalScore.toFixed(1) }}分</span>
|
|
||||||
<span class="score-level">{{ getScoreLevel(totalScore) }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="mobile-form-section">
|
<div class="mobile-form-section">
|
||||||
<div class="mobile-form-section__title">评价备注</div>
|
<div class="mobile-form-section__title">评价备注</div>
|
||||||
<el-form-item prop="remark">
|
<el-form-item prop="remark"><el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入评价备注(可选)" maxlength="500" show-word-limit /></el-form-item>
|
||||||
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入评价备注(可选)" maxlength="500" show-word-limit />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm" :loading="formLoading">确 定</el-button>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else v-model="dialogVisible" :title="dialogTitle" width="600px">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
|
||||||
|
<el-form-item label="供应商" prop="supplierName"><el-input v-model="formData.supplierName" disabled /></el-form-item>
|
||||||
|
<el-form-item label="订单单号" prop="orderNo"><el-input v-model="formData.orderNo" disabled /></el-form-item>
|
||||||
|
<el-divider content-position="left">评分标准(满分10分)</el-divider>
|
||||||
|
<el-form-item label="质量评分" prop="qualityScore"><div class="rating-container"><el-rate v-model="formData.qualityScore" :max="10" show-score score-template="{value}分" allow-half /><div class="rating-desc">产品质量、规格符合度、缺陷率等</div></div></el-form-item>
|
||||||
|
<el-form-item label="服务评分" prop="serviceScore"><div class="rating-container"><el-rate v-model="formData.serviceScore" :max="10" show-score score-template="{value}分" allow-half /><div class="rating-desc">售前售后服务、响应速度、专业程度等</div></div></el-form-item>
|
||||||
|
<el-form-item label="价格评分" prop="priceScore"><div class="rating-container"><el-rate v-model="formData.priceScore" :max="10" show-score score-template="{value}分" allow-half /><div class="rating-desc">价格合理性、性价比、优惠政策等</div></div></el-form-item>
|
||||||
|
<el-form-item label="交付评分" prop="deliveryScore"><div class="rating-container"><el-rate v-model="formData.deliveryScore" :max="10" show-score score-template="{value}分" allow-half /><div class="rating-desc">交付及时性、包装质量、物流配送等</div></div></el-form-item>
|
||||||
|
<el-form-item label="综合评分" prop="totalScore"><div class="total-score"><span class="score-value">{{ totalScore.toFixed(1) }}分</span><span class="score-level">{{ getScoreLevel(totalScore) }}</span></div></el-form-item>
|
||||||
|
<el-form-item label="评价备注" prop="remark"><el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入评价备注(可选)" maxlength="500" show-word-limit /></el-form-item>
|
||||||
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<div class="mobile-form-footer">
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
<el-button @click="dialogVisible = false" style="flex:1">取消</el-button>
|
<el-button type="primary" @click="submitForm" :loading="formLoading">确定</el-button>
|
||||||
<el-button type="primary" @click="submitForm" :loading="formLoading" style="flex:1">确定</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { SupplierEvaluationApi, SupplierEvaluationVO } from '@/api/erp/purchase/supplierEvaluation'
|
import { SupplierEvaluationApi, SupplierEvaluationVO } from '@/api/erp/purchase/supplierEvaluation'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
interface SupplierEvaluationForm {
|
interface SupplierEvaluationForm {
|
||||||
id?: number
|
id?: number
|
||||||
@@ -222,10 +212,19 @@ defineExpose({ open })
|
|||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.mobile-form-footer {
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
gap: 12px;
|
gap: 12px;
|
||||||
padding: 0 4px;
|
z-index: 10;
|
||||||
|
margin: 0 -4px -12px;
|
||||||
|
.el-button { flex: 1; height: 40px; font-size: 15px; }
|
||||||
}
|
}
|
||||||
.rating-container {
|
.rating-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -3,7 +3,11 @@
|
|||||||
<!-- 筛选条件 -->
|
<!-- 筛选条件 -->
|
||||||
<div class="mobile-dashboard-filter">
|
<div class="mobile-dashboard-filter">
|
||||||
<el-select v-model="queryParams.supplierId" clearable filterable placeholder="选择供应商" @change="getSupplierStats" size="small" style="flex:1">
|
<el-select v-model="queryParams.supplierId" clearable filterable placeholder="选择供应商" @change="getSupplierStats" size="small" style="flex:1">
|
||||||
<el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
|
<el-option
|
||||||
|
v-for="item in supplierList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id" />
|
||||||
</el-select>
|
</el-select>
|
||||||
<el-button size="small" @click="getAllSuppliersStats">刷新</el-button>
|
<el-button size="small" @click="getAllSuppliersStats">刷新</el-button>
|
||||||
</div>
|
</div>
|
||||||
@@ -88,7 +92,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mobile-distribution__item">
|
<div class="mobile-distribution__item">
|
||||||
<div class="mobile-distribution__header">
|
<div class="mobile-distribution__header">
|
||||||
<span class="mobile-distribution__label">不及格 (<6)</span>
|
<span class="mobile-distribution__label">不及格 (<6)</span>
|
||||||
<span class="mobile-distribution__count">{{ failCount }} 个</span>
|
<span class="mobile-distribution__count">{{ failCount }} 个</span>
|
||||||
</div>
|
</div>
|
||||||
<el-progress :percentage="scoreDistribution.fail" color="#f56c6c" :stroke-width="10" />
|
<el-progress :percentage="scoreDistribution.fail" color="#f56c6c" :stroke-width="10" />
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mobile-evaluation">
|
<!-- 移动端布局 -->
|
||||||
|
<div v-if="isMobile" class="mobile-evaluation">
|
||||||
<!-- 顶部操作栏 -->
|
<!-- 顶部操作栏 -->
|
||||||
<div class="mobile-header">
|
<div class="mobile-header">
|
||||||
<div class="mobile-header__search">
|
<div class="mobile-header__search">
|
||||||
@@ -135,11 +136,96 @@
|
|||||||
<!-- 供应商汇总表弹窗 -->
|
<!-- 供应商汇总表弹窗 -->
|
||||||
<SupplierSummaryTable ref="summaryRef" />
|
<SupplierSummaryTable ref="summaryRef" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<div v-else>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
|
<el-form-item label="供应商" prop="supplierId">
|
||||||
|
<el-select v-model="queryParams.supplierId" clearable filterable placeholder="请选择供应商" class="!w-240px">
|
||||||
|
<el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="采购订单" prop="purchaseOrderId">
|
||||||
|
<el-input v-model="queryParams.purchaseOrderNo" placeholder="请输入采购订单号" clearable @keyup.enter="handleQuery" class="!w-240px" />
|
||||||
|
</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-240px" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="评分范围" prop="scoreRange">
|
||||||
|
<el-select v-model="queryParams.scoreRange" placeholder="请选择评分范围" clearable class="!w-240px">
|
||||||
|
<el-option label="优秀 (9.0-10.0)" value="excellent" />
|
||||||
|
<el-option label="良好 (8.0-8.9)" value="good" />
|
||||||
|
<el-option label="一般 (7.0-7.9)" value="average" />
|
||||||
|
<el-option label="及格 (6.0-6.9)" value="pass" />
|
||||||
|
<el-option label="不及格 (0-5.9)" value="fail" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="创建人" prop="creator">
|
||||||
|
<el-select v-model="queryParams.creator" clearable filterable placeholder="请选择创建人" class="!w-240px">
|
||||||
|
<el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</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 type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:supplier-evaluation:export']"><Icon icon="ep:download" class="mr-5px" /> 导出</el-button>
|
||||||
|
<el-button type="info" plain @click="showStatistics = !showStatistics"><Icon icon="ep:data-analysis" class="mr-5px" /> {{ showStatistics ? '隐藏统计' : '显示统计' }}</el-button>
|
||||||
|
<el-button type="primary" plain @click="openSupplierSummary" v-hasPermi="['erp:supplier-evaluation:query']"><Icon icon="ep:grid" class="mr-5px" /> 供应商汇总表</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 统计信息 -->
|
||||||
|
<ContentWrap v-if="showStatistics">
|
||||||
|
<el-row :gutter="20" class="mb-4">
|
||||||
|
<el-col :span="6"><el-card class="statistics-card"><div class="statistics-content"><div class="statistics-value">{{ statistics.totalEvaluations }}</div><div class="statistics-label">总评价数</div></div><Icon icon="ep:document" class="statistics-icon" /></el-card></el-col>
|
||||||
|
<el-col :span="6"><el-card class="statistics-card"><div class="statistics-content"><div class="statistics-value">{{ statistics.avgTotalScore }}</div><div class="statistics-label">平均总分</div></div><Icon icon="ep:star" class="statistics-icon" /></el-card></el-col>
|
||||||
|
<el-col :span="6"><el-card class="statistics-card"><div class="statistics-content"><div class="statistics-value">{{ statistics.excellentCount }}</div><div class="statistics-label">优秀评价</div></div><Icon icon="ep:trophy" class="statistics-icon" /></el-card></el-col>
|
||||||
|
<el-col :span="6"><el-card class="statistics-card"><div class="statistics-content"><div class="statistics-value">{{ statistics.supplierCount }}</div><div class="statistics-label">评价供应商</div></div><Icon icon="ep:office-building" class="statistics-icon" /></el-card></el-col>
|
||||||
|
</el-row>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange">
|
||||||
|
<el-table-column width="30" label="选择" type="selection" />
|
||||||
|
<el-table-column label="供应商" align="center" prop="supplierName" min-width="120" />
|
||||||
|
<el-table-column label="采购订单号" align="center" prop="orderNo" min-width="140" />
|
||||||
|
<el-table-column label="质量评分" align="center" prop="qualityScore" width="80"><template #default="scope"><el-tag :type="getScoreTagType(scope.row.qualityScore)">{{ scope.row.qualityScore }}分</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="服务评分" align="center" prop="serviceScore" width="80"><template #default="scope"><el-tag :type="getScoreTagType(scope.row.serviceScore)">{{ scope.row.serviceScore }}分</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="价格评分" align="center" prop="priceScore" width="80"><template #default="scope"><el-tag :type="getScoreTagType(scope.row.priceScore)">{{ scope.row.priceScore }}分</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="交付评分" align="center" prop="deliveryScore" width="80"><template #default="scope"><el-tag :type="getScoreTagType(scope.row.deliveryScore)">{{ scope.row.deliveryScore }}分</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="综合评分" align="center" prop="totalScore" width="100"><template #default="scope"><div class="total-score-cell"><el-tag :type="getScoreTagType(scope.row.totalScore)" size="large">{{ scope.row.totalScore }}分</el-tag><div class="score-level">{{ getScoreLevel(scope.row.totalScore) }}</div></div></template></el-table-column>
|
||||||
|
<el-table-column label="评价备注" align="center" prop="remark" min-width="150"><template #default="scope"><span v-if="scope.row.remark">{{ scope.row.remark }}</span><span v-else class="text-gray-400">无备注</span></template></el-table-column>
|
||||||
|
<el-table-column label="评价时间" align="center" prop="createTime" :formatter="dateFormatter2" width="120px" sortable />
|
||||||
|
<el-table-column label="评价人" align="center" prop="creatorName" width="100" />
|
||||||
|
<el-table-column label="操作" align="center" fixed="right" width="160">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link @click="openDetail(scope.row)" v-hasPermi="['erp:supplier-evaluation:query']">详情</el-button>
|
||||||
|
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:supplier-evaluation:update']">编辑</el-button>
|
||||||
|
<el-button link type="danger" @click="handleDelete([scope.row.id])" v-hasPermi="['erp:supplier-evaluation:delete']">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:编辑 -->
|
||||||
|
<SupplierEvaluationForm ref="formRef" @success="getList" />
|
||||||
|
<!-- 详情弹窗 -->
|
||||||
|
<SupplierEvaluationDetail ref="detailRef" />
|
||||||
|
<!-- 供应商汇总表弹窗 -->
|
||||||
|
<SupplierSummaryTable ref="summaryRef" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { Search, Filter, DataAnalysis } from '@element-plus/icons-vue'
|
import { Search, Filter, DataAnalysis } from '@element-plus/icons-vue'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate, dateFormatter2 } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { SupplierEvaluationApi, SupplierEvaluationVO } from '@/api/erp/purchase/supplierEvaluation'
|
import { SupplierEvaluationApi, SupplierEvaluationVO } from '@/api/erp/purchase/supplierEvaluation'
|
||||||
import SupplierEvaluationForm from './SupplierEvaluationForm.vue'
|
import SupplierEvaluationForm from './SupplierEvaluationForm.vue'
|
||||||
@@ -148,10 +234,14 @@ import SupplierSummaryTable from './SupplierSummaryTable.vue'
|
|||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
import { UserVO } from '@/api/system/user'
|
import { UserVO } from '@/api/system/user'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 供应商评价记录列表 */
|
/** ERP 供应商评价记录列表 */
|
||||||
defineOptions({ name: 'ErpSupplierEvaluationList' })
|
defineOptions({ name: 'ErpSupplierEvaluationList' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -267,6 +357,9 @@ const handleExport = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const selectionList = ref<SupplierEvaluationVO[]>([])
|
const selectionList = ref<SupplierEvaluationVO[]>([])
|
||||||
|
const handleSelectionChange = (rows: SupplierEvaluationVO[]) => {
|
||||||
|
selectionList.value = rows
|
||||||
|
}
|
||||||
|
|
||||||
const getScoreTagType = (score: number) => {
|
const getScoreTagType = (score: number) => {
|
||||||
if (score >= 9) return 'success'
|
if (score >= 9) return 'success'
|
||||||
|
|||||||
@@ -1,175 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer
|
<!-- 移动端布局 -->
|
||||||
v-model="dialogVisible"
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
:title="dialogTitle"
|
|
||||||
direction="rtl"
|
|
||||||
size="100%"
|
|
||||||
:close-on-press-escape="true"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
:append-to-body="true"
|
|
||||||
class="mobile-form-drawer"
|
|
||||||
>
|
|
||||||
<div class="mobile-form" v-loading="formLoading">
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
<el-form
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" :disabled="disabled">
|
||||||
ref="formRef"
|
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
label-position="top"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
<!-- 基本信息 -->
|
|
||||||
<div class="mobile-form__section">
|
<div class="mobile-form__section">
|
||||||
<div class="mobile-form__section-title">基本信息</div>
|
<div class="mobile-form__section-title">基本信息</div>
|
||||||
<el-form-item label="入库单号" prop="no">
|
<el-form-item label="入库单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item>
|
||||||
<el-input disabled v-model="formData.no" placeholder="保存时自动生成" />
|
<el-form-item label="入库时间" prop="inTime"><el-date-picker v-model="formData.inTime" type="date" value-format="x" placeholder="选择入库时间" style="width:100%" /></el-form-item>
|
||||||
</el-form-item>
|
<el-form-item label="关联订单" prop="orderNo"><el-input readonly><template #prefix><el-link v-if="formData.orderNo && formData.orderId" type="primary" :underline="false" @click.stop="openPurchaseOrderDetail">{{ formData.orderNo }}</el-link></template><template #append><el-button @click="openPurchaseOrderInEnableList"><Icon icon="ep:search" /></el-button></template></el-input></el-form-item>
|
||||||
<el-form-item label="入库时间" prop="inTime">
|
<el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable disabled placeholder="请选择供应商" style="width:100%"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
<el-date-picker
|
<el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="2" placeholder="请输入备注" /></el-form-item>
|
||||||
v-model="formData.inTime"
|
<el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item>
|
||||||
type="date"
|
|
||||||
value-format="x"
|
|
||||||
placeholder="选择入库时间"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="关联订单" prop="orderNo">
|
|
||||||
<el-input readonly>
|
|
||||||
<template #prefix>
|
|
||||||
<el-link
|
|
||||||
v-if="formData.orderNo && formData.orderId"
|
|
||||||
type="primary"
|
|
||||||
:underline="false"
|
|
||||||
@click.stop="openPurchaseOrderDetail"
|
|
||||||
>
|
|
||||||
{{ formData.orderNo }}
|
|
||||||
</el-link>
|
|
||||||
</template>
|
|
||||||
<template #append>
|
|
||||||
<el-button @click="openPurchaseOrderInEnableList">
|
|
||||||
<Icon icon="ep:search" /> 选择
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="供应商" prop="supplierId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.supplierId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
disabled
|
|
||||||
placeholder="请选择供应商"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in supplierList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
v-model="formData.remark"
|
|
||||||
:rows="2"
|
|
||||||
placeholder="请输入备注"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="附件" prop="fileUrl">
|
|
||||||
<UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 入库产品清单 -->
|
|
||||||
<div class="mobile-form__section">
|
<div class="mobile-form__section">
|
||||||
<div class="mobile-form__section-title">入库产品清单</div>
|
<div class="mobile-form__section-title">入库产品清单</div>
|
||||||
<PurchaseInItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" />
|
<PurchaseInItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 费用信息 -->
|
|
||||||
<div class="mobile-form__section">
|
<div class="mobile-form__section">
|
||||||
<div class="mobile-form__section-title">费用信息</div>
|
<div class="mobile-form__section-title">费用信息</div>
|
||||||
<el-form-item label="优惠率(%)" prop="discountPercent">
|
<el-form-item label="优惠率(%)" prop="discountPercent"><el-input-number v-model="formData.discountPercent" controls-position="right" :min="0" :precision="4" placeholder="请输入优惠率" style="width:100%" /></el-form-item>
|
||||||
<el-input-number
|
<el-form-item label="付款优惠" prop="discountPrice"><el-input disabled v-model="formData.discountPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
v-model="formData.discountPercent"
|
<el-form-item label="优惠后金额"><el-input disabled :model-value="formData.totalPrice - formData.otherPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
controls-position="right"
|
<el-form-item label="其它费用" prop="otherPrice"><el-input-number v-model="formData.otherPrice" controls-position="right" :min="0" :precision="4" placeholder="请输入其它费用" style="width:100%" /></el-form-item>
|
||||||
:min="0"
|
<el-form-item label="结算账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" style="width:100%"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
:precision="4"
|
<el-form-item label="应付金额"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
placeholder="请输入优惠率"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="付款优惠" prop="discountPrice">
|
|
||||||
<el-input
|
|
||||||
disabled
|
|
||||||
v-model="formData.discountPrice"
|
|
||||||
:formatter="erpPriceInputFormatter"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="优惠后金额">
|
|
||||||
<el-input
|
|
||||||
disabled
|
|
||||||
:model-value="formData.totalPrice - formData.otherPrice"
|
|
||||||
:formatter="erpPriceInputFormatter"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="其它费用" prop="otherPrice">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.otherPrice"
|
|
||||||
controls-position="right"
|
|
||||||
:min="0"
|
|
||||||
:precision="4"
|
|
||||||
placeholder="请输入其它费用"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="结算账户" prop="accountId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.accountId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择结算账户"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in accountList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="应付金额">
|
|
||||||
<el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 审核信息 -->
|
|
||||||
<div class="mobile-form__section">
|
<div class="mobile-form__section">
|
||||||
<div class="mobile-form__section-title">审核信息</div>
|
<div class="mobile-form__section-title">审核信息</div>
|
||||||
<el-form-item label="审核状态">
|
<el-form-item label="审核状态"><el-tag :type="formData.status === 10 ? 'info' : (formData.isQualified ? 'success' : 'danger')">{{ formData.status === 10 ? '未审核' : (formData.isQualified ? '已审核' : '不合格') }}</el-tag></el-form-item>
|
||||||
<el-tag
|
<el-form-item label="返回方式" v-if="formData.status !== 10 && !formData.isQualified"><span>{{ RETURN_TYPE_OPTIONS.find(item => item.value === formData.returnType)?.label || '-' }}</span></el-form-item>
|
||||||
:type="formData.status === 10 ? 'info' : (formData.isQualified ? 'success' : 'danger')"
|
<el-form-item label="返回备注" v-if="formData.status !== 10 && !formData.isQualified"><el-input type="textarea" v-model="formData.returnRemark" :rows="3" disabled placeholder="无" /></el-form-item>
|
||||||
>
|
|
||||||
{{ formData.status === 10 ? '未审核' : (formData.isQualified ? '已审核' : '不合格') }}
|
|
||||||
</el-tag>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="返回方式" v-if="formData.status !== 10 && !formData.isQualified">
|
|
||||||
<span>{{ RETURN_TYPE_OPTIONS.find(item => item.value === formData.returnType)?.label || '-' }}</span>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="返回备注" v-if="formData.status !== 10 && !formData.isQualified">
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
v-model="formData.returnRemark"
|
|
||||||
:rows="3"
|
|
||||||
disabled
|
|
||||||
placeholder="无"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<!-- 底部操作按钮 -->
|
|
||||||
<div class="mobile-form__footer">
|
<div class="mobile-form__footer">
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
@@ -177,12 +39,47 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
<!-- 可入库的订单列表 -->
|
<!-- PC端布局 -->
|
||||||
<PurchaseOrderInEnableList
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible" width="1440">
|
||||||
ref="purchaseOrderInEnableListRef"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading" :disabled="disabled">
|
||||||
@success="handlePurchaseOrderChange"
|
<el-row :gutter="20">
|
||||||
/>
|
<el-col :span="8"><el-form-item label="入库单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="入库时间" prop="inTime"><el-date-picker v-model="formData.inTime" type="date" value-format="x" placeholder="选择入库时间" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="关联订单" prop="orderNo"><el-input readonly><template #prefix><el-link v-if="formData.orderNo && formData.orderId" type="primary" :underline="false" @click.stop="openPurchaseOrderDetail" class="order-link">{{ formData.orderNo }}</el-link></template><template #append><el-button @click="openPurchaseOrderInEnableList"><Icon icon="ep:search" /> 选择</el-button></template></el-input></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable disabled placeholder="请选择供应商" class="!w-1/1"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
|
<el-col :span="16"><el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="1" placeholder="请输入备注" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item></el-col>
|
||||||
|
</el-row>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
||||||
|
<el-tab-pane label="入库产品清单" name="item"><PurchaseInItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" /></el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</ContentWrap>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8"><el-form-item label="优惠率(%)" prop="discountPercent"><el-input-number v-model="formData.discountPercent" controls-position="right" :min="0" :precision="4" placeholder="请输入优惠率" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="付款优惠" prop="discountPrice"><el-input disabled v-model="formData.discountPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="优惠后金额"><el-input disabled :model-value="formData.totalPrice - formData.otherPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="其它费用" prop="otherPrice"><el-input-number v-model="formData.otherPrice" controls-position="right" :min="0" :precision="4" placeholder="请输入其它费用" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="结算账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" class="!w-1/1"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="应付金额"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8"><el-form-item label="审核状态"><el-tag :type="formData.status === 10 ? 'info' : (formData.isQualified ? 'success' : 'danger')">{{ formData.status === 10 ? '未审核' : (formData.isQualified ? '已审核' : '不合格') }}</el-tag></el-form-item></el-col>
|
||||||
|
<el-col :span="8" v-if="formData.status !== 10 && !formData.isQualified"><el-form-item label="返回方式"><span>{{ RETURN_TYPE_OPTIONS.find(item => item.value === formData.returnType)?.label || '-' }}</span></el-form-item></el-col>
|
||||||
|
<el-col :span="16" v-if="formData.status !== 10 && !formData.isQualified"><el-form-item label="返回备注"><el-input type="textarea" v-model="formData.returnRemark" :rows="2" disabled placeholder="无" /></el-form-item></el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
|
<el-button @click="handlePrint" type="warning">
|
||||||
|
<Icon icon="ep:printer" class="mr-5px" /> 打 印
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- 可入库的订单列表 -->
|
||||||
|
<PurchaseOrderInEnableList ref="purchaseOrderInEnableListRef" @success="handlePurchaseOrderChange" />
|
||||||
<!-- 采购订单详情弹窗 -->
|
<!-- 采购订单详情弹窗 -->
|
||||||
<PurchaseOrderForm ref="purchaseOrderFormRef" />
|
<PurchaseOrderForm ref="purchaseOrderFormRef" />
|
||||||
</template>
|
</template>
|
||||||
@@ -200,10 +97,14 @@ import { PurchaseOrderVO } from '@/api/erp/purchase/order'
|
|||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
import { ElTag } from 'element-plus'
|
import { ElTag } from 'element-plus'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 销售入库表单 */
|
/** ERP 采购入库表单 */
|
||||||
defineOptions({ name: 'PurchaseInForm' })
|
defineOptions({ name: 'PurchaseInForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -254,7 +155,7 @@ const formRef = ref() // 表单 Ref
|
|||||||
const supplierList = ref<SupplierVO[]>([]) // 供应商列表
|
const supplierList = ref<SupplierVO[]>([]) // 供应商列表
|
||||||
const accountList = ref<AccountVO[]>([]) // 账户列表
|
const accountList = ref<AccountVO[]>([]) // 账户列表
|
||||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
|
const subTabsName = ref('item')
|
||||||
const itemFormRef = ref()
|
const itemFormRef = ref()
|
||||||
|
|
||||||
/** 计算 discountPrice、totalPrice 价格 */
|
/** 计算 discountPrice、totalPrice 价格 */
|
||||||
@@ -394,6 +295,166 @@ const resetForm = () => {
|
|||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 打印当前页面数据 */
|
||||||
|
const handlePrint = () => {
|
||||||
|
// 获取供应商名称
|
||||||
|
const supplier = supplierList.value.find((item) => item.id === formData.value.supplierId)
|
||||||
|
const supplierName = supplier ? supplier.name : ''
|
||||||
|
// 获取结算账户名称
|
||||||
|
const account = accountList.value.find((item) => item.id === formData.value.accountId)
|
||||||
|
const accountName = account ? account.name : ''
|
||||||
|
// 格式化入库时间
|
||||||
|
const inTime = formData.value.inTime
|
||||||
|
? new Date(formData.value.inTime).toLocaleDateString('zh-CN')
|
||||||
|
: ''
|
||||||
|
|
||||||
|
// 构建产品清单表格
|
||||||
|
let itemsTableHtml = ''
|
||||||
|
if (formData.value.items && formData.value.items.length > 0) {
|
||||||
|
// 计算合计
|
||||||
|
const totalCount = formData.value.items.reduce((sum, item) => sum + (item.count || 0), 0)
|
||||||
|
const totalProductPrice = formData.value.items.reduce((sum, item) => sum + (item.totalProductPrice || 0), 0)
|
||||||
|
const totalTaxPrice = formData.value.items.reduce((sum, item) => sum + (item.taxPrice || 0), 0)
|
||||||
|
const totalPriceSum = formData.value.items.reduce((sum, item) => sum + (item.totalPrice || 0), 0)
|
||||||
|
|
||||||
|
itemsTableHtml = `
|
||||||
|
<table border="1" cellpadding="6" cellspacing="0" style="width:100%; border-collapse: collapse; margin-top: 10px; font-size: 12px;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background-color: #f5f5f5;">
|
||||||
|
<th>序号</th>
|
||||||
|
<th>产品名称</th>
|
||||||
|
<th>产品分类</th>
|
||||||
|
<th>库存</th>
|
||||||
|
<th>条码</th>
|
||||||
|
<th>单位</th>
|
||||||
|
<th>数量</th>
|
||||||
|
<th>产品单价</th>
|
||||||
|
<th>金额</th>
|
||||||
|
<th>税率(%)</th>
|
||||||
|
<th>税额</th>
|
||||||
|
<th>税额合计</th>
|
||||||
|
<th>备注</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${formData.value.items
|
||||||
|
.map(
|
||||||
|
(item, index) => `
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: center;">${index + 1}</td>
|
||||||
|
<td>${item.productName || ''}</td>
|
||||||
|
<td>${item.productCategoryName || ''}</td>
|
||||||
|
<td style="text-align: right;">${item.stockCount || 0}</td>
|
||||||
|
<td>${item.productBarCode || ''}</td>
|
||||||
|
<td style="text-align: center;">${item.productUnitName || ''}</td>
|
||||||
|
<td style="text-align: right;">${item.count || 0}</td>
|
||||||
|
<td style="text-align: right;">${item.productPrice || 0}</td>
|
||||||
|
<td style="text-align: right;">${item.totalProductPrice || 0}</td>
|
||||||
|
<td style="text-align: right;">${item.taxPercent || 0}</td>
|
||||||
|
<td style="text-align: right;">${item.taxPrice || 0}</td>
|
||||||
|
<td style="text-align: right;">${item.totalPrice || 0}</td>
|
||||||
|
<td>${item.remark || ''}</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join('')}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr style="background-color: #f9f9f9; font-weight: bold;">
|
||||||
|
<td style="text-align: center;">合计</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td style="text-align: right;">${totalCount}</td>
|
||||||
|
<td></td>
|
||||||
|
<td style="text-align: right;">${totalProductPrice}</td>
|
||||||
|
<td></td>
|
||||||
|
<td style="text-align: right;">${totalTaxPrice}</td>
|
||||||
|
<td style="text-align: right;">${totalPriceSum}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建打印内容
|
||||||
|
const printContent = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>采购入库单打印</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Microsoft YaHei', Arial, sans-serif; padding: 20px; }
|
||||||
|
h1 { text-align: center; margin-bottom: 20px; }
|
||||||
|
.info-row { display: flex; flex-wrap: wrap; margin-bottom: 10px; }
|
||||||
|
.info-item { width: 33%; margin-bottom: 10px; }
|
||||||
|
.info-label { font-weight: bold; color: #666; }
|
||||||
|
.info-value { margin-left: 10px; }
|
||||||
|
.section-title { font-size: 16px; font-weight: bold; margin: 20px 0 10px; border-bottom: 1px solid #ddd; padding-bottom: 5px; }
|
||||||
|
table { font-size: 14px; }
|
||||||
|
th, td { padding: 8px; text-align: left; }
|
||||||
|
@media print {
|
||||||
|
body { padding: 0; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>采购入库单</h1>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-item"><span class="info-label">入库单号:</span><span class="info-value">${formData.value.no || '(保存后生成)'}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">入库时间:</span><span class="info-value">${inTime}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">关联订单:</span><span class="info-value">${formData.value.orderNo || ''}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">供应商:</span><span class="info-value">${supplierName}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">结算账户:</span><span class="info-value">${accountName}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-item" style="width: 100%;"><span class="info-label">备注:</span><span class="info-value">${formData.value.remark || ''}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="section-title">入库产品清单</div>
|
||||||
|
${itemsTableHtml}
|
||||||
|
<div class="info-row" style="margin-top: 20px;">
|
||||||
|
<div class="info-item"><span class="info-label">优惠率(%):</span><span class="info-value">${formData.value.discountPercent || 0}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">付款优惠:</span><span class="info-value">${formData.value.discountPrice || 0}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">优惠后金额:</span><span class="info-value">${(formData.value.totalPrice || 0) - (formData.value.otherPrice || 0)}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">其它费用:</span><span class="info-value">${formData.value.otherPrice || 0}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">应付金额:</span><span class="info-value">${formData.value.totalPrice || 0}</span></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
// 使用iframe打印,避免被浏览器拦截
|
||||||
|
const iframe = document.createElement('iframe')
|
||||||
|
iframe.style.position = 'absolute'
|
||||||
|
iframe.style.width = '0'
|
||||||
|
iframe.style.height = '0'
|
||||||
|
iframe.style.border = 'none'
|
||||||
|
iframe.style.left = '-9999px'
|
||||||
|
document.body.appendChild(iframe)
|
||||||
|
|
||||||
|
const iframeDoc = iframe.contentWindow?.document
|
||||||
|
if (iframeDoc) {
|
||||||
|
iframeDoc.open()
|
||||||
|
iframeDoc.write(printContent)
|
||||||
|
iframeDoc.close()
|
||||||
|
|
||||||
|
// 等待内容加载完成后打印
|
||||||
|
iframe.contentWindow?.focus()
|
||||||
|
setTimeout(() => {
|
||||||
|
iframe.contentWindow?.print()
|
||||||
|
// 打印完成后移除iframe
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(iframe)
|
||||||
|
}, 1000)
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form
|
<!-- 移动端布局 -->
|
||||||
ref="formRef"
|
<el-form v-if="isMobile" ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-position="top" :inline-message="true" :disabled="disabled">
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
v-loading="formLoading"
|
|
||||||
label-position="top"
|
|
||||||
:inline-message="true"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
<div class="mobile-item-list">
|
<div class="mobile-item-list">
|
||||||
<div
|
<div
|
||||||
v-for="(row, $index) in formData"
|
v-for="(row, $index) in formData"
|
||||||
@@ -123,25 +116,37 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 合计 -->
|
<!-- 合计 -->
|
||||||
<div class="mobile-item-summary" v-if="formData.length > 0">
|
<div class="mobile-item-summary" v-if="formData.length > 0">
|
||||||
<div class="mobile-item-summary__row">
|
<div class="mobile-item-summary__row"><span>合计数量</span><span>{{ erpCountInputFormatter(summaryData.count) }}</span></div>
|
||||||
<span>合计数量</span>
|
<div class="mobile-item-summary__row"><span>合计金额</span><span>{{ erpPriceInputFormatter(summaryData.totalProductPrice) }}</span></div>
|
||||||
<span>{{ erpCountInputFormatter(summaryData.count) }}</span>
|
<div class="mobile-item-summary__row"><span>合计税额</span><span>{{ erpPriceInputFormatter(summaryData.taxPrice) }}</span></div>
|
||||||
</div>
|
<div class="mobile-item-summary__row mobile-item-summary__row--total"><span>税额合计</span><span>{{ erpPriceInputFormatter(summaryData.totalPrice) }}</span></div>
|
||||||
<div class="mobile-item-summary__row">
|
|
||||||
<span>合计金额</span>
|
|
||||||
<span>{{ erpPriceInputFormatter(summaryData.totalProductPrice) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-item-summary__row">
|
|
||||||
<span>合计税额</span>
|
|
||||||
<span>{{ erpPriceInputFormatter(summaryData.taxPrice) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-item-summary__row mobile-item-summary__row--total">
|
|
||||||
<span>税额合计</span>
|
|
||||||
<span>{{ erpPriceInputFormatter(summaryData.totalPrice) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<el-form v-else ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="0px" :inline-message="true" :disabled="disabled">
|
||||||
|
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px">
|
||||||
|
<el-table-column label="序号" type="index" align="center" width="60" />
|
||||||
|
<el-table-column label="仓库名称" min-width="125">
|
||||||
|
<template #default="{ row, $index }"><el-form-item :prop="`${$index}.warehouseId`" :rules="formRules.warehouseId" class="mb-0px!"><el-select v-model="row.warehouseId" clearable filterable placeholder="请选择仓库" @change="onChangeWarehouse($event, row)"><el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="产品名称" min-width="180"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.productName" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="库存" min-width="100"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.stockCount" :formatter="erpCountInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="条码" min-width="150"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.productBarCode" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="单位" min-width="80"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.productUnitName" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="原数量" fixed="right" min-width="80" v-if="formData[0]?.totalCount != null"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.totalCount" :formatter="erpCountInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="已入库" fixed="right" min-width="80" v-if="formData[0]?.inCount != null"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.inCount" :formatter="erpCountInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="数量" prop="count" fixed="right" min-width="140"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.count`" :rules="formRules.count" class="mb-0px!"><el-input-number v-model="row.count" controls-position="right" :min="0.0001" :precision="4" class="!w-100%" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="产品单价" fixed="right" min-width="120"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.productPrice`" class="mb-0px!"><el-input-number v-model="row.productPrice" controls-position="right" :min="0" :precision="4" class="!w-100%" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="金额" prop="totalProductPrice" fixed="right" min-width="100"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.totalProductPrice`" class="mb-0px!"><el-input disabled v-model="row.totalProductPrice" :formatter="erpPriceInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="税率(%)" fixed="right" min-width="115"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.taxPercent`" class="mb-0px!"><el-input-number v-model="row.taxPercent" controls-position="right" :min="0" :precision="4" class="!w-100%" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="税额" prop="taxPrice" fixed="right" min-width="120"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.taxPrice`" class="mb-0px!"><el-input disabled v-model="row.taxPrice" :formatter="erpPriceInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="税额合计" prop="totalPrice" fixed="right" min-width="100"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.totalPrice`" class="mb-0px!"><el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.remark`" class="mb-0px!"><el-input v-model="row.remark" placeholder="请输入备注" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column align="center" fixed="right" label="操作" width="60"><template #default="{ $index }"><el-button :disabled="formData.length === 1" @click="handleDelete($index)" link>—</el-button></template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
@@ -153,6 +158,10 @@ import {
|
|||||||
getSumValue
|
getSumValue
|
||||||
} from '@/utils'
|
} from '@/utils'
|
||||||
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
|
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
items: undefined
|
items: undefined
|
||||||
@@ -208,7 +217,7 @@ watch(
|
|||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 合计 */
|
/** 合计 - 移动端 */
|
||||||
const summaryData = computed(() => {
|
const summaryData = computed(() => {
|
||||||
const data = formData.value
|
const data = formData.value
|
||||||
return {
|
return {
|
||||||
@@ -219,6 +228,20 @@ const summaryData = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 合计 - PC端 */
|
||||||
|
const getSummaries = (param: any) => {
|
||||||
|
const { columns, data } = param
|
||||||
|
const sums: string[] = []
|
||||||
|
columns.forEach((column, index: number) => {
|
||||||
|
if (index === 0) { sums[index] = '合计'; return }
|
||||||
|
if (['count', 'totalProductPrice', 'taxPrice', 'totalPrice'].includes(column.property)) {
|
||||||
|
const sum = getSumValue(data.map((item) => Number(item[column.property])))
|
||||||
|
sums[index] = column.property === 'count' ? erpCountInputFormatter(sum) : erpPriceInputFormatter(sum)
|
||||||
|
} else { sums[index] = '' }
|
||||||
|
})
|
||||||
|
return sums
|
||||||
|
}
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
/** 新增按钮操作 */
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
const row = {
|
const row = {
|
||||||
|
|||||||
@@ -1,21 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mobile-purchase-in">
|
<!-- 移动端布局 -->
|
||||||
<!-- 顶部操作栏 -->
|
<div v-if="isMobile" class="mobile-purchase-in">
|
||||||
<div class="mobile-header">
|
<div class="mobile-header">
|
||||||
<div class="mobile-header__search">
|
<div class="mobile-header__search"><el-input v-model="queryParams.no" placeholder="搜索入库单号" clearable @keyup.enter="handleQuery" :prefix-icon="Search" /></div>
|
||||||
<el-input
|
|
||||||
v-model="queryParams.no"
|
|
||||||
placeholder="搜索入库单号"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
:prefix-icon="Search"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-header__actions">
|
<div class="mobile-header__actions">
|
||||||
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
||||||
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:purchase-in:create']" />
|
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:purchase-in:create']" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mobile-header__quick-filter">
|
||||||
|
<div
|
||||||
|
class="quick-filter-item"
|
||||||
|
:class="{ active: queryParams.status === undefined }"
|
||||||
|
@click="handleQuickFilter(undefined)"
|
||||||
|
>
|
||||||
|
全部订单
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="quick-filter-item"
|
||||||
|
:class="{ active: queryParams.status === 10 }"
|
||||||
|
@click="handleQuickFilter(10)"
|
||||||
|
>
|
||||||
|
待审核
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="quick-filter-item"
|
||||||
|
:class="{ active: queryParams.status === 20 }"
|
||||||
|
@click="handleQuickFilter(20)"
|
||||||
|
>
|
||||||
|
已审核
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 卡片列表 -->
|
<!-- 卡片列表 -->
|
||||||
<div class="mobile-list" v-loading="loading">
|
<div class="mobile-list" v-loading="loading">
|
||||||
@@ -136,40 +151,256 @@
|
|||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<PurchaseInForm ref="formRef" @success="getList" />
|
<PurchaseInForm ref="formRef" @success="getList" />
|
||||||
|
|
||||||
<!-- 审核弹窗 -->
|
<!-- 审核弹窗(移动端) -->
|
||||||
<el-dialog v-model="auditVisible" title="采购入库审核" width="90%" append-to-body destroy-on-close>
|
<el-dialog v-model="auditVisible" title="采购入库审核" width="90%" append-to-body destroy-on-close>
|
||||||
<el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-position="top">
|
<el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-position="top">
|
||||||
<el-form-item label="是否合格" prop="isQualified">
|
<el-form-item label="是否合格" prop="isQualified"><el-switch v-model="auditForm.isQualified" :active-value="true" :inactive-value="false" @change="handleQualifiedChange" /></el-form-item>
|
||||||
<el-switch v-model="auditForm.isQualified" :active-value="true" :inactive-value="false" @change="handleQualifiedChange" />
|
<el-form-item label="返回方式" prop="returnType"><el-select v-model="auditForm.returnType" placeholder="请选择返回方式" clearable style="width:100%" :disabled="auditForm.isQualified"><el-option v-for="option in RETURN_TYPE_OPTIONS" :key="option.value" :label="option.label" :value="option.value" /></el-select></el-form-item>
|
||||||
</el-form-item>
|
<el-form-item label="返回备注" prop="returnRemark"><el-input v-model="auditForm.returnRemark" type="textarea" placeholder="请输入返回方式备注" :rows="3" :disabled="auditForm.isQualified" /></el-form-item>
|
||||||
<el-form-item label="返回方式" prop="returnType">
|
|
||||||
<el-select v-model="auditForm.returnType" placeholder="请选择返回方式" clearable style="width:100%" :disabled="auditForm.isQualified">
|
|
||||||
<el-option v-for="option in RETURN_TYPE_OPTIONS" :key="option.value" :label="option.label" :value="option.value" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="返回备注" prop="returnRemark">
|
|
||||||
<el-input v-model="auditForm.returnRemark" type="textarea" placeholder="请输入返回方式备注" :rows="3" :disabled="auditForm.isQualified" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer><el-button @click="auditVisible = false">取消</el-button><el-button type="primary" @click="submitAudit" :loading="auditLoading">确定</el-button></template>
|
||||||
<el-button @click="auditVisible = false">取消</el-button>
|
|
||||||
<el-button type="primary" @click="submitAudit" :loading="auditLoading">确定</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="68px"
|
||||||
|
>
|
||||||
|
<el-form-item label="入库单号" prop="no">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.no"
|
||||||
|
placeholder="请输入入库单号"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="产品" prop="productId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.productId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择产品"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in productList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="入库时间" prop="inTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.inTime"
|
||||||
|
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-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="供应商" prop="supplierId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.supplierId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择供供应商"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in supplierList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="仓库" prop="warehouseId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.warehouseId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择仓库"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in warehouseList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="创建人" prop="creator">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.creator"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择创建人"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in userList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.nickname"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="关联订单" prop="orderNo">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.orderNo"
|
||||||
|
placeholder="请输入关联订单"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="结算账户" prop="accountId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.accountId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择结算账户"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in accountList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="付款状态" prop="paymentStatus">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.paymentStatus"
|
||||||
|
placeholder="请选择有款状态"
|
||||||
|
clearable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option label="未付款" value="0" />
|
||||||
|
<el-option label="部分付款" value="1" />
|
||||||
|
<el-option label="全部付款" value="2" />
|
||||||
|
</el-select>
|
||||||
|
</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.ERP_AUDIT_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.remark"
|
||||||
|
placeholder="请输入备注"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</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
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="openForm('create')"
|
||||||
|
v-hasPermi="['erp:purchase-in:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['erp:purchase-in:export']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
@click="handleDelete(selectionList.map((item) => item.id))"
|
||||||
|
v-hasPermi="['erp:purchase-in:delete']"
|
||||||
|
:disabled="selectionList.length === 0"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:delete" class="mr-5px" /> 删除
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange" @row-click="handleRowClick">
|
||||||
|
<el-table-column width="30" label="选择" type="selection" />
|
||||||
|
<el-table-column min-width="180" label="入库单号" align="center" prop="no" />
|
||||||
|
<el-table-column label="产品信息" align="center" prop="productNames" min-width="200" />
|
||||||
|
<el-table-column label="供应商" align="center" prop="supplierName" />
|
||||||
|
<el-table-column label="入库时间" align="center" prop="inTime" :formatter="dateFormatter2" width="120px" sortable />
|
||||||
|
<el-table-column label="创建人" align="center" prop="creatorName" />
|
||||||
|
<el-table-column label="总数量" align="center" prop="totalCount" :formatter="erpCountTableColumnFormatter" />
|
||||||
|
<el-table-column label="应付金额" align="center" prop="totalPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
|
<el-table-column label="已付金额" align="center" prop="paymentPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
|
<el-table-column label="未付金额" align="center"><template #default="scope"><span v-if="scope.row.paymentPrice === scope.row.totalPrice">0</span><el-tag type="danger" v-else>{{ erpPriceInputFormatter(scope.row.totalPrice - scope.row.paymentPrice) }}</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="审核状态" align="center" prop="status" width="120"><template #default="scope"><el-tag :type="scope.row.status === 10 ? 'info' : (scope.row.isQualified ? 'success' : 'danger')">{{ scope.row.status === 10 ? '未审核' : (scope.row.isQualified ? '已审核' : '不合格') }}</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" fixed="right" width="220">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link type="primary" @click="openForm('detail', scope.row.id)" v-hasPermi="['erp:purchase-in:query']">详情</el-button>
|
||||||
|
<el-button v-if="scope.row.status === 10" link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:purchase-in:update']">修改</el-button>
|
||||||
|
<el-button v-if="scope.row.status === 10" link type="primary" @click="handleUpdateStatus(scope.row.id, scope.row.status)" v-hasPermi="['erp:purchase-in:update-status']">审核</el-button>
|
||||||
|
<el-button v-if="scope.row.status === 20 || scope.row.status === 30" link type="warning" @click="handleUpdateStatus(scope.row.id, scope.row.status)" v-hasPermi="['erp:purchase-in:update-status']">反审核</el-button>
|
||||||
|
<el-button v-if="scope.row.status === 10" link type="danger" @click="handleDelete([scope.row.id])" v-hasPermi="['erp:purchase-in:delete']">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
<!-- 审核弹窗(PC端) -->
|
||||||
|
<el-dialog v-model="auditVisible" title="采购入库审核" width="500px" append-to-body destroy-on-close>
|
||||||
|
<el-form ref="auditFormRef" :model="auditForm" :rules="auditRules" label-width="100px">
|
||||||
|
<el-form-item label="是否合格" prop="isQualified"><el-switch v-model="auditForm.isQualified" :active-value="true" :inactive-value="false" @change="handleQualifiedChange" /></el-form-item>
|
||||||
|
<el-form-item label="返回方式" prop="returnType"><el-select v-model="auditForm.returnType" placeholder="请选择返回方式" clearable style="width:100%" :disabled="auditForm.isQualified"><el-option v-for="option in RETURN_TYPE_OPTIONS" :key="option.value" :label="option.label" :value="option.value" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="返回备注" prop="returnRemark"><el-input v-model="auditForm.returnRemark" type="textarea" placeholder="请输入返回方式备注" :rows="3" :disabled="auditForm.isQualified" /></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer><el-button @click="auditVisible = false">取 消</el-button><el-button type="primary" @click="submitAudit" :loading="auditLoading">确 定</el-button></template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<PurchaseInForm ref="formRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate, dateFormatter2 } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { PurchaseInApi, PurchaseInVO, RETURN_TYPE_OPTIONS } from '@/api/erp/purchase/in'
|
import { PurchaseInApi, PurchaseInVO, RETURN_TYPE_OPTIONS } from '@/api/erp/purchase/in'
|
||||||
import PurchaseInForm from './PurchaseInForm.vue'
|
import PurchaseInForm from './PurchaseInForm.vue'
|
||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
import { UserVO } from '@/api/system/user'
|
import { UserVO } from '@/api/system/user'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import { erpCountInputFormatter, erpPriceInputFormatter } from '@/utils'
|
import { erpCountInputFormatter, erpPriceInputFormatter, erpCountTableColumnFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||||
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
|
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
|
||||||
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
@@ -177,6 +408,7 @@ import { ElMessage, ElMessageBox } from 'element-plus'
|
|||||||
import { useMessage } from '@/hooks/web/useMessage'
|
import { useMessage } from '@/hooks/web/useMessage'
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { ref, reactive, onMounted, onActivated } from 'vue'
|
import { ref, reactive, onMounted, onActivated } from 'vue'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
interface AuditFormData {
|
interface AuditFormData {
|
||||||
id: number
|
id: number
|
||||||
@@ -189,6 +421,9 @@ interface AuditFormData {
|
|||||||
/** ERP 销售入库列表 */
|
/** ERP 销售入库列表 */
|
||||||
defineOptions({ name: 'ErpPurchaseIn' })
|
defineOptions({ name: 'ErpPurchaseIn' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
const loading = ref(true)
|
const loading = ref(true)
|
||||||
@@ -287,9 +522,40 @@ const handleDelete = async (ids: number[]) => {
|
|||||||
await PurchaseInApi.deletePurchaseIn(ids)
|
await PurchaseInApi.deletePurchaseIn(ids)
|
||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
await getList()
|
await getList()
|
||||||
|
selectionList.value = selectionList.value.filter((item) => !ids.includes(item.id))
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 选中操作(PC端) */
|
||||||
|
const selectionList = ref<PurchaseInVO[]>([])
|
||||||
|
const handleSelectionChange = (rows: PurchaseInVO[]) => {
|
||||||
|
selectionList.value = rows
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 行点击操作(PC端) */
|
||||||
|
const handleRowClick = (row: PurchaseInVO, column: any, event: MouseEvent) => {
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (target.tagName === 'BUTTON' || target.tagName === 'A' || target.tagName === 'I' || target.tagName === 'svg' || target.closest('button') || target.closest('a') || target.closest('.el-button') || target.closest('.el-checkbox')) return
|
||||||
|
if (row.status === 20 || row.status === 30) {
|
||||||
|
openForm('detail', row.id)
|
||||||
|
} else if (row.status === 10) {
|
||||||
|
openForm('update', row.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出按钮操作 */
|
||||||
|
const handleExport = async () => {
|
||||||
|
try {
|
||||||
|
await message.exportConfirm()
|
||||||
|
exportLoading.value = true
|
||||||
|
const exportParams = selectionList.value.length > 0 ? { ids: selectionList.value.map(item => item.id) } : queryParams
|
||||||
|
const data = await PurchaseInApi.exportPurchaseIn(exportParams)
|
||||||
|
download.excel(data, '采购入库.xls')
|
||||||
|
} catch {} finally {
|
||||||
|
exportLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 审核弹窗相关
|
// 审核弹窗相关
|
||||||
const auditVisible = ref(false)
|
const auditVisible = ref(false)
|
||||||
const auditLoading = ref(false)
|
const auditLoading = ref(false)
|
||||||
@@ -409,9 +675,40 @@ onActivated(() => {
|
|||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/** 快捷分类筛选 */
|
||||||
|
const handleQuickFilter = (status: number | undefined) => {
|
||||||
|
queryParams.status = status
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.mobile-header__quick-filter {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.quick-filter-item {
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #909399;
|
||||||
|
background: transparent;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #fff;
|
||||||
|
background: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mobile-purchase-in {
|
.mobile-purchase-in {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
|
|||||||
@@ -1,132 +1,81 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer
|
<!-- 移动端布局 -->
|
||||||
v-model="dialogVisible"
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
:title="dialogTitle"
|
|
||||||
direction="rtl"
|
|
||||||
size="100%"
|
|
||||||
:close-on-press-escape="true"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
:append-to-body="true"
|
|
||||||
class="mobile-form-drawer"
|
|
||||||
>
|
|
||||||
<div class="mobile-form" v-loading="formLoading">
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
<el-form
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" :disabled="disabled">
|
||||||
ref="formRef"
|
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
label-position="top"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
<!-- 基本信息 -->
|
|
||||||
<div class="mobile-form__section">
|
<div class="mobile-form__section">
|
||||||
<div class="mobile-form__section-title">基本信息</div>
|
<div class="mobile-form__section-title">基本信息</div>
|
||||||
<el-form-item label="比价单号" prop="no">
|
<el-form-item label="比价单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item>
|
||||||
<el-input disabled v-model="formData.no" placeholder="保存时自动生成" />
|
<el-form-item label="询价时间" prop="inquiryTime"><el-date-picker v-model="formData.inquiryTime" type="date" value-format="x" placeholder="选择询价时间" style="width: 100%" /></el-form-item>
|
||||||
</el-form-item>
|
<el-form-item label="截止日期" prop="deadline"><el-date-picker v-model="formData.deadline" type="date" value-format="x" placeholder="选择截止日期" style="width: 100%" /></el-form-item>
|
||||||
<el-form-item label="询价时间" prop="inquiryTime">
|
<el-form-item label="产品" prop="productId"><el-select v-model="formData.productId" clearable filterable placeholder="请选择产品" style="width: 100%" @change="onChangeProduct"><el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
<el-date-picker
|
|
||||||
v-model="formData.inquiryTime"
|
|
||||||
type="date"
|
|
||||||
value-format="x"
|
|
||||||
placeholder="选择询价时间"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="截止日期" prop="deadline">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="formData.deadline"
|
|
||||||
type="date"
|
|
||||||
value-format="x"
|
|
||||||
placeholder="选择截止日期"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="产品" prop="productId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.productId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择产品"
|
|
||||||
style="width: 100%"
|
|
||||||
@change="onChangeProduct"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in productList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<div class="mobile-form__input-group">
|
<div class="mobile-form__input-group">
|
||||||
<el-form-item label="需求数量" prop="requireCount">
|
<el-form-item label="需求数量" prop="requireCount"><el-input-number v-model="formData.requireCount" controls-position="right" :min="0.0001" :precision="4" placeholder="需求数量" style="width: 100%" /></el-form-item>
|
||||||
<el-input-number
|
<el-form-item label="预算金额" prop="budgetPrice"><el-input-number v-model="formData.budgetPrice" controls-position="right" :min="0" :precision="2" placeholder="预算金额" style="width: 100%" /></el-form-item>
|
||||||
v-model="formData.requireCount"
|
|
||||||
controls-position="right"
|
|
||||||
:min="0.0001"
|
|
||||||
:precision="4"
|
|
||||||
placeholder="需求数量"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="预算金额" prop="budgetPrice">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.budgetPrice"
|
|
||||||
controls-position="right"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
placeholder="预算金额"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
</div>
|
||||||
<el-form-item label="备注" prop="remark">
|
<el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="2" placeholder="请输入备注" /></el-form-item>
|
||||||
<el-input type="textarea" v-model="formData.remark" :rows="2" placeholder="请输入备注" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 供应商报价 -->
|
|
||||||
<div class="mobile-form__section">
|
<div class="mobile-form__section">
|
||||||
<div class="mobile-form__section-title">供应商报价</div>
|
<div class="mobile-form__section-title">供应商报价</div>
|
||||||
<InquiryQuoteForm ref="quoteFormRef" :quotes="formData.quotes" :disabled="disabled" :require-count="formData.requireCount" />
|
<InquiryQuoteForm ref="quoteFormRef" :quotes="formData.quotes" :disabled="disabled" :require-count="formData.requireCount" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 比价汇总 -->
|
|
||||||
<div class="mobile-form__section" v-if="formData.quotes && formData.quotes.length > 0">
|
<div class="mobile-form__section" v-if="formData.quotes && formData.quotes.length > 0">
|
||||||
<div class="mobile-form__section-title">比价汇总</div>
|
<div class="mobile-form__section-title">比价汇总</div>
|
||||||
<div class="mobile-form__info-row">
|
<div class="mobile-form__info-row"><span class="mobile-form__info-label">报价数量</span><span class="mobile-form__info-value">{{ formData.quotes.length }}</span></div>
|
||||||
<span class="mobile-form__info-label">报价数量</span>
|
<div class="mobile-form__info-row"><span class="mobile-form__info-label">最低报价</span><span class="mobile-form__info-value" style="color: #67c23a">{{ erpPriceInputFormatter(minQuotePrice) }}</span></div>
|
||||||
<span class="mobile-form__info-value">{{ formData.quotes.length }}</span>
|
<div class="mobile-form__info-row"><span class="mobile-form__info-label">最高报价</span><span class="mobile-form__info-value" style="color: #e6a23c">{{ erpPriceInputFormatter(maxQuotePrice) }}</span></div>
|
||||||
</div>
|
|
||||||
<div class="mobile-form__info-row">
|
|
||||||
<span class="mobile-form__info-label">最低报价</span>
|
|
||||||
<span class="mobile-form__info-value" style="color: #67c23a">{{ erpPriceInputFormatter(minQuotePrice) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-form__info-row">
|
|
||||||
<span class="mobile-form__info-label">最高报价</span>
|
|
||||||
<span class="mobile-form__info-value" style="color: #e6a23c">{{ erpPriceInputFormatter(maxQuotePrice) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<!-- 底部操作按钮 -->
|
|
||||||
<div class="mobile-form__footer">
|
<div class="mobile-form__footer">
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible" width="1200">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading" :disabled="disabled">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8"><el-form-item label="比价单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="询价时间" prop="inquiryTime"><el-date-picker v-model="formData.inquiryTime" type="date" value-format="x" placeholder="选择询价时间" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="截止日期" prop="deadline"><el-date-picker v-model="formData.deadline" type="date" value-format="x" placeholder="选择截止日期" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="产品" prop="productId"><el-select v-model="formData.productId" clearable filterable placeholder="请选择产品" class="!w-1/1" @change="onChangeProduct"><el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="需求数量" prop="requireCount"><el-input-number v-model="formData.requireCount" controls-position="right" :min="0.0001" :precision="4" placeholder="请输入需求数量" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="预算金额" prop="budgetPrice"><el-input-number v-model="formData.budgetPrice" controls-position="right" :min="0" :precision="2" placeholder="请输入预算金额" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="24"><el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="2" placeholder="请输入备注" /></el-form-item></el-col>
|
||||||
|
</el-row>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
||||||
|
<el-tab-pane label="供应商报价" name="quote"><InquiryQuoteForm ref="quoteFormRef" :quotes="formData.quotes" :disabled="disabled" :require-count="formData.requireCount" /></el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</ContentWrap>
|
||||||
|
<el-row :gutter="20" v-if="formData.quotes && formData.quotes.length > 0">
|
||||||
|
<el-col :span="8"><el-form-item label="报价数量"><el-input disabled :value="formData.quotes.length" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="最低报价"><el-input disabled :value="minQuotePrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="最高报价"><el-input disabled :value="maxQuotePrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { nextTick } from 'vue'
|
import { nextTick, computed } from 'vue'
|
||||||
import { PurchaseInquiryApi, PurchaseInquiryVO } from '@/api/erp/purchase/inquiry'
|
import { PurchaseInquiryApi, PurchaseInquiryVO } from '@/api/erp/purchase/inquiry'
|
||||||
import InquiryQuoteForm from './components/InquiryQuoteForm.vue'
|
import InquiryQuoteForm from './components/InquiryQuoteForm.vue'
|
||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
import { erpPriceInputFormatter } from '@/utils'
|
import { erpPriceInputFormatter } from '@/utils'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 采购比价表单 */
|
/** ERP 采购比价表单 */
|
||||||
defineOptions({ name: 'InquiryForm' })
|
defineOptions({ name: 'InquiryForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -166,7 +115,7 @@ const formRules = reactive({
|
|||||||
const disabled = computed(() => formType.value === 'detail')
|
const disabled = computed(() => formType.value === 'detail')
|
||||||
const formRef = ref() // 表单 Ref
|
const formRef = ref() // 表单 Ref
|
||||||
const productList = ref<ProductVO[]>([]) // 产品列表
|
const productList = ref<ProductVO[]>([]) // 产品列表
|
||||||
|
const subTabsName = ref('quote') // PC端子表标签
|
||||||
const quoteFormRef = ref()
|
const quoteFormRef = ref()
|
||||||
|
|
||||||
/** 计算最低报价 */
|
/** 计算最低报价 */
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form
|
<!-- 移动端布局 -->
|
||||||
ref="formRef"
|
<el-form v-if="isMobile" ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-position="top" :inline-message="true" :disabled="disabled">
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
v-loading="formLoading"
|
|
||||||
label-position="top"
|
|
||||||
:inline-message="true"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
<div class="mobile-quote-list">
|
<div class="mobile-quote-list">
|
||||||
<div
|
<div
|
||||||
v-for="(row, $index) in formData"
|
v-for="(row, $index) in formData"
|
||||||
@@ -118,20 +111,50 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 添加按钮 -->
|
<!-- 添加按钮 -->
|
||||||
<div class="mobile-quote-add" v-if="!disabled">
|
<div class="mobile-quote-add" v-if="!disabled"><el-button @click="handleAdd" round>+ 添加供应商报价</el-button></div>
|
||||||
<el-button @click="handleAdd" round>+ 添加供应商报价</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="0px" :inline-message="true" :disabled="disabled">
|
||||||
|
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px" :row-class-name="tableRowClassName">
|
||||||
|
<el-table-column label="序号" type="index" align="center" width="60" />
|
||||||
|
<el-table-column label="选中" width="70" align="center"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.isSelected`" class="mb-0px!"><el-checkbox v-model="row.isSelected" @change="onSelectChange(row, $index)" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="供应商" min-width="200"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.supplierId`" :rules="formRules.supplierId" class="mb-0px!"><el-select v-model="row.supplierId" clearable filterable @change="onChangeSupplier($event, row)" placeholder="请选择供应商" class="!w-full"><template #prefix v-if="!disabled"><el-button type="primary" link size="small" @click.stop="openQuickSupplierForm($index)" title="快速新增供应商" class="!p-0 !m-0"><Icon icon="ep:plus" /></el-button></template><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="联系人" min-width="120"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.contactName`" class="mb-0px!"><el-input v-model="row.contactName" placeholder="联系人" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="联系电话" min-width="140"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.contactPhone`" class="mb-0px!"><el-input v-model="row.contactPhone" placeholder="联系电话" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="报价日期" min-width="160"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.quoteDate`" class="mb-0px!"><el-date-picker v-model="row.quoteDate" type="date" value-format="x" placeholder="报价日期" class="!w-100%" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="单价" prop="unitPrice" min-width="140"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.unitPrice`" :rules="formRules.unitPrice" class="mb-0px!"><el-input-number v-model="row.unitPrice" controls-position="right" :min="0" :precision="4" class="!w-100%" placeholder="单价" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="总价" prop="totalPrice" min-width="120"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.totalPrice`" class="mb-0px!"><el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="付款条件" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.paymentTerms`" class="mb-0px!"><el-select v-model="row.paymentTerms" placeholder="付款条件" clearable><el-option label="款到发货" value="款到发货" /><el-option label="货到付款" value="货到付款" /><el-option label="月结30天" value="月结30天" /><el-option label="月结60天" value="月结60天" /><el-option label="月结90天" value="月结90天" /><el-option label="预付30%" value="预付30%" /><el-option label="预付50%" value="预付50%" /><el-option label="其他" value="其他" /></el-select></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="交货周期(天)" min-width="130"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.deliveryCycle`" class="mb-0px!"><el-input-number v-model="row.deliveryCycle" controls-position="right" :min="0" :precision="0" class="!w-100%" placeholder="天数" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="资质文件" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.qualificationFile`" class="mb-0px!"><UploadFile :is-show-tip="false" v-model="row.qualificationFile" :limit="1" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.remark`" class="mb-0px!"><el-input v-model="row.remark" placeholder="请输入备注" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="质量评分" width="100"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.qualityScore`" class="mb-0px!"><el-input-number v-model="row.qualityScore" controls-position="right" :min="0" :max="100" :precision="0" class="!w-100%" placeholder="0-100" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="服务评分" width="100"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.serviceScore`" class="mb-0px!"><el-input-number v-model="row.serviceScore" controls-position="right" :min="0" :max="100" :precision="0" class="!w-100%" placeholder="0-100" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="价格得分" width="90" align="center"><template #default="{ row }"><span :class="row.priceScore >= 100 ? 'text-green-600 font-bold' : ''">{{ row.priceScore?.toFixed(2) ?? '-' }}</span></template></el-table-column>
|
||||||
|
<el-table-column label="交付得分" width="90" align="center"><template #default="{ row }"><span :class="row.deliveryScore >= 100 ? 'text-green-600 font-bold' : ''">{{ row.deliveryScore?.toFixed(2) ?? '-' }}</span></template></el-table-column>
|
||||||
|
<el-table-column label="综合得分" width="100" align="center"><template #default="{ row }"><el-tag v-if="row.totalScore != null" :type="row.isSelected ? 'success' : 'info'">{{ row.totalScore?.toFixed(2) }}</el-tag><span v-else class="text-gray-400">-</span></template></el-table-column>
|
||||||
|
<el-table-column align="center" fixed="right" label="操作" width="60"><template #default="{ $index }"><el-button @click="handleDelete($index)" link>—</el-button></template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-form>
|
||||||
|
<el-row justify="center" class="mt-3" v-if="!disabled"><el-button @click="handleAdd" round>+ 添加供应商报价</el-button></el-row>
|
||||||
|
</template>
|
||||||
<!-- 快速新增供应商弹窗 -->
|
<!-- 快速新增供应商弹窗 -->
|
||||||
<QuickSupplierForm ref="quickSupplierFormRef" @success="onQuickSupplierSuccess" />
|
<QuickSupplierForm ref="quickSupplierFormRef" @success="onQuickSupplierSuccess" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
import { SupplierEvaluationApi } from '@/api/erp/purchase/supplierEvaluation'
|
import { SupplierEvaluationApi } from '@/api/erp/purchase/supplierEvaluation'
|
||||||
import { erpPriceInputFormatter, erpPriceMultiply, getSumValue } from '@/utils'
|
import { erpPriceInputFormatter, erpPriceMultiply, getSumValue } from '@/utils'
|
||||||
import { PurchaseInquiryQuoteVO } from '@/api/erp/purchase/inquiry'
|
import { PurchaseInquiryQuoteVO } from '@/api/erp/purchase/inquiry'
|
||||||
import QuickSupplierForm from './QuickSupplierForm.vue'
|
import QuickSupplierForm from './QuickSupplierForm.vue'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -241,11 +264,25 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 合计总价 */
|
/** 合计总价 - 移动端 */
|
||||||
const summaryTotalPrice = computed(() => {
|
const summaryTotalPrice = computed(() => {
|
||||||
return getSumValue(formData.value.map((item) => Number(item.totalPrice || 0)))
|
return getSumValue(formData.value.map((item) => Number(item.totalPrice || 0)))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 合计 - PC端 */
|
||||||
|
const getSummaries = (param: any) => {
|
||||||
|
const { columns, data } = param
|
||||||
|
const sums: string[] = []
|
||||||
|
columns.forEach((column, index: number) => {
|
||||||
|
if (index === 0) { sums[index] = '合计'; return }
|
||||||
|
if (['totalPrice'].includes(column.property)) {
|
||||||
|
const sum = getSumValue(data.map((item) => Number(item[column.property])))
|
||||||
|
sums[index] = erpPriceInputFormatter(sum)
|
||||||
|
} else { sums[index] = '' }
|
||||||
|
})
|
||||||
|
return sums
|
||||||
|
}
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
/** 新增按钮操作 */
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
const row: PurchaseInquiryQuoteVO = {
|
const row: PurchaseInquiryQuoteVO = {
|
||||||
|
|||||||
@@ -1,34 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer
|
<!-- 移动端布局 -->
|
||||||
v-model="dialogVisible"
|
<el-drawer v-if="isMobile" v-model="dialogVisible" title="快速新增供应商" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true">
|
||||||
title="快速新增供应商"
|
|
||||||
direction="rtl"
|
|
||||||
size="100%"
|
|
||||||
:close-on-press-escape="true"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
>
|
|
||||||
<div class="mobile-quick-supplier" v-loading="formLoading">
|
<div class="mobile-quick-supplier" v-loading="formLoading">
|
||||||
<el-form
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top">
|
||||||
ref="formRef"
|
<el-form-item label="供应商名称" prop="name"><el-input v-model="formData.name" placeholder="请输入供应商名称" /></el-form-item>
|
||||||
:model="formData"
|
<el-form-item label="联系人" prop="contact"><el-input v-model="formData.contact" placeholder="请输入联系人" /></el-form-item>
|
||||||
:rules="formRules"
|
<el-form-item label="手机号码" prop="mobile"><el-input v-model="formData.mobile" placeholder="请输入手机号码" /></el-form-item>
|
||||||
label-position="top"
|
<el-form-item label="联系电话" prop="telephone"><el-input v-model="formData.telephone" placeholder="请输入联系电话" /></el-form-item>
|
||||||
>
|
<el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" :rows="2" /></el-form-item>
|
||||||
<el-form-item label="供应商名称" prop="name">
|
|
||||||
<el-input v-model="formData.name" placeholder="请输入供应商名称" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="联系人" prop="contact">
|
|
||||||
<el-input v-model="formData.contact" placeholder="请输入联系人" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="手机号码" prop="mobile">
|
|
||||||
<el-input v-model="formData.mobile" placeholder="请输入手机号码" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="联系电话" prop="telephone">
|
|
||||||
<el-input v-model="formData.telephone" placeholder="请输入联系电话" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" :rows="2" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -36,14 +15,34 @@
|
|||||||
<el-button @click="submitForm" type="primary" :loading="formLoading">确 定</el-button>
|
<el-button @click="submitForm" type="primary" :loading="formLoading">确 定</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else title="快速新增供应商" v-model="dialogVisible" width="500px">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading">
|
||||||
|
<el-form-item label="供应商名称" prop="name"><el-input v-model="formData.name" placeholder="请输入供应商名称" /></el-form-item>
|
||||||
|
<el-form-item label="联系人" prop="contact"><el-input v-model="formData.contact" placeholder="请输入联系人" /></el-form-item>
|
||||||
|
<el-form-item label="手机号码" prop="mobile"><el-input v-model="formData.mobile" placeholder="请输入手机号码" /></el-form-item>
|
||||||
|
<el-form-item label="联系电话" prop="telephone"><el-input v-model="formData.telephone" placeholder="请输入联系电话" /></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" :rows="2" /></el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :loading="formLoading">确 定</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'QuickSupplierForm' })
|
defineOptions({ name: 'QuickSupplierForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mobile-purchase-inquiry">
|
<!-- 移动端布局 -->
|
||||||
|
<div v-if="isMobile" class="mobile-purchase-inquiry">
|
||||||
<!-- 顶部操作栏 -->
|
<!-- 顶部操作栏 -->
|
||||||
<div class="mobile-header">
|
<div class="mobile-header">
|
||||||
<div class="mobile-header__search">
|
<div class="mobile-header__search">
|
||||||
@@ -11,11 +12,16 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="mobile-header__quick-filter">
|
||||||
|
<div class="quick-filter-item" :class="{ active: queryParams.status === undefined }" @click="handleQuickFilter(undefined)">全部订单</div>
|
||||||
|
<div class="quick-filter-item" :class="{ active: queryParams.status === 10 }" @click="handleQuickFilter(10)">待询价</div>
|
||||||
|
<div class="quick-filter-item" :class="{ active: queryParams.status === 20 }" @click="handleQuickFilter(20)">询价中</div>
|
||||||
|
<div class="quick-filter-item" :class="{ active: queryParams.status === 30 }" @click="handleQuickFilter(30)">已完成</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 卡片列表 -->
|
<!-- 卡片列表 -->
|
||||||
<div class="mobile-list" v-loading="loading">
|
<div class="mobile-list" v-loading="loading">
|
||||||
<div v-if="list.length === 0 && !loading" class="mobile-empty">
|
<div v-if="list.length === 0 && !loading" class="mobile-empty"><el-empty description="暂无比价记录" /></div>
|
||||||
<el-empty description="暂无比价记录" />
|
|
||||||
</div>
|
|
||||||
<div v-for="item in list" :key="item.id" class="mobile-card" @click="handleCardClick(item)">
|
<div v-for="item in list" :key="item.id" class="mobile-card" @click="handleCardClick(item)">
|
||||||
<div class="mobile-card__header">
|
<div class="mobile-card__header">
|
||||||
<span class="mobile-card__no">{{ item.no }}</span>
|
<span class="mobile-card__no">{{ item.no }}</span>
|
||||||
@@ -24,39 +30,15 @@
|
|||||||
<el-tag v-else-if="item.status === 30" type="success" size="small">已完成</el-tag>
|
<el-tag v-else-if="item.status === 30" type="success" size="small">已完成</el-tag>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card__body">
|
<div class="mobile-card__body">
|
||||||
<div class="mobile-card__row">
|
<div class="mobile-card__row"><span class="mobile-card__label">产品</span><span class="mobile-card__value">{{ item.productName || '-' }}</span></div>
|
||||||
<span class="mobile-card__label">产品</span>
|
<div class="mobile-card__row"><span class="mobile-card__label">询价时间</span><span class="mobile-card__value">{{ formatDate2(item.inquiryTime) }}</span></div>
|
||||||
<span class="mobile-card__value">{{ item.productName || '-' }}</span>
|
<div class="mobile-card__row"><span class="mobile-card__label">截止日期</span><span class="mobile-card__value">{{ formatDate2(item.deadline) }}</span></div>
|
||||||
</div>
|
<div class="mobile-card__row"><span class="mobile-card__label">创建人</span><span class="mobile-card__value">{{ item.creatorName || '-' }}</span></div>
|
||||||
<div class="mobile-card__row">
|
|
||||||
<span class="mobile-card__label">询价时间</span>
|
|
||||||
<span class="mobile-card__value">{{ formatDate2(item.inquiryTime) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__row">
|
|
||||||
<span class="mobile-card__label">截止日期</span>
|
|
||||||
<span class="mobile-card__value">{{ formatDate2(item.deadline) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__row">
|
|
||||||
<span class="mobile-card__label">创建人</span>
|
|
||||||
<span class="mobile-card__value">{{ item.creatorName || '-' }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__nums">
|
<div class="mobile-card__nums">
|
||||||
<div class="mobile-card__num-item">
|
<div class="mobile-card__num-item"><div class="mobile-card__num-val">{{ erpCountInputFormatter(item.requireCount) }}</div><div class="mobile-card__num-label">需求数量</div></div>
|
||||||
<div class="mobile-card__num-val">{{ erpCountInputFormatter(item.requireCount) }}</div>
|
<div class="mobile-card__num-item"><div class="mobile-card__num-val mobile-card__num-val--price">¥{{ erpPriceInputFormatter(item.budgetPrice) }}</div><div class="mobile-card__num-label">预算金额</div></div>
|
||||||
<div class="mobile-card__num-label">需求数量</div>
|
<div class="mobile-card__num-item"><div class="mobile-card__num-val">{{ item.quoteCount || 0 }}</div><div class="mobile-card__num-label">报价数</div></div>
|
||||||
</div>
|
<div class="mobile-card__num-item"><div class="mobile-card__num-val" style="color:#67c23a">¥{{ erpPriceInputFormatter(item.minQuotePrice) }}</div><div class="mobile-card__num-label">最低报价</div></div>
|
||||||
<div class="mobile-card__num-item">
|
|
||||||
<div class="mobile-card__num-val mobile-card__num-val--price">¥{{ erpPriceInputFormatter(item.budgetPrice) }}</div>
|
|
||||||
<div class="mobile-card__num-label">预算金额</div>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__num-item">
|
|
||||||
<div class="mobile-card__num-val">{{ item.quoteCount || 0 }}</div>
|
|
||||||
<div class="mobile-card__num-label">报价数</div>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-card__num-item">
|
|
||||||
<div class="mobile-card__num-val" style="color:#67c23a">¥{{ erpPriceInputFormatter(item.minQuotePrice) }}</div>
|
|
||||||
<div class="mobile-card__num-label">最低报价</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mobile-card__footer">
|
<div class="mobile-card__footer">
|
||||||
@@ -78,26 +60,10 @@
|
|||||||
<!-- 筛选抽屉 -->
|
<!-- 筛选抽屉 -->
|
||||||
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="60%">
|
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="60%">
|
||||||
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
<el-form-item label="产品" prop="productId">
|
<el-form-item label="产品" prop="productId"><el-select v-model="queryParams.productId" clearable filterable placeholder="请选择产品" style="width:100%"><el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
<el-select v-model="queryParams.productId" clearable filterable placeholder="请选择产品" style="width:100%">
|
<el-form-item label="询价时间" prop="inquiryTime"><el-date-picker v-model="queryParams.inquiryTime" 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')]" style="width:100%" /></el-form-item>
|
||||||
<el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" />
|
<el-form-item label="状态" prop="status"><el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width:100%"><el-option label="待询价" :value="10" /><el-option label="询价中" :value="20" /><el-option label="已完成" :value="30" /></el-select></el-form-item>
|
||||||
</el-select>
|
<el-form-item label="创建人" prop="creator"><el-select v-model="queryParams.creator" clearable filterable placeholder="请选择创建人" style="width:100%"><el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" /></el-select></el-form-item>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="询价时间" prop="inquiryTime">
|
|
||||||
<el-date-picker v-model="queryParams.inquiryTime" 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')]" style="width:100%" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="状态" prop="status">
|
|
||||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width:100%">
|
|
||||||
<el-option label="待询价" :value="10" />
|
|
||||||
<el-option label="询价中" :value="20" />
|
|
||||||
<el-option label="已完成" :value="30" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="创建人" prop="creator">
|
|
||||||
<el-select v-model="queryParams.creator" clearable filterable placeholder="请选择创建人" style="width:100%">
|
|
||||||
<el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" />
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="resetQuery">重置</el-button>
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
@@ -110,11 +76,73 @@
|
|||||||
<!-- 自动比价弹窗 -->
|
<!-- 自动比价弹窗 -->
|
||||||
<AutoCompareDialog ref="autoCompareRef" @success="getList" />
|
<AutoCompareDialog ref="autoCompareRef" @success="getList" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<div v-else>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
|
<el-form-item label="比价单号" prop="no"><el-input v-model="queryParams.no" placeholder="请输入比价单号" clearable @keyup.enter="handleQuery" class="!w-240px" /></el-form-item>
|
||||||
|
<el-form-item label="产品" prop="productId"><el-select v-model="queryParams.productId" clearable filterable placeholder="请选择产品" class="!w-240px"><el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="询价时间" prop="inquiryTime"><el-date-picker v-model="queryParams.inquiryTime" 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-240px" /></el-form-item>
|
||||||
|
<el-form-item label="状态" prop="status"><el-select v-model="queryParams.status" placeholder="请选择状态" clearable class="!w-240px"><el-option label="待询价" :value="10" /><el-option label="询价中" :value="20" /><el-option label="已完成" :value="30" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="创建人" prop="creator"><el-select v-model="queryParams.creator" clearable filterable placeholder="请选择创建人" class="!w-240px"><el-option v-for="item in userList" :key="item.id" :label="item.nickname" :value="item.id" /></el-select></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 type="primary" plain @click="openForm('create')" v-hasPermi="['erp:purchase-inquiry:create']"><Icon icon="ep:plus" class="mr-5px" /> 新增</el-button>
|
||||||
|
<el-button type="success" plain @click="handleExport" :loading="exportLoading" v-hasPermi="['erp:purchase-inquiry:export']"><Icon icon="ep:download" class="mr-5px" /> 导出</el-button>
|
||||||
|
<el-button type="danger" plain @click="handleDelete(selectionList.map((item) => item.id))" v-hasPermi="['erp:purchase-inquiry:delete']" :disabled="selectionList.length === 0"><Icon icon="ep:delete" class="mr-5px" /> 删除</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange" @row-click="handleRowClick">
|
||||||
|
<el-table-column width="30" label="选择" type="selection" />
|
||||||
|
<el-table-column min-width="180" label="比价单号" align="center" prop="no" />
|
||||||
|
<el-table-column label="产品名称" align="center" prop="productName" min-width="150" />
|
||||||
|
<el-table-column label="需求数量" align="center" prop="requireCount" :formatter="erpCountTableColumnFormatter" width="100" />
|
||||||
|
<el-table-column label="预算金额" align="center" prop="budgetPrice" :formatter="erpPriceTableColumnFormatter" width="120" />
|
||||||
|
<el-table-column label="询价时间" align="center" prop="inquiryTime" :formatter="dateFormatter2" width="120" sortable />
|
||||||
|
<el-table-column label="截止日期" align="center" prop="deadline" :formatter="dateFormatter2" width="120" sortable />
|
||||||
|
<el-table-column label="报价数量" align="center" prop="quoteCount" width="100" />
|
||||||
|
<el-table-column label="最低报价" align="center" prop="minQuotePrice" :formatter="erpPriceTableColumnFormatter" width="120" />
|
||||||
|
<el-table-column label="创建人" align="center" prop="creatorName" width="100" />
|
||||||
|
<el-table-column label="状态" align="center" fixed="right" width="100" prop="status">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag v-if="scope.row.status === 10" type="info">待询价</el-tag>
|
||||||
|
<el-tag v-else-if="scope.row.status === 20" type="warning">询价中</el-tag>
|
||||||
|
<el-tag v-else-if="scope.row.status === 30" type="success">已完成</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" fixed="right" width="300">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link @click="openForm('detail', scope.row.id)" v-hasPermi="['erp:purchase-inquiry:query']">详情</el-button>
|
||||||
|
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:purchase-inquiry:update']" :disabled="scope.row.status === 30">编辑</el-button>
|
||||||
|
<el-button link type="primary" @click="handleUpdateStatus(scope.row.id, 20)" v-hasPermi="['erp:purchase-inquiry:update-status']" v-if="scope.row.status === 10">询价</el-button>
|
||||||
|
<el-button link type="warning" @click="openAutoCompare(scope.row.id)" v-hasPermi="['erp:purchase-inquiry:update']" v-if="scope.row.status === 20 && scope.row.quoteCount >= 2">比价</el-button>
|
||||||
|
<el-button link type="success" @click="handleUpdateStatus(scope.row.id, 30)" v-hasPermi="['erp:purchase-inquiry:update-status']" v-if="scope.row.status === 20">完成</el-button>
|
||||||
|
<el-button link type="danger" @click="handleDelete([scope.row.id])" v-hasPermi="['erp:purchase-inquiry:delete']">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<InquiryForm ref="formRef" @success="getList" />
|
||||||
|
<!-- 自动比价弹窗 -->
|
||||||
|
<AutoCompareDialog ref="autoCompareRef" @success="getList" />
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate, dateFormatter2 } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { PurchaseInquiryApi, PurchaseInquiryVO } from '@/api/erp/purchase/inquiry'
|
import { PurchaseInquiryApi, PurchaseInquiryVO } from '@/api/erp/purchase/inquiry'
|
||||||
import InquiryForm from './InquiryForm.vue'
|
import InquiryForm from './InquiryForm.vue'
|
||||||
@@ -122,11 +150,15 @@ import AutoCompareDialog from './AutoCompareDialog.vue'
|
|||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
import { UserVO } from '@/api/system/user'
|
import { UserVO } from '@/api/system/user'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import { erpCountInputFormatter, erpPriceInputFormatter } from '@/utils'
|
import { erpCountInputFormatter, erpPriceInputFormatter, erpCountTableColumnFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 采购比价列表 */
|
/** ERP 采购比价列表 */
|
||||||
defineOptions({ name: 'ErpPurchaseInquiry' })
|
defineOptions({ name: 'ErpPurchaseInquiry' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -220,7 +252,10 @@ const handleExport = async () => {
|
|||||||
try {
|
try {
|
||||||
await message.exportConfirm()
|
await message.exportConfirm()
|
||||||
exportLoading.value = true
|
exportLoading.value = true
|
||||||
const data = await PurchaseInquiryApi.exportPurchaseInquiry(queryParams)
|
const exportParams = selectionList.value.length > 0
|
||||||
|
? { ids: selectionList.value.map(item => item.id) }
|
||||||
|
: queryParams
|
||||||
|
const data = await PurchaseInquiryApi.exportPurchaseInquiry(exportParams)
|
||||||
download.excel(data, '采购比价.xls')
|
download.excel(data, '采购比价.xls')
|
||||||
} catch {
|
} catch {
|
||||||
} finally {
|
} finally {
|
||||||
@@ -228,14 +263,65 @@ const handleExport = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 选中操作 */
|
||||||
|
const selectionList = ref<PurchaseInquiryVO[]>([])
|
||||||
|
const handleSelectionChange = (rows: PurchaseInquiryVO[]) => {
|
||||||
|
selectionList.value = rows
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 行点击操作 */
|
||||||
|
const handleRowClick = (row: PurchaseInquiryVO, column: any, event: MouseEvent) => {
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (target.tagName === 'BUTTON' || target.tagName === 'A' || target.tagName === 'I' || target.tagName === 'svg' || target.closest('button') || target.closest('a') || target.closest('.el-button') || target.closest('.el-checkbox')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (row.status === 30) {
|
||||||
|
openForm('detail', row.id)
|
||||||
|
} else {
|
||||||
|
openForm('update', row.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getList()
|
await getList()
|
||||||
productList.value = await ProductApi.getProductSimpleList()
|
productList.value = await ProductApi.getProductSimpleList()
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 快捷分类筛选 */
|
||||||
|
const handleQuickFilter = (status: number | undefined) => {
|
||||||
|
queryParams.status = status
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.mobile-header__quick-filter {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.quick-filter-item {
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #909399;
|
||||||
|
background: transparent;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #fff;
|
||||||
|
background: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.mobile-purchase-inquiry {
|
.mobile-purchase-inquiry {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
|
|||||||
@@ -1,136 +1,30 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer
|
<!-- 移动端布局 -->
|
||||||
v-model="dialogVisible"
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
:title="dialogTitle"
|
|
||||||
direction="rtl"
|
|
||||||
size="100%"
|
|
||||||
:close-on-press-escape="true"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
:append-to-body="true"
|
|
||||||
class="mobile-form-drawer"
|
|
||||||
>
|
|
||||||
<div class="mobile-form" v-loading="formLoading">
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
<el-form
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" :disabled="disabled">
|
||||||
ref="formRef"
|
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
label-position="top"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
<!-- 基本信息 -->
|
|
||||||
<div class="mobile-form__section">
|
<div class="mobile-form__section">
|
||||||
<div class="mobile-form__section-title">基本信息</div>
|
<div class="mobile-form__section-title">基本信息</div>
|
||||||
<el-form-item label="订单单号" prop="no">
|
<el-form-item label="订单单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item>
|
||||||
<el-input disabled v-model="formData.no" placeholder="保存时自动生成" />
|
<el-form-item label="订单时间" prop="orderTime"><el-date-picker v-model="formData.orderTime" type="date" value-format="x" placeholder="选择订单时间" style="width:100%" /></el-form-item>
|
||||||
</el-form-item>
|
<el-form-item label="关联请购单" prop="purchaseRequisitionNo"><el-input readonly><template #prefix><el-link v-if="formData.purchaseRequisitionId" type="primary" :underline="false" @click.stop="openRequisitionDetail">{{ formData.purchaseRequisitionNo }}</el-link></template><template #append><el-button @click="openRequisitionSelect"><Icon icon="ep:search" /></el-button></template></el-input></el-form-item>
|
||||||
<el-form-item label="订单时间" prop="orderTime">
|
<el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable placeholder="请选择供应商" style="width:100%"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select><div class="mobile-form__tip">*如果没有供应商请忽略此项填写</div></el-form-item>
|
||||||
<el-date-picker
|
<el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="3" placeholder="请输入备注" /></el-form-item>
|
||||||
v-model="formData.orderTime"
|
<el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item>
|
||||||
type="date"
|
|
||||||
value-format="x"
|
|
||||||
placeholder="选择订单时间"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="关联请购单" prop="purchaseRequisitionNo">
|
|
||||||
<div class="mobile-form__requisition">
|
|
||||||
<el-link
|
|
||||||
v-if="formData.purchaseRequisitionNo && formData.purchaseRequisitionId"
|
|
||||||
type="primary"
|
|
||||||
:underline="false"
|
|
||||||
@click.stop="openRequisitionDetail"
|
|
||||||
>
|
|
||||||
{{ formData.purchaseRequisitionNo }}
|
|
||||||
</el-link>
|
|
||||||
<span v-else class="mobile-form__requisition-empty">未选择</span>
|
|
||||||
<el-button size="small" @click="openRequisitionSelect" :disabled="disabled">选择</el-button>
|
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="供应商" prop="supplierId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.supplierId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择供应商"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in supplierList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
<div class="mobile-form__tip">*如果没有供应商请忽略此项填写</div>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
v-model="formData.remark"
|
|
||||||
:rows="3"
|
|
||||||
placeholder="请输入备注"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="附件" prop="fileUrl">
|
|
||||||
<UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 产品清单 -->
|
|
||||||
<div class="mobile-form__section">
|
<div class="mobile-form__section">
|
||||||
<div class="mobile-form__section-title">订单产品清单</div>
|
<div class="mobile-form__section-title">订单产品清单</div>
|
||||||
<PurchaseOrderItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" />
|
<PurchaseOrderItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 费用信息 -->
|
|
||||||
<div class="mobile-form__section">
|
<div class="mobile-form__section">
|
||||||
<div class="mobile-form__section-title">费用信息</div>
|
<div class="mobile-form__section-title">费用信息</div>
|
||||||
<el-form-item label="优惠率(%)" prop="discountPercent">
|
<el-form-item label="优惠率(%)" prop="discountPercent"><el-input-number v-model="formData.discountPercent" controls-position="right" :min="0" :precision="2" placeholder="请输入优惠率" style="width:100%" /></el-form-item>
|
||||||
<el-input-number
|
<el-form-item label="付款优惠" prop="discountPrice"><el-input disabled v-model="formData.discountPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
v-model="formData.discountPercent"
|
<el-form-item label="优惠后金额"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
controls-position="right"
|
<el-form-item label="结算账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" style="width:100%"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
:min="0"
|
<el-form-item label="支付订金" prop="depositPrice"><el-input-number v-model="formData.depositPrice" controls-position="right" :min="0" :precision="2" placeholder="请输入支付订金" style="width:100%" /></el-form-item>
|
||||||
:precision="2"
|
|
||||||
placeholder="请输入优惠率"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="付款优惠" prop="discountPrice">
|
|
||||||
<el-input disabled v-model="formData.discountPrice" :formatter="erpPriceInputFormatter" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="优惠后金额">
|
|
||||||
<el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="结算账户" prop="accountId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.accountId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择结算账户"
|
|
||||||
style="width: 100%"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in accountList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="支付订金" prop="depositPrice">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.depositPrice"
|
|
||||||
controls-position="right"
|
|
||||||
:min="0"
|
|
||||||
:precision="2"
|
|
||||||
placeholder="请输入支付订金"
|
|
||||||
style="width: 100%"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
<!-- 底部操作按钮 -->
|
|
||||||
<div class="mobile-form__footer">
|
<div class="mobile-form__footer">
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
@@ -138,13 +32,46 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible" width="1440">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading" :disabled="disabled">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8"><el-form-item label="订单单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="订单时间" prop="orderTime"><el-date-picker v-model="formData.orderTime" type="date" value-format="x" placeholder="选择订单时间" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="关联请购单" prop="purchaseRequisitionNo"><el-input readonly><template #prefix><el-link v-if="formData.purchaseRequisitionNo && formData.purchaseRequisitionId" type="primary" :underline="false" @click.stop="openRequisitionDetail" class="requisition-link">{{ formData.purchaseRequisitionNo }}</el-link></template><template #append><el-button @click="openRequisitionSelect" :disabled="disabled"><Icon icon="ep:search" /> 选择</el-button></template></el-input></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable placeholder="请选择供应商" class="!w-1/1"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select><div style="margin-top:4px;font-size:12px;color:var(--el-color-danger);">*如果没有供应商请忽略此项填写</div></el-form-item></el-col>
|
||||||
|
<el-col :span="16"><el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="3" placeholder="请输入备注(如果有额外的内容填写,请在此文本框里面填写)" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item></el-col>
|
||||||
|
</el-row>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
||||||
|
<el-tab-pane label="订单产品清单" name="item"><PurchaseOrderItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" /></el-tab-pane>
|
||||||
|
</el-tabs>
|
||||||
|
</ContentWrap>
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="8"><el-form-item label="优惠率(%)" prop="discountPercent"><el-input-number v-model="formData.discountPercent" controls-position="right" :min="0" :precision="2" placeholder="请输入优惠率" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="付款优惠" prop="discountPrice"><el-input disabled v-model="formData.discountPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="优惠后金额"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="结算账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" class="!w-1/1"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
|
<el-col :span="8"><el-form-item label="支付订金" prop="depositPrice"><el-input-number v-model="formData.depositPrice" controls-position="right" :min="0" :precision="2" placeholder="请输入支付订金" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
|
<el-button @click="handlePrint" type="warning">
|
||||||
|
<Icon icon="ep:printer" class="mr-5px" /> 打 印
|
||||||
|
</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
<!-- 请购单选择弹窗 -->
|
<!-- 请购单选择弹窗 -->
|
||||||
<PurchaseRequisitionTableSelect ref="requisitionSelectRef" @change="onRequisitionSelected" />
|
<PurchaseRequisitionTableSelect ref="requisitionSelectRef" @change="onRequisitionSelected" />
|
||||||
<!-- 请购单详情弹窗 -->
|
<!-- 请购单详情弹窗 -->
|
||||||
<PurchaseRequisitionForm ref="requisitionFormRef" />
|
<PurchaseRequisitionForm ref="requisitionFormRef" />
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { nextTick } from 'vue'
|
import { nextTick, computed } from 'vue'
|
||||||
import { PurchaseOrderApi, PurchaseOrderVO } from '@/api/erp/purchase/order'
|
import { PurchaseOrderApi, PurchaseOrderVO } from '@/api/erp/purchase/order'
|
||||||
import PurchaseOrderItemForm from './components/PurchaseOrderItemForm.vue'
|
import PurchaseOrderItemForm from './components/PurchaseOrderItemForm.vue'
|
||||||
import PurchaseRequisitionTableSelect from './components/PurchaseRequisitionTableSelect.vue'
|
import PurchaseRequisitionTableSelect from './components/PurchaseRequisitionTableSelect.vue'
|
||||||
@@ -154,10 +81,14 @@ import { erpPriceInputFormatter, erpPriceMultiply } from '@/utils'
|
|||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
||||||
import { PurchaseRequisitionApi, PurchaseRequisition } from '@/api/erp/purchaserequisition'
|
import { PurchaseRequisitionApi, PurchaseRequisition } from '@/api/erp/purchaserequisition'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 销售订单表单 */
|
/** ERP 采购订单表单 */
|
||||||
defineOptions({ name: 'PurchaseOrderForm' })
|
defineOptions({ name: 'PurchaseOrderForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -215,7 +146,7 @@ const accountList = ref<AccountVO[]>([]) // 账户列表
|
|||||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
||||||
const requisitionSelectRef = ref() // 请购单选择弹窗Ref
|
const requisitionSelectRef = ref() // 请购单选择弹窗Ref
|
||||||
const requisitionFormRef = ref() // 请购单详情弹窗Ref
|
const requisitionFormRef = ref() // 请购单详情弹窗Ref
|
||||||
|
const subTabsName = ref('item')
|
||||||
const itemFormRef = ref()
|
const itemFormRef = ref()
|
||||||
|
|
||||||
/** 计算 discountPrice、totalPrice 价格 */
|
/** 计算 discountPrice、totalPrice 价格 */
|
||||||
@@ -379,6 +310,161 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/** 打印当前页面数据 */
|
||||||
|
const handlePrint = () => {
|
||||||
|
// 获取供应商名称
|
||||||
|
const supplier = supplierList.value.find((item) => item.id === formData.value.supplierId)
|
||||||
|
const supplierName = supplier ? supplier.name : ''
|
||||||
|
// 获取结算账户名称
|
||||||
|
const account = accountList.value.find((item) => item.id === formData.value.accountId)
|
||||||
|
const accountName = account ? account.name : ''
|
||||||
|
// 格式化订单时间
|
||||||
|
const orderTime = formData.value.orderTime
|
||||||
|
? new Date(formData.value.orderTime).toLocaleDateString('zh-CN')
|
||||||
|
: ''
|
||||||
|
|
||||||
|
// 构建产品清单表格
|
||||||
|
let itemsTableHtml = ''
|
||||||
|
if (formData.value.items && formData.value.items.length > 0) {
|
||||||
|
// 计算合计
|
||||||
|
const totalCount = formData.value.items.reduce((sum, item) => sum + (item.count || 0), 0)
|
||||||
|
const totalProductPrice = formData.value.items.reduce((sum, item) => sum + (item.totalProductPrice || 0), 0)
|
||||||
|
const totalTaxPrice = formData.value.items.reduce((sum, item) => sum + (item.taxPrice || 0), 0)
|
||||||
|
const totalPriceSum = formData.value.items.reduce((sum, item) => sum + (item.totalPrice || 0), 0)
|
||||||
|
|
||||||
|
itemsTableHtml = `
|
||||||
|
<table border="1" cellpadding="6" cellspacing="0" style="width:100%; border-collapse: collapse; margin-top: 10px; font-size: 12px;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background-color: #f5f5f5;">
|
||||||
|
<th>序号</th>
|
||||||
|
<th>产品名称</th>
|
||||||
|
<th>库存</th>
|
||||||
|
<th>条码</th>
|
||||||
|
<th>单位</th>
|
||||||
|
<th>数量</th>
|
||||||
|
<th>产品单价</th>
|
||||||
|
<th>金额</th>
|
||||||
|
<th>税率(%)</th>
|
||||||
|
<th>税额</th>
|
||||||
|
<th>税额合计</th>
|
||||||
|
<th>备注</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
${formData.value.items
|
||||||
|
.map(
|
||||||
|
(item, index) => `
|
||||||
|
<tr>
|
||||||
|
<td style="text-align: center;">${index + 1}</td>
|
||||||
|
<td>${item.productName || ''}</td>
|
||||||
|
<td style="text-align: right;">${item.stockCount || 0}</td>
|
||||||
|
<td>${item.productBarCode || ''}</td>
|
||||||
|
<td style="text-align: center;">${item.productUnitName || ''}</td>
|
||||||
|
<td style="text-align: right;">${item.count || 0}</td>
|
||||||
|
<td style="text-align: right;">${item.productPrice || 0}</td>
|
||||||
|
<td style="text-align: right;">${item.totalProductPrice || 0}</td>
|
||||||
|
<td style="text-align: right;">${item.taxPercent || 0}</td>
|
||||||
|
<td style="text-align: right;">${item.taxPrice || 0}</td>
|
||||||
|
<td style="text-align: right;">${item.totalPrice || 0}</td>
|
||||||
|
<td>${item.remark || ''}</td>
|
||||||
|
</tr>
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.join('')}
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr style="background-color: #f9f9f9; font-weight: bold;">
|
||||||
|
<td style="text-align: center;">合计</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td style="text-align: right;">${totalCount}</td>
|
||||||
|
<td></td>
|
||||||
|
<td style="text-align: right;">${totalProductPrice}</td>
|
||||||
|
<td></td>
|
||||||
|
<td style="text-align: right;">${totalTaxPrice}</td>
|
||||||
|
<td style="text-align: right;">${totalPriceSum}</td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建打印内容
|
||||||
|
const printContent = `
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>采购订单打印</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Microsoft YaHei', Arial, sans-serif; padding: 20px; }
|
||||||
|
h1 { text-align: center; margin-bottom: 20px; }
|
||||||
|
.info-row { display: flex; flex-wrap: wrap; margin-bottom: 10px; }
|
||||||
|
.info-item { width: 33%; margin-bottom: 10px; }
|
||||||
|
.info-label { font-weight: bold; color: #666; }
|
||||||
|
.info-value { margin-left: 10px; }
|
||||||
|
.section-title { font-size: 16px; font-weight: bold; margin: 20px 0 10px; border-bottom: 1px solid #ddd; padding-bottom: 5px; }
|
||||||
|
table { font-size: 14px; }
|
||||||
|
th, td { padding: 8px; text-align: left; }
|
||||||
|
@media print {
|
||||||
|
body { padding: 0; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>采购订单</h1>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-item"><span class="info-label">订单单号:</span><span class="info-value">${formData.value.no || '(保存后生成)'}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">订单时间:</span><span class="info-value">${orderTime}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">关联请购单:</span><span class="info-value">${formData.value.purchaseRequisitionNo || ''}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">供应商:</span><span class="info-value">${supplierName}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">结算账户:</span><span class="info-value">${accountName}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">支付订金:</span><span class="info-value">${formData.value.depositPrice || 0}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="info-row">
|
||||||
|
<div class="info-item" style="width: 100%;"><span class="info-label">备注:</span><span class="info-value">${formData.value.remark || ''}</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="section-title">订单产品清单</div>
|
||||||
|
${itemsTableHtml}
|
||||||
|
<div class="info-row" style="margin-top: 20px;">
|
||||||
|
<div class="info-item"><span class="info-label">优惠率(%):</span><span class="info-value">${formData.value.discountPercent || 0}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">付款优惠:</span><span class="info-value">${formData.value.discountPrice || 0}</span></div>
|
||||||
|
<div class="info-item"><span class="info-label">优惠后金额:</span><span class="info-value">${formData.value.totalPrice || 0}</span></div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
// 使用iframe打印,避免被浏览器拦截
|
||||||
|
const iframe = document.createElement('iframe')
|
||||||
|
iframe.style.position = 'absolute'
|
||||||
|
iframe.style.width = '0'
|
||||||
|
iframe.style.height = '0'
|
||||||
|
iframe.style.border = 'none'
|
||||||
|
iframe.style.left = '-9999px'
|
||||||
|
document.body.appendChild(iframe)
|
||||||
|
|
||||||
|
const iframeDoc = iframe.contentWindow?.document
|
||||||
|
if (iframeDoc) {
|
||||||
|
iframeDoc.open()
|
||||||
|
iframeDoc.write(printContent)
|
||||||
|
iframeDoc.close()
|
||||||
|
|
||||||
|
// 等待内容加载完成后打印
|
||||||
|
iframe.contentWindow?.focus()
|
||||||
|
setTimeout(() => {
|
||||||
|
iframe.contentWindow?.print()
|
||||||
|
// 打印完成后移除iframe
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(iframe)
|
||||||
|
}, 1000)
|
||||||
|
}, 250)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<!-- 可入库的订单列表 -->
|
<!-- 可入库的订单列表 -->
|
||||||
<template>
|
<template>
|
||||||
|
<!-- 移动端布局 -->
|
||||||
<el-drawer
|
<el-drawer
|
||||||
|
v-if="isMobile"
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="选择采购订单(仅展示可入库)"
|
title="选择采购订单(仅展示可入库)"
|
||||||
direction="rtl"
|
direction="rtl"
|
||||||
@@ -111,15 +113,50 @@
|
|||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else title="选择采购订单(仅展示可入库)" v-model="dialogVisible" :appendToBody="true" :scroll="true" width="1080">
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px" class="-mb-15px">
|
||||||
|
<el-form-item label="订单单号" prop="no"><el-input v-model="queryParams.no" placeholder="请输入订单单号" clearable @keyup.enter="handleQuery" class="!w-160px" /></el-form-item>
|
||||||
|
<el-form-item label="产品" prop="productId"><el-select v-model="queryParams.productId" clearable filterable placeholder="请选择产品" class="!w-160px"><el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="订单时间" prop="orderTime"><el-date-picker v-model="queryParams.orderTime" 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-160px" /></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-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||||
|
<el-table-column align="center" width="65"><template #default="{ row }"><el-radio :value="row.id" v-model="currentRowValue" @change="handleCurrentChange(row)"> </el-radio></template></el-table-column>
|
||||||
|
<el-table-column label="订单单号" align="center" prop="no" min-width="180" />
|
||||||
|
<el-table-column label="供应商" align="center" prop="supplierName" />
|
||||||
|
<el-table-column label="产品信息" align="center" prop="productNames" min-width="200" />
|
||||||
|
<el-table-column label="订单时间" align="center" prop="orderTime" :formatter="dateFormatter2" width="120px" />
|
||||||
|
<el-table-column label="总数量" align="center" prop="totalCount" :formatter="erpCountTableColumnFormatter" />
|
||||||
|
<el-table-column label="入库数量" align="center" prop="inCount" :formatter="erpCountTableColumnFormatter" />
|
||||||
|
<el-table-column label="金额合计" align="center" prop="totalProductPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
|
<el-table-column label="含税金额" align="center" prop="totalPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
|
</el-table>
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
<template #footer>
|
||||||
|
<el-button :disabled="!currentRow" type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
import { Search, Filter } from '@element-plus/icons-vue'
|
import { Search, Filter } from '@element-plus/icons-vue'
|
||||||
import { PurchaseOrderApi, PurchaseOrderVO } from '@/api/erp/purchase/order'
|
import { PurchaseOrderApi, PurchaseOrderVO } from '@/api/erp/purchase/order'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate, dateFormatter2 } from '@/utils/formatTime'
|
||||||
import { erpCountInputFormatter, erpPriceInputFormatter } from '@/utils'
|
import { erpCountInputFormatter, erpPriceInputFormatter, erpCountTableColumnFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'ErpPurchaseOrderOutEnableList' })
|
defineOptions({ name: 'ErpPurchaseOrderInEnableList' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const list = ref<PurchaseOrderVO[]>([]) // 列表的数据
|
const list = ref<PurchaseOrderVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form
|
<!-- 移动端布局 -->
|
||||||
ref="formRef"
|
<el-form v-if="isMobile" ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-position="top" :inline-message="true" :disabled="disabled">
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
v-loading="formLoading"
|
|
||||||
label-position="top"
|
|
||||||
:inline-message="true"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
<div class="mobile-item-list">
|
<div class="mobile-item-list">
|
||||||
<div
|
<div
|
||||||
v-for="(row, $index) in formData"
|
v-for="(row, $index) in formData"
|
||||||
@@ -139,11 +132,32 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 添加按钮 -->
|
<!-- 添加按钮 -->
|
||||||
<div class="mobile-item-add" v-if="!disabled">
|
<div class="mobile-item-add" v-if="!disabled"><el-button @click="handleAdd" round>+ 添加采购产品</el-button></div>
|
||||||
<el-button @click="handleAdd" round>+ 添加采购产品</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="0px" :inline-message="true" :disabled="disabled">
|
||||||
|
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px">
|
||||||
|
<el-table-column label="序号" type="index" align="center" width="60" />
|
||||||
|
<el-table-column label="产品名称" min-width="180"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.productId`" :rules="formRules.productId" class="mb-0px!"><el-select v-model="row.productId" clearable filterable @change="onChangeProduct($event, row)" placeholder="请选择产品"><el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="库存" min-width="100"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.stockCount" :formatter="erpCountInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="条码" min-width="150"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.productBarCode" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="单位" min-width="80"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.productUnitName" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="供应商" min-width="150"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.supplierName" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="数量" prop="count" fixed="right" min-width="140"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.count`" :rules="formRules.count" class="mb-0px!"><el-input-number v-model="row.count" controls-position="right" :min="0.0001" :precision="4" class="!w-100%" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="产品单价" fixed="right" min-width="120"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.productPrice`" :rules="formRules.productPrice" class="mb-0px!"><el-input-number v-model="row.productPrice" controls-position="right" :min="0.0001" :precision="4" class="!w-100%" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="金额" prop="totalProductPrice" fixed="right" min-width="100"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.totalProductPrice`" class="mb-0px!"><el-input disabled v-model="row.totalProductPrice" :formatter="erpPriceInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="税率(%)" fixed="right" min-width="115"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.taxPercent`" class="mb-0px!"><el-input-number v-model="row.taxPercent" controls-position="right" :min="0" :precision="4" class="!w-100%" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="税额" prop="taxPrice" fixed="right" min-width="120"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.taxPrice`" class="mb-0px!"><el-input disabled v-model="row.taxPrice" :formatter="erpPriceInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="税额合计" prop="totalPrice" fixed="right" min-width="120"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.totalPrice`" class="mb-0px!"><el-input-number v-model="row.totalPrice" controls-position="right" :min="0" :precision="4" class="!w-100%" @change="onChangeTotalPrice(row)" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.remark`" class="mb-0px!"><el-input v-model="row.remark" placeholder="请输入备注" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column align="center" fixed="right" label="操作" width="60"><template #default="{ $index }"><el-button @click="handleDelete($index)" link>—</el-button></template></el-table-column>
|
||||||
|
</el-table>
|
||||||
|
</el-form>
|
||||||
|
<el-row justify="center" class="mt-3" v-if="!disabled"><el-button @click="handleAdd" round>+ 添加采购产品</el-button></el-row>
|
||||||
|
</template>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
@@ -155,6 +169,10 @@ import {
|
|||||||
getSumValue
|
getSumValue
|
||||||
} from '@/utils'
|
} from '@/utils'
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
@@ -231,7 +249,7 @@ watch(
|
|||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 合计 */
|
/** 合计 - 移动端 */
|
||||||
const summaryData = computed(() => {
|
const summaryData = computed(() => {
|
||||||
const data = formData.value || []
|
const data = formData.value || []
|
||||||
return {
|
return {
|
||||||
@@ -242,6 +260,20 @@ const summaryData = computed(() => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 合计 - PC端 */
|
||||||
|
const getSummaries = (param: any) => {
|
||||||
|
const { columns, data } = param
|
||||||
|
const sums: string[] = []
|
||||||
|
columns.forEach((column, index: number) => {
|
||||||
|
if (index === 0) { sums[index] = '合计'; return }
|
||||||
|
if (['count', 'totalProductPrice', 'taxPrice', 'totalPrice'].includes(column.property)) {
|
||||||
|
const sum = getSumValue(data.map((item) => Number(item[column.property])))
|
||||||
|
sums[index] = column.property === 'count' ? erpCountInputFormatter(sum) : erpPriceInputFormatter(sum)
|
||||||
|
} else { sums[index] = '' }
|
||||||
|
})
|
||||||
|
return sums
|
||||||
|
}
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
/** 新增按钮操作 */
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
const row: any = {
|
const row: any = {
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
<!-- 可退货的订单列表 -->
|
<!-- 可退货的订单列表 -->
|
||||||
<template>
|
<template>
|
||||||
|
<!-- 移动端布局 -->
|
||||||
<el-drawer
|
<el-drawer
|
||||||
|
v-if="isMobile"
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="选择采购订单(仅展示可退货)"
|
title="选择采购订单(仅展示可退货)"
|
||||||
direction="rtl"
|
direction="rtl"
|
||||||
@@ -115,17 +117,53 @@
|
|||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else title="选择采购订单(仅展示可退货)" v-model="dialogVisible" :appendToBody="true" :scroll="true" width="1100">
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px" class="-mb-15px">
|
||||||
|
<el-form-item label="订单单号" prop="no"><el-input v-model="queryParams.no" placeholder="请输入订单单号" clearable @keyup.enter="handleQuery" class="!w-160px" /></el-form-item>
|
||||||
|
<el-form-item label="产品" prop="productId"><el-select v-model="queryParams.productId" clearable filterable placeholder="请选择产品" class="!w-160px"><el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="订单时间" prop="orderTime"><el-date-picker v-model="queryParams.orderTime" 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-160px" /></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-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||||
|
<el-table-column align="center" width="65"><template #default="{ row }"><el-radio :value="row.id" v-model="currentRowValue" @change="handleCurrentChange(row)"> </el-radio></template></el-table-column>
|
||||||
|
<el-table-column label="订单单号" align="center" prop="no" min-width="180" />
|
||||||
|
<el-table-column label="供应商" align="center" prop="supplierName" />
|
||||||
|
<el-table-column label="产品信息" align="center" prop="productNames" min-width="200" />
|
||||||
|
<el-table-column label="订单时间" align="center" prop="orderTime" :formatter="dateFormatter2" width="120px" />
|
||||||
|
<el-table-column label="总数量" align="center" prop="totalCount" :formatter="erpCountTableColumnFormatter" />
|
||||||
|
<el-table-column label="入库数量" align="center" prop="inCount" :formatter="erpCountTableColumnFormatter" />
|
||||||
|
<el-table-column label="退货数量" align="center" prop="returnCount" :formatter="erpCountTableColumnFormatter" />
|
||||||
|
<el-table-column label="金额合计" align="center" prop="totalProductPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
|
<el-table-column label="含税金额" align="center" prop="totalPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
|
</el-table>
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
<template #footer>
|
||||||
|
<el-button :disabled="!currentRow" type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
import { Search, Filter } from '@element-plus/icons-vue'
|
import { Search, Filter } from '@element-plus/icons-vue'
|
||||||
import { PurchaseOrderApi, PurchaseOrderVO } from '@/api/erp/purchase/order'
|
import { PurchaseOrderApi, PurchaseOrderVO } from '@/api/erp/purchase/order'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate, dateFormatter2 } from '@/utils/formatTime'
|
||||||
import { erpCountInputFormatter, erpPriceInputFormatter } from '@/utils'
|
import { erpCountInputFormatter, erpPriceInputFormatter, erpCountTableColumnFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'PurchaseOrderReturnEnableList' })
|
defineOptions({ name: 'PurchaseOrderReturnEnableList' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const list = ref<PurchaseOrderVO[]>([]) // 列表的数据
|
const list = ref<PurchaseOrderVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
const loading = ref(false) // 列表的加载中
|
const loading = ref(false) // 列表的加载中
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!-- 移动端布局 -->
|
||||||
<el-drawer
|
<el-drawer
|
||||||
|
v-if="isMobile"
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
title="选择请购单"
|
title="选择请购单"
|
||||||
direction="rtl"
|
direction="rtl"
|
||||||
@@ -103,16 +105,52 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else title="选择请购单" v-model="dialogVisible" :appendToBody="true" :scroll="true" width="1080">
|
||||||
|
<ContentWrap>
|
||||||
|
<el-form :model="queryParams" ref="filterFormRef" :inline="true" label-width="68px" class="-mb-15px">
|
||||||
|
<el-form-item label="请购单号" prop="no"><el-input v-model="queryParams.no" placeholder="请输入请购单号" clearable @keyup.enter="handleQuery" class="!w-160px" /></el-form-item>
|
||||||
|
<el-form-item label="请购人" prop="requesterName"><el-input v-model="queryParams.requesterName" placeholder="请输入请购人" clearable class="!w-160px" /></el-form-item>
|
||||||
|
<el-form-item label="请购时间" prop="requestTime"><el-date-picker v-model="queryParams.requestTime" 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-160px" /></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-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true">
|
||||||
|
<el-table-column align="center" width="65"><template #default="{ row }"><el-radio :value="row.id" v-model="selectedId" @change="handleSingleSelected(row)"> </el-radio></template></el-table-column>
|
||||||
|
<el-table-column label="请购单号" align="center" prop="no" min-width="180" />
|
||||||
|
<el-table-column label="类型" align="center" prop="type" width="80"><template #default="scope"><el-tag size="small" :type="getTypeTagType(scope.row.type)">{{ getTypeLabel(scope.row.type) }}</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="优先级" align="center" prop="priority" width="80"><template #default="scope"><el-tag size="small" :type="getPriorityTagType(scope.row.priority)">{{ getPriorityLabel(scope.row.priority) }}</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="请购人" align="center" prop="requesterNickname" width="100" />
|
||||||
|
<el-table-column label="请购部门" align="center" prop="requesterDeptName" width="120" />
|
||||||
|
<el-table-column label="请购时间" align="center" prop="requestTime" :formatter="dateFormatter2" width="120px" />
|
||||||
|
<el-table-column label="合计数量" align="center" prop="totalCount" :formatter="erpCountTableColumnFormatter" />
|
||||||
|
<el-table-column label="合计金额" align="center" prop="totalPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
|
</el-table>
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
<template #footer>
|
||||||
|
<el-button :disabled="!currentRow" type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
import { Search, Filter } from '@element-plus/icons-vue'
|
import { Search, Filter } from '@element-plus/icons-vue'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate, dateFormatter2 } from '@/utils/formatTime'
|
||||||
|
import { erpCountTableColumnFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||||
import { PurchaseRequisitionApi, PurchaseRequisition } from '@/api/erp/purchaserequisition'
|
import { PurchaseRequisitionApi, PurchaseRequisition } from '@/api/erp/purchaserequisition'
|
||||||
import { CHANGE_EVENT } from 'element-plus'
|
import { CHANGE_EVENT } from 'element-plus'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'PurchaseRequisitionTableSelect' })
|
defineOptions({ name: 'PurchaseRequisitionTableSelect' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const list = ref<PurchaseRequisition[]>([])
|
const list = ref<PurchaseRequisition[]>([])
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
@@ -128,6 +166,7 @@ const queryParams = ref({
|
|||||||
requestTime: []
|
requestTime: []
|
||||||
})
|
})
|
||||||
const selectedId = ref()
|
const selectedId = ref()
|
||||||
|
const currentRow = ref<PurchaseRequisition>()
|
||||||
|
|
||||||
const formatDate2 = (date: any) => {
|
const formatDate2 = (date: any) => {
|
||||||
if (!date) return '-'
|
if (!date) return '-'
|
||||||
@@ -138,6 +177,7 @@ const formatDate2 = (date: any) => {
|
|||||||
const open = (currentNo?: string) => {
|
const open = (currentNo?: string) => {
|
||||||
dialogVisible.value = true
|
dialogVisible.value = true
|
||||||
selectedId.value = undefined
|
selectedId.value = undefined
|
||||||
|
currentRow.value = undefined
|
||||||
if (currentNo) {
|
if (currentNo) {
|
||||||
queryParams.value.no = currentNo
|
queryParams.value.no = currentNo
|
||||||
} else {
|
} else {
|
||||||
@@ -180,9 +220,20 @@ const resetQuery = () => {
|
|||||||
|
|
||||||
/** 单选中时触发 */
|
/** 单选中时触发 */
|
||||||
const handleSingleSelected = (row: PurchaseRequisition) => {
|
const handleSingleSelected = (row: PurchaseRequisition) => {
|
||||||
|
if (isMobile.value) {
|
||||||
emits(CHANGE_EVENT, row)
|
emits(CHANGE_EVENT, row)
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
|
}
|
||||||
selectedId.value = row.id
|
selectedId.value = row.id
|
||||||
|
currentRow.value = row
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 提交选择 */
|
||||||
|
const submitForm = () => {
|
||||||
|
if (currentRow.value) {
|
||||||
|
emits(CHANGE_EVENT, currentRow.value)
|
||||||
|
}
|
||||||
|
dialogVisible.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const emits = defineEmits<{
|
const emits = defineEmits<{
|
||||||
|
|||||||
@@ -1,25 +1,34 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mobile-purchase-order">
|
<!-- 移动端布局 -->
|
||||||
<!-- 顶部操作栏 -->
|
<div v-if="isMobile" class="mobile-purchase-order">
|
||||||
<div class="mobile-header">
|
<div class="mobile-header">
|
||||||
<div class="mobile-header__search">
|
<div class="mobile-header__search"><el-input v-model="queryParams.no" placeholder="搜索订单单号" clearable @keyup.enter="handleQuery" :prefix-icon="Search" /></div>
|
||||||
<el-input
|
|
||||||
v-model="queryParams.no"
|
|
||||||
placeholder="搜索订单单号"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
:prefix-icon="Search"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mobile-header__actions">
|
<div class="mobile-header__actions">
|
||||||
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
||||||
<el-button
|
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:purchase-order:create']" />
|
||||||
type="primary"
|
</div>
|
||||||
:icon="Plus"
|
</div>
|
||||||
circle
|
<div class="mobile-header__quick-filter">
|
||||||
@click="openForm('create')"
|
<div
|
||||||
v-hasPermi="['erp:purchase-order:create']"
|
class="quick-filter-item"
|
||||||
/>
|
:class="{ active: queryParams.status === undefined }"
|
||||||
|
@click="handleQuickFilter(undefined)"
|
||||||
|
>
|
||||||
|
全部订单
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="quick-filter-item"
|
||||||
|
:class="{ active: queryParams.status === 10 }"
|
||||||
|
@click="handleQuickFilter(10)"
|
||||||
|
>
|
||||||
|
待审核
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="quick-filter-item"
|
||||||
|
:class="{ active: queryParams.status === 20 }"
|
||||||
|
@click="handleQuickFilter(20)"
|
||||||
|
>
|
||||||
|
已审核
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -171,12 +180,334 @@
|
|||||||
<!-- 供应商评价对话框 -->
|
<!-- 供应商评价对话框 -->
|
||||||
<SupplierEvaluationDialog ref="supplierEvaluationDialogRef" @success="getList" />
|
<SupplierEvaluationDialog ref="supplierEvaluationDialogRef" @success="getList" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="68px"
|
||||||
|
>
|
||||||
|
<el-form-item label="订单单号" prop="no">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.no"
|
||||||
|
placeholder="请输入订单单号"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="产品" prop="productId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.productId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择产品"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in productList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="订单时间" prop="orderTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.orderTime"
|
||||||
|
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-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="供应商" prop="supplierId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.supplierId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择供供应商"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in supplierList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="创建人" prop="creator">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.creator"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择创建人"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in userList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.nickname"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</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.ERP_AUDIT_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.remark"
|
||||||
|
placeholder="请输入备注"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="入库数量" prop="inStatus">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.inStatus"
|
||||||
|
placeholder="请选择入库数量"
|
||||||
|
clearable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option label="未入库" value="0" />
|
||||||
|
<el-option label="部分入库" value="1" />
|
||||||
|
<el-option label="全部入库" value="2" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="退货数量" prop="returnStatus">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.returnStatus"
|
||||||
|
placeholder="请选择退货数量"
|
||||||
|
clearable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option label="未退货" value="0" />
|
||||||
|
<el-option label="部分退货" value="1" />
|
||||||
|
<el-option label="全部退货" value="2" />
|
||||||
|
</el-select>
|
||||||
|
</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
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="openForm('create')"
|
||||||
|
v-hasPermi="['erp:purchase-order:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['erp:purchase-order:export']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
@click="handleDelete(selectionList.map((item) => item.id))"
|
||||||
|
v-hasPermi="['erp:purchase-order:delete']"
|
||||||
|
:disabled="selectionList.length === 0"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:delete" class="mr-5px" /> 删除
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<!-- 列表 -->
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table
|
||||||
|
v-loading="loading"
|
||||||
|
:data="list"
|
||||||
|
:stripe="true"
|
||||||
|
:show-overflow-tooltip="true"
|
||||||
|
@selection-change="handleSelectionChange"
|
||||||
|
@row-click="handleRowClick"
|
||||||
|
>
|
||||||
|
<el-table-column width="30" label="选择" type="selection" />
|
||||||
|
<el-table-column min-width="180" label="订单单号" align="center" prop="no" />
|
||||||
|
<el-table-column label="产品信息" align="center" prop="productNames" min-width="200" />
|
||||||
|
<el-table-column label="供应商" align="center" prop="supplierName" />
|
||||||
|
<el-table-column
|
||||||
|
label="订单时间"
|
||||||
|
align="center"
|
||||||
|
prop="orderTime"
|
||||||
|
:formatter="dateFormatter2"
|
||||||
|
width="120px"
|
||||||
|
sortable
|
||||||
|
/>
|
||||||
|
<el-table-column label="创建人" align="center" prop="creatorName" />
|
||||||
|
<!-- <el-table-column label="审核人" align="center" prop="auditorName" />-->
|
||||||
|
<el-table-column
|
||||||
|
label="总数量"
|
||||||
|
align="center"
|
||||||
|
prop="totalCount"
|
||||||
|
:formatter="erpCountTableColumnFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="入库数量"
|
||||||
|
align="center"
|
||||||
|
prop="inCount"
|
||||||
|
:formatter="erpCountTableColumnFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="退货数量"
|
||||||
|
align="center"
|
||||||
|
prop="returnCount"
|
||||||
|
:formatter="erpCountTableColumnFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="金额合计"
|
||||||
|
align="center"
|
||||||
|
prop="totalProductPrice"
|
||||||
|
:formatter="erpPriceTableColumnFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="含税金额"
|
||||||
|
align="center"
|
||||||
|
prop="totalPrice"
|
||||||
|
:formatter="erpPriceTableColumnFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column
|
||||||
|
label="支付订金"
|
||||||
|
align="center"
|
||||||
|
prop="depositPrice"
|
||||||
|
:formatter="erpPriceTableColumnFormatter"
|
||||||
|
/>
|
||||||
|
<el-table-column label="状态" align="center" fixed="right" width="90" prop="status">
|
||||||
|
<template #default="scope">
|
||||||
|
<dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="scope.row.status" />
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" fixed="right" width="380">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
@click="openForm('detail', scope.row.id)"
|
||||||
|
v-hasPermi="['erp:purchase-order:query']"
|
||||||
|
>
|
||||||
|
详情
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="openForm('update', scope.row.id)"
|
||||||
|
v-hasPermi="['erp:purchase-order:update']"
|
||||||
|
:disabled="scope.row.status === 20 || scope.row.hasApprovalRecords"
|
||||||
|
>
|
||||||
|
编辑
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="success"
|
||||||
|
@click="handleSubmitApproval(scope.row.id)"
|
||||||
|
v-hasPermi="['erp:purchase-order:submit-approval']"
|
||||||
|
v-if="scope.row.status === 10 && !scope.row.hasApprovalRecords"
|
||||||
|
>
|
||||||
|
提交审批
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="info"
|
||||||
|
@click="handleViewApproval(scope.row.id)"
|
||||||
|
v-hasPermi="['erp:purchase-order:query-approval']"
|
||||||
|
v-if="scope.row.hasApprovalRecords"
|
||||||
|
>
|
||||||
|
审批记录
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="handleProcessApproval(scope.row.id)"
|
||||||
|
v-hasPermi="['erp:approval-record:process']"
|
||||||
|
v-if="scope.row.hasApprovalRecords && scope.row.status !== 20"
|
||||||
|
>
|
||||||
|
处理审批
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="primary"
|
||||||
|
@click="handleUpdateStatus(scope.row.id, 20)"
|
||||||
|
v-hasPermi="['erp:purchase-order:update-status']"
|
||||||
|
v-if="scope.row.status === 10"
|
||||||
|
>
|
||||||
|
审批
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleUpdateStatus(scope.row.id, 10)"
|
||||||
|
v-hasPermi="['erp:purchase-order:update-status']"
|
||||||
|
v-else
|
||||||
|
>
|
||||||
|
反审批
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="warning"
|
||||||
|
@click="handleSupplierEvaluation(scope.row)"
|
||||||
|
v-hasPermi="['erp:supplier-evaluation:create']"
|
||||||
|
v-if="scope.row.status === 20"
|
||||||
|
>
|
||||||
|
供应商评价
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
link
|
||||||
|
type="danger"
|
||||||
|
@click="handleDelete([scope.row.id])"
|
||||||
|
v-hasPermi="['erp:purchase-order:delete']"
|
||||||
|
:disabled="scope.row.hasApprovalRecords"
|
||||||
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<!-- 分页 -->
|
||||||
|
<Pagination
|
||||||
|
:total="total"
|
||||||
|
v-model:page="queryParams.pageNo"
|
||||||
|
v-model:limit="queryParams.pageSize"
|
||||||
|
@pagination="getList"
|
||||||
|
/>
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
|
<PurchaseOrderForm ref="formRef" @success="getList" />
|
||||||
|
<SubmitApprovalDialog ref="submitApprovalDialogRef" @success="getList" />
|
||||||
|
<ApprovalRecordsDialog ref="approvalDialogRef" />
|
||||||
|
<ProcessApprovalDialog ref="processApprovalDialogRef" @success="getList" />
|
||||||
|
<SupplierEvaluationDialog ref="supplierEvaluationDialogRef" @success="getList" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate, dateFormatter2 } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { PurchaseOrderApi, PurchaseOrderVO } from '@/api/erp/purchase/order'
|
import { PurchaseOrderApi, PurchaseOrderVO } from '@/api/erp/purchase/order'
|
||||||
import PurchaseOrderForm from './PurchaseOrderForm.vue'
|
import PurchaseOrderForm from './PurchaseOrderForm.vue'
|
||||||
@@ -185,13 +516,17 @@ import { SubmitApprovalDialog, ApprovalRecordsDialog, ProcessApprovalDialog } fr
|
|||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
import { UserVO } from '@/api/system/user'
|
import { UserVO } from '@/api/system/user'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import { erpCountInputFormatter, erpPriceInputFormatter } from '@/utils'
|
import { erpCountInputFormatter, erpPriceInputFormatter, erpCountTableColumnFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
import { ApprovalRecordApi, ApprovalRecordVO } from '@/api/erp/approval/index'
|
import { ApprovalRecordApi, ApprovalRecordVO } from '@/api/erp/approval/index'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 销售订单列表 */
|
/** ERP 销售订单列表 */
|
||||||
defineOptions({ name: 'ErpPurchaseOrder' })
|
defineOptions({ name: 'ErpPurchaseOrder' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
@@ -315,16 +650,31 @@ const handleSupplierEvaluation = (row: PurchaseOrderVO) => {
|
|||||||
/** 删除按钮操作 */
|
/** 删除按钮操作 */
|
||||||
const handleDelete = async (ids: number[]) => {
|
const handleDelete = async (ids: number[]) => {
|
||||||
try {
|
try {
|
||||||
// 删除的二次确认
|
|
||||||
await message.delConfirm()
|
await message.delConfirm()
|
||||||
// 发起删除
|
|
||||||
await PurchaseOrderApi.deletePurchaseOrder(ids)
|
await PurchaseOrderApi.deletePurchaseOrder(ids)
|
||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
// 刷新列表
|
|
||||||
await getList()
|
await getList()
|
||||||
|
selectionList.value = selectionList.value.filter((item) => !ids.includes(item.id))
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 选中操作(PC端) */
|
||||||
|
const selectionList = ref<PurchaseOrderVO[]>([])
|
||||||
|
const handleSelectionChange = (rows: PurchaseOrderVO[]) => {
|
||||||
|
selectionList.value = rows
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 行点击操作(PC端) */
|
||||||
|
const handleRowClick = (row: PurchaseOrderVO, column: any, event: MouseEvent) => {
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (target.tagName === 'BUTTON' || target.tagName === 'A' || target.tagName === 'I' || target.tagName === 'svg' || target.closest('button') || target.closest('a') || target.closest('.el-button') || target.closest('.el-checkbox')) return
|
||||||
|
if (row.status === 20) {
|
||||||
|
openForm('detail', row.id)
|
||||||
|
} else if (row.status === 10) {
|
||||||
|
openForm('update', row.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** 审批/反审批操作 */
|
/** 审批/反审批操作 */
|
||||||
const handleUpdateStatus = async (id: number, status: number) => {
|
const handleUpdateStatus = async (id: number, status: number) => {
|
||||||
try {
|
try {
|
||||||
@@ -361,9 +711,41 @@ onMounted(async () => {
|
|||||||
supplierList.value = await SupplierApi.getSupplierSimpleList()
|
supplierList.value = await SupplierApi.getSupplierSimpleList()
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/** 快捷分类筛选 */
|
||||||
|
const handleQuickFilter = (status: number | undefined) => {
|
||||||
|
queryParams.status = status
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.mobile-header__quick-filter {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.quick-filter-item {
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #909399;
|
||||||
|
background: transparent;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #fff;
|
||||||
|
background: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.mobile-purchase-order {
|
.mobile-purchase-order {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
|
|||||||
@@ -1,184 +1,75 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="1440">
|
<!-- 移动端布局 -->
|
||||||
<el-form
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
ref="formRef"
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
:model="formData"
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" :disabled="disabled">
|
||||||
:rules="formRules"
|
<div class="mobile-form__section">
|
||||||
label-width="100px"
|
<div class="mobile-form__section-title">基本信息</div>
|
||||||
v-loading="formLoading"
|
<el-form-item label="退货单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item>
|
||||||
:disabled="disabled"
|
<el-form-item label="退货时间" prop="returnTime"><el-date-picker v-model="formData.returnTime" type="date" value-format="x" placeholder="选择退货时间" style="width:100%" /></el-form-item>
|
||||||
>
|
<el-form-item label="关联订单" prop="orderNo"><el-input readonly><template #prefix><el-link v-if="formData.orderId" type="primary" :underline="false" @click.stop="openPurchaseOrderDetail">{{ formData.orderNo }}</el-link></template><template #append><el-button @click="openPurchaseOrderReturnEnableList"><Icon icon="ep:search" /></el-button></template></el-input></el-form-item>
|
||||||
|
<el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable disabled placeholder="请选择供应商" style="width:100%"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="3" placeholder="请输入备注" /></el-form-item>
|
||||||
|
<el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">退货产品清单</div>
|
||||||
|
<PurchaseReturnItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-form__section">
|
||||||
|
<div class="mobile-form__section-title">费用信息</div>
|
||||||
|
<el-form-item label="优惠率(%)" prop="discountPercent"><el-input-number v-model="formData.discountPercent" controls-position="right" :min="0" :precision="4" placeholder="请输入优惠率" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="退款优惠" prop="discountPrice"><el-input disabled v-model="formData.discountPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
|
<el-form-item label="其它费用" prop="otherPrice"><el-input-number v-model="formData.otherPrice" controls-position="right" :min="0" :precision="4" placeholder="请输入其它费用" style="width:100%" /></el-form-item>
|
||||||
|
<el-form-item label="优惠后金额"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item>
|
||||||
|
<el-form-item label="结算账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" style="width:100%"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
<div class="mobile-form__footer">
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible" width="1440">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px" v-loading="formLoading" :disabled="disabled">
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8"><el-form-item label="退货单号" prop="no"><el-input disabled v-model="formData.no" placeholder="保存时自动生成" /></el-form-item></el-col>
|
||||||
<el-form-item label="退货单号" prop="no">
|
<el-col :span="8"><el-form-item label="退货时间" prop="returnTime"><el-date-picker v-model="formData.returnTime" type="date" value-format="x" placeholder="选择退货时间" class="!w-1/1" /></el-form-item></el-col>
|
||||||
<el-input disabled v-model="formData.no" placeholder="保存时自动生成" />
|
<el-col :span="8"><el-form-item label="关联订单" prop="orderNo"><el-input readonly><template #prefix><el-link v-if="formData.orderId" type="primary" :underline="false" @click.stop="openPurchaseOrderDetail">{{ formData.orderNo }}</el-link></template><template #append><el-button @click="openPurchaseOrderReturnEnableList"><Icon icon="ep:search" /> 选择</el-button></template></el-input></el-form-item></el-col>
|
||||||
</el-form-item>
|
<el-col :span="8"><el-form-item label="供应商" prop="supplierId"><el-select v-model="formData.supplierId" clearable filterable disabled placeholder="请选择供应商" class="!w-1/1"><el-option v-for="item in supplierList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
</el-col>
|
<el-col :span="16"><el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" :rows="1" placeholder="请输入备注" /></el-form-item></el-col>
|
||||||
<el-col :span="8">
|
<el-col :span="8"><el-form-item label="附件" prop="fileUrl"><UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" /></el-form-item></el-col>
|
||||||
<el-form-item label="退货时间" prop="returnTime">
|
|
||||||
<el-date-picker
|
|
||||||
v-model="formData.returnTime"
|
|
||||||
type="date"
|
|
||||||
value-format="x"
|
|
||||||
placeholder="选择退货时间"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="关联订单" prop="orderNo">
|
|
||||||
<el-input readonly>
|
|
||||||
<template #prefix>
|
|
||||||
<el-link
|
|
||||||
v-if="formData.orderId"
|
|
||||||
type="primary"
|
|
||||||
@click.stop="openPurchaseOrderDetail"
|
|
||||||
:underline="false"
|
|
||||||
>
|
|
||||||
{{ formData.orderNo }}
|
|
||||||
</el-link>
|
|
||||||
</template>
|
|
||||||
<template #append>
|
|
||||||
<el-button @click="openPurchaseOrderReturnEnableList">
|
|
||||||
<Icon icon="ep:search" /> 选择
|
|
||||||
</el-button>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="供应商" prop="supplierId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.supplierId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
disabled
|
|
||||||
placeholder="请选择供应商"
|
|
||||||
class="!w-1/1"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in supplierList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="16">
|
|
||||||
<el-form-item label="备注" prop="remark">
|
|
||||||
<el-input
|
|
||||||
type="textarea"
|
|
||||||
v-model="formData.remark"
|
|
||||||
:rows="1"
|
|
||||||
placeholder="请输入备注"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="附件" prop="fileUrl">
|
|
||||||
<UploadFile :is-show-tip="false" v-model="formData.fileUrl" :limit="1" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
<!-- 子表的表单 -->
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
<el-tabs v-model="subTabsName" class="-mt-15px -mb-10px">
|
||||||
<el-tab-pane label="退货产品清单" name="item">
|
<el-tab-pane label="退货产品清单" name="item"><PurchaseReturnItemForm ref="itemFormRef" :items="formData.items" :disabled="disabled" /></el-tab-pane>
|
||||||
<PurchaseReturnItemForm
|
|
||||||
ref="itemFormRef"
|
|
||||||
:items="formData.items"
|
|
||||||
:disabled="disabled"
|
|
||||||
/>
|
|
||||||
</el-tab-pane>
|
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
<el-row :gutter="20">
|
<el-row :gutter="20">
|
||||||
<el-col :span="8">
|
<el-col :span="8"><el-form-item label="优惠率(%)" prop="discountPercent"><el-input-number v-model="formData.discountPercent" controls-position="right" :min="0" :precision="4" placeholder="请输入优惠率" class="!w-1/1" /></el-form-item></el-col>
|
||||||
<el-form-item label="优惠率(%)" prop="discountPercent">
|
<el-col :span="8"><el-form-item label="退款优惠" prop="discountPrice"><el-input disabled v-model="formData.discountPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
<el-input-number
|
<el-col :span="8"><el-form-item label="其它费用" prop="otherPrice"><el-input-number v-model="formData.otherPrice" controls-position="right" :min="0" :precision="4" placeholder="请输入其它费用" class="!w-1/1" /></el-form-item></el-col>
|
||||||
v-model="formData.discountPercent"
|
<el-col :span="8"><el-form-item label="优惠后金额"><el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item></el-col>
|
||||||
controls-position="right"
|
<el-col :span="8"><el-form-item label="结算账户" prop="accountId"><el-select v-model="formData.accountId" clearable filterable placeholder="请选择结算账户" class="!w-1/1"><el-option v-for="item in accountList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></el-col>
|
||||||
:min="0"
|
|
||||||
:precision="4"
|
|
||||||
placeholder="请输入优惠率"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="退款优惠" prop="discountPrice">
|
|
||||||
<el-input
|
|
||||||
disabled
|
|
||||||
v-model="formData.discountPrice"
|
|
||||||
:formatter="erpPriceInputFormatter"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="优惠后金额">
|
|
||||||
<el-input
|
|
||||||
disabled
|
|
||||||
:model-value="formData.totalPrice - formData.otherPrice"
|
|
||||||
:formatter="erpPriceInputFormatter"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="其它费用" prop="otherPrice">
|
|
||||||
<el-input-number
|
|
||||||
v-model="formData.otherPrice"
|
|
||||||
controls-position="right"
|
|
||||||
:min="0"
|
|
||||||
:precision="4"
|
|
||||||
placeholder="请输入其它费用"
|
|
||||||
class="!w-1/1"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="结算账户" prop="accountId">
|
|
||||||
<el-select
|
|
||||||
v-model="formData.accountId"
|
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择结算账户"
|
|
||||||
class="!w-1/1"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in accountList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
<el-col :span="8">
|
|
||||||
<el-form-item label="应退金额" prop="totalPrice">
|
|
||||||
<el-input disabled v-model="formData.totalPrice" :formatter="erpPriceInputFormatter" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-col>
|
|
||||||
</el-row>
|
</el-row>
|
||||||
</el-form>
|
</el-form>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
确 定
|
|
||||||
</el-button>
|
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<!-- 可退货的订单列表 -->
|
<!-- 可退货的订单列表 -->
|
||||||
<PurchaseOrderReturnEnableList
|
<PurchaseOrderReturnEnableList ref="purchaseOrderReturnEnableListRef" @success="handlePurchaseOrderChange" />
|
||||||
ref="purchaseOrderReturnEnableListRef"
|
|
||||||
@success="handlePurchaseOrderChange"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<!-- 采购订单详情弹窗 -->
|
<!-- 采购订单详情弹窗 -->
|
||||||
<PurchaseOrderForm ref="purchaseOrderFormRef" />
|
<PurchaseOrderForm ref="purchaseOrderFormRef" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { ref, reactive, computed, watch, nextTick } from 'vue'
|
||||||
import { PurchaseReturnApi, PurchaseReturnVO } from '@/api/erp/purchase/return'
|
import { PurchaseReturnApi, PurchaseReturnVO } from '@/api/erp/purchase/return'
|
||||||
import PurchaseReturnItemForm from './components/PurchaseReturnItemForm.vue'
|
import PurchaseReturnItemForm from './components/PurchaseReturnItemForm.vue'
|
||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
@@ -188,19 +79,25 @@ import PurchaseOrderReturnEnableList from '@/views/erp/purchase/order/components
|
|||||||
import PurchaseOrderForm from '@/views/erp/purchase/order/PurchaseOrderForm.vue'
|
import PurchaseOrderForm from '@/views/erp/purchase/order/PurchaseOrderForm.vue'
|
||||||
import { PurchaseOrderVO } from '@/api/erp/purchase/order'
|
import { PurchaseOrderVO } from '@/api/erp/purchase/order'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 采购退货表单 */
|
|
||||||
defineOptions({ name: 'PurchaseReturnForm' })
|
defineOptions({ name: 'PurchaseReturnForm' })
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { width } = useWindowSize()
|
||||||
const message = useMessage() // 消息弹窗
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
const { t } = useI18n()
|
||||||
|
const message = useMessage()
|
||||||
|
|
||||||
const dialogVisible = ref(false) // 弹窗的是否展示
|
/** 弹窗显示 */
|
||||||
const dialogTitle = ref('') // 弹窗的标题
|
const dialogVisible = ref(false)
|
||||||
const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
const dialogTitle = ref('')
|
||||||
const formType = ref('') // 表单的类型:create - 新增;update - 修改;detail - 详情
|
const formLoading = ref(false)
|
||||||
|
const formType = ref('')
|
||||||
|
|
||||||
|
/** 表单数据 */
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
|
orderId: undefined,
|
||||||
supplierId: undefined,
|
supplierId: undefined,
|
||||||
accountId: undefined,
|
accountId: undefined,
|
||||||
returnTime: undefined,
|
returnTime: undefined,
|
||||||
@@ -212,35 +109,35 @@ const formData = ref({
|
|||||||
otherPrice: 0,
|
otherPrice: 0,
|
||||||
orderNo: undefined,
|
orderNo: undefined,
|
||||||
items: [],
|
items: [],
|
||||||
no: undefined // 退货单号,后端返回
|
no: undefined
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 表单验证 */
|
||||||
const formRules = reactive({
|
const formRules = reactive({
|
||||||
supplierId: [{ required: true, message: '供应商不能为空', trigger: 'blur' }],
|
supplierId: [{ required: true, message: '供应商不能为空', trigger: 'blur' }],
|
||||||
returnTime: [{ required: true, message: '退货时间不能为空', trigger: 'blur' }]
|
returnTime: [{ required: true, message: '退货时间不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/** 计算禁用状态 */
|
||||||
const disabled = computed(() => formType.value === 'detail')
|
const disabled = computed(() => formType.value === 'detail')
|
||||||
const formRef = ref() // 表单 Ref
|
|
||||||
const supplierList = ref<SupplierVO[]>([]) // 供应商列表
|
|
||||||
const accountList = ref<AccountVO[]>([]) // 账户列表
|
|
||||||
const userList = ref<UserApi.UserVO[]>([]) // 用户列表
|
|
||||||
|
|
||||||
/** 子表的表单 */
|
/** ref */
|
||||||
const subTabsName = ref('item')
|
const formRef = ref() // el-form
|
||||||
const itemFormRef = ref()
|
const itemFormRef = ref()
|
||||||
|
const supplierList = ref<SupplierVO[]>([])
|
||||||
|
const accountList = ref<AccountVO[]>([])
|
||||||
|
const userList = ref<UserApi.UserVO[]>([])
|
||||||
|
const subTabsName = ref('item')
|
||||||
|
|
||||||
/** 计算 discountPrice、totalPrice 价格 */
|
/** 计算价格 */
|
||||||
watch(
|
watch(
|
||||||
() => formData.value,
|
() => formData.value,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!val) {
|
if (!val) return
|
||||||
return
|
const totalItemPrice = (val.items || []).reduce((sum, item) => sum + (item.totalPrice || 0), 0)
|
||||||
}
|
const discountPrice = erpPriceMultiply(totalItemPrice, (val.discountPercent || 0) / 100) || 0
|
||||||
// 计算
|
|
||||||
const totalPrice = val.items.reduce((prev, curr) => prev + curr.totalPrice, 0)
|
|
||||||
const discountPrice =
|
|
||||||
val.discountPercent != null ? erpPriceMultiply(totalPrice, val.discountPercent / 100.0) : 0
|
|
||||||
formData.value.discountPrice = discountPrice
|
formData.value.discountPrice = discountPrice
|
||||||
formData.value.totalPrice = totalPrice - discountPrice + val.otherPrice
|
formData.value.totalPrice = totalItemPrice - discountPrice + (val.otherPrice || 0)
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
@@ -251,7 +148,6 @@ const open = async (type: string, id?: number) => {
|
|||||||
dialogTitle.value = t('action.' + type)
|
dialogTitle.value = t('action.' + type)
|
||||||
formType.value = type
|
formType.value = type
|
||||||
resetForm()
|
resetForm()
|
||||||
// 修改时,设置数据
|
|
||||||
if (id) {
|
if (id) {
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
@@ -260,35 +156,28 @@ const open = async (type: string, id?: number) => {
|
|||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 加载供应商列表
|
|
||||||
supplierList.value = await SupplierApi.getSupplierSimpleList()
|
supplierList.value = await SupplierApi.getSupplierSimpleList()
|
||||||
// 加载用户列表
|
|
||||||
userList.value = await UserApi.getSimpleUserList()
|
|
||||||
// 加载账户列表
|
|
||||||
accountList.value = await AccountApi.getAccountSimpleList()
|
accountList.value = await AccountApi.getAccountSimpleList()
|
||||||
|
userList.value = await UserApi.getSimpleUserList()
|
||||||
const defaultAccount = accountList.value.find((item) => item.defaultStatus)
|
const defaultAccount = accountList.value.find((item) => item.defaultStatus)
|
||||||
if (defaultAccount) {
|
if (defaultAccount) formData.value.accountId = defaultAccount.id
|
||||||
formData.value.accountId = defaultAccount.id
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
defineExpose({ open }) // 提供 open 方法,用于打开弹窗
|
defineExpose({ open })
|
||||||
|
|
||||||
/** 打开【可退货的订单列表】弹窗 */
|
/** 打开可退货订单列表 */
|
||||||
const purchaseOrderReturnEnableListRef = ref() // 可退货的订单列表 Ref
|
const purchaseOrderReturnEnableListRef = ref()
|
||||||
const openPurchaseOrderReturnEnableList = () => {
|
const openPurchaseOrderReturnEnableList = () => {
|
||||||
purchaseOrderReturnEnableListRef.value.open()
|
purchaseOrderReturnEnableListRef.value?.open()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 打开【采购订单详情】弹窗 */
|
/** 打开采购订单详情 */
|
||||||
const purchaseOrderFormRef = ref()
|
const purchaseOrderFormRef = ref()
|
||||||
const openPurchaseOrderDetail = () => {
|
const openPurchaseOrderDetail = () => {
|
||||||
if (formData.value.orderId) {
|
if (formData.value.orderId) purchaseOrderFormRef.value?.open('detail', formData.value.orderId)
|
||||||
purchaseOrderFormRef.value.open('detail', formData.value.orderId)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 订单选择回调 */
|
||||||
const handlePurchaseOrderChange = (order: PurchaseOrderVO) => {
|
const handlePurchaseOrderChange = (order: PurchaseOrderVO) => {
|
||||||
// 将订单设置到退货单
|
|
||||||
formData.value.orderId = order.id
|
formData.value.orderId = order.id
|
||||||
formData.value.orderNo = order.no
|
formData.value.orderNo = order.no
|
||||||
formData.value.supplierId = order.supplierId
|
formData.value.supplierId = order.supplierId
|
||||||
@@ -296,22 +185,21 @@ const handlePurchaseOrderChange = (order: PurchaseOrderVO) => {
|
|||||||
formData.value.discountPercent = order.discountPercent
|
formData.value.discountPercent = order.discountPercent
|
||||||
formData.value.remark = order.remark
|
formData.value.remark = order.remark
|
||||||
formData.value.fileUrl = order.fileUrl
|
formData.value.fileUrl = order.fileUrl
|
||||||
// 将订单项设置到退货单项
|
|
||||||
order.items.forEach((item) => {
|
// 处理订单明细
|
||||||
|
order.items.forEach(item => {
|
||||||
item.count = item.inCount - item.returnCount
|
item.count = item.inCount - item.returnCount
|
||||||
item.orderItemId = item.id
|
item.orderItemId = item.id
|
||||||
item.id = undefined
|
item.id = undefined
|
||||||
})
|
})
|
||||||
formData.value.items = order.items.filter((item) => item.count > 0)
|
formData.value.items = order.items.filter(item => item.count > 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 提交表单 */
|
/** 提交表单 */
|
||||||
const emit = defineEmits(['success']) // 定义 success 事件,用于操作成功后的回调
|
const emit = defineEmits(['success'])
|
||||||
const submitForm = async () => {
|
const submitForm = async () => {
|
||||||
// 校验表单
|
|
||||||
await formRef.value.validate()
|
await formRef.value.validate()
|
||||||
await itemFormRef.value.validate()
|
await itemFormRef.value.validate()
|
||||||
// 提交请求
|
|
||||||
formLoading.value = true
|
formLoading.value = true
|
||||||
try {
|
try {
|
||||||
const data = formData.value as unknown as PurchaseReturnVO
|
const data = formData.value as unknown as PurchaseReturnVO
|
||||||
@@ -323,7 +211,6 @@ const submitForm = async () => {
|
|||||||
message.success(t('common.updateSuccess'))
|
message.success(t('common.updateSuccess'))
|
||||||
}
|
}
|
||||||
dialogVisible.value = false
|
dialogVisible.value = false
|
||||||
// 发送操作成功的事件
|
|
||||||
emit('success')
|
emit('success')
|
||||||
} finally {
|
} finally {
|
||||||
formLoading.value = false
|
formLoading.value = false
|
||||||
@@ -334,17 +221,66 @@ const submitForm = async () => {
|
|||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
formData.value = {
|
formData.value = {
|
||||||
id: undefined,
|
id: undefined,
|
||||||
|
orderId: undefined,
|
||||||
supplierId: undefined,
|
supplierId: undefined,
|
||||||
accountId: undefined,
|
accountId: undefined,
|
||||||
returnTime: undefined,
|
returnTime: undefined,
|
||||||
remark: undefined,
|
remark: undefined,
|
||||||
fileUrl: undefined,
|
fileUrl: '',
|
||||||
discountPercent: 0,
|
discountPercent: 0,
|
||||||
discountPrice: 0,
|
discountPrice: 0,
|
||||||
totalPrice: 0,
|
totalPrice: 0,
|
||||||
otherPrice: 0,
|
otherPrice: 0,
|
||||||
items: []
|
orderNo: undefined,
|
||||||
|
items: [],
|
||||||
|
no: undefined
|
||||||
}
|
}
|
||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form {
|
||||||
|
padding: 12px;
|
||||||
|
}
|
||||||
|
.mobile-form__section {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
}
|
||||||
|
.mobile-form__section-title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-form__requisition {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.mobile-form__footer {
|
||||||
|
position: sticky;
|
||||||
|
bottom: 0;
|
||||||
|
background: #fff;
|
||||||
|
padding: 12px 16px;
|
||||||
|
padding-bottom: calc(12px + env(safe-area-inset-bottom));
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: 12px;
|
||||||
|
z-index: 10;
|
||||||
|
margin: 0 -12px -12px;
|
||||||
|
|
||||||
|
.el-button {
|
||||||
|
flex: 1;
|
||||||
|
height: 40px;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,27 +1,37 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-form
|
<!-- 移动端布局 -->
|
||||||
ref="formRef"
|
<el-form v-if="isMobile" ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-position="top" :inline-message="true" :disabled="disabled">
|
||||||
:model="formData"
|
<div class="mobile-item-list">
|
||||||
:rules="formRules"
|
<div
|
||||||
v-loading="formLoading"
|
v-for="(row, $index) in formData"
|
||||||
label-width="0px"
|
:key="$index"
|
||||||
:inline-message="true"
|
class="mobile-item-card"
|
||||||
:disabled="disabled"
|
|
||||||
>
|
>
|
||||||
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px">
|
<!-- 卡片头部 -->
|
||||||
<el-table-column label="序号" type="index" align="center" width="60" />
|
<div class="mobile-item-card__header">
|
||||||
<el-table-column label="仓库名称" min-width="125">
|
<span class="mobile-item-card__index">#{{ $index + 1 }}</span>
|
||||||
<template #default="{ row, $index }">
|
<span class="mobile-item-card__name">{{ row.productName || '未选择产品' }}</span>
|
||||||
<el-form-item
|
<el-button
|
||||||
:prop="`${$index}.warehouseId`"
|
:disabled="formData.length === 1 || disabled"
|
||||||
:rules="formRules.warehouseId"
|
type="danger"
|
||||||
class="mb-0px!"
|
link
|
||||||
|
size="small"
|
||||||
|
@click="handleDelete($index)"
|
||||||
>
|
>
|
||||||
|
删除
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 卡片内容 -->
|
||||||
|
<div class="mobile-item-card__body">
|
||||||
|
<!-- 仓库 -->
|
||||||
|
<el-form-item :prop="`${$index}.warehouseId`" :rules="formRules.warehouseId" label="仓库">
|
||||||
<el-select
|
<el-select
|
||||||
v-model="row.warehouseId"
|
v-model="row.warehouseId"
|
||||||
clearable
|
clearable
|
||||||
filterable
|
filterable
|
||||||
placeholder="请选择仓库"
|
placeholder="请选择仓库"
|
||||||
|
style="width: 100%"
|
||||||
@change="onChangeWarehouse($event, row)"
|
@change="onChangeWarehouse($event, row)"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
@@ -32,234 +42,222 @@
|
|||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
|
||||||
</el-table-column>
|
<!-- 产品名称 -->
|
||||||
<el-table-column label="产品名称" min-width="180">
|
<div class="mobile-item-card__info-row">
|
||||||
<template #default="{ row }">
|
<span class="mobile-item-card__info-label">产品名称</span>
|
||||||
<el-form-item class="mb-0px!">
|
<span class="mobile-item-card__info-value">{{ row.productName || '-' }}</span>
|
||||||
<el-input disabled v-model="row.productName" />
|
</div>
|
||||||
</el-form-item>
|
|
||||||
</template>
|
<!-- 库存 -->
|
||||||
</el-table-column>
|
<div class="mobile-item-card__info-row">
|
||||||
<el-table-column label="库存" min-width="100">
|
<span class="mobile-item-card__info-label">库存</span>
|
||||||
<template #default="{ row }">
|
<span class="mobile-item-card__info-value">{{ erpCountInputFormatter(row.stockCount) }}</span>
|
||||||
<el-form-item class="mb-0px!">
|
</div>
|
||||||
<el-input disabled v-model="row.stockCount" :formatter="erpCountInputFormatter" />
|
|
||||||
</el-form-item>
|
<!-- 条码 -->
|
||||||
</template>
|
<div class="mobile-item-card__info-row">
|
||||||
</el-table-column>
|
<span class="mobile-item-card__info-label">条码</span>
|
||||||
<el-table-column label="条码" min-width="150">
|
<span class="mobile-item-card__info-value">{{ row.productBarCode || '-' }}</span>
|
||||||
<template #default="{ row }">
|
</div>
|
||||||
<el-form-item class="mb-0px!">
|
|
||||||
<el-input disabled v-model="row.productBarCode" />
|
<!-- 单位 -->
|
||||||
</el-form-item>
|
<div class="mobile-item-card__info-row">
|
||||||
</template>
|
<span class="mobile-item-card__info-label">单位</span>
|
||||||
</el-table-column>
|
<span class="mobile-item-card__info-value">{{ row.productUnitName || '-' }}</span>
|
||||||
<el-table-column label="单位" min-width="80">
|
</div>
|
||||||
<template #default="{ row }">
|
|
||||||
<el-form-item class="mb-0px!">
|
<!-- 已出库 -->
|
||||||
<el-input disabled v-model="row.productUnitName" />
|
<div class="mobile-item-card__info-row" v-if="row.inCount != null">
|
||||||
</el-form-item>
|
<span class="mobile-item-card__info-label">已出库</span>
|
||||||
</template>
|
<span class="mobile-item-card__info-value">{{ erpCountInputFormatter(row.inCount) }}</span>
|
||||||
</el-table-column>
|
</div>
|
||||||
<el-table-column
|
|
||||||
label="已出库"
|
<!-- 已退货 -->
|
||||||
fixed="right"
|
<div class="mobile-item-card__info-row" v-if="row.returnCount != null">
|
||||||
min-width="80"
|
<span class="mobile-item-card__info-label">已退货</span>
|
||||||
v-if="formData[0]?.inCount != null"
|
<span class="mobile-item-card__info-value">{{ erpCountInputFormatter(row.returnCount) }}</span>
|
||||||
>
|
</div>
|
||||||
<template #default="{ row }">
|
|
||||||
<el-form-item class="mb-0px!">
|
<!-- 数量 & 单价 -->
|
||||||
<el-input disabled v-model="row.inCount" :formatter="erpCountInputFormatter" />
|
<div class="mobile-item-card__input-group">
|
||||||
</el-form-item>
|
<el-form-item label="数量" :prop="`${$index}.count`" :rules="formRules.count">
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column
|
|
||||||
label="已退货"
|
|
||||||
fixed="right"
|
|
||||||
min-width="80"
|
|
||||||
v-if="formData[0]?.returnCount != null"
|
|
||||||
>
|
|
||||||
<template #default="{ row }">
|
|
||||||
<el-form-item class="mb-0px!">
|
|
||||||
<el-input disabled v-model="row.returnCount" :formatter="erpCountInputFormatter" />
|
|
||||||
</el-form-item>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="数量" prop="count" fixed="right" min-width="140">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<el-form-item :prop="`${$index}.count`" :rules="formRules.count" class="mb-0px!">
|
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="row.count"
|
v-model="row.count"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
:min="0.0001"
|
:min="0.0001"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
class="!w-100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
<el-form-item label="单价" :prop="`${$index}.productPrice`">
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="产品单价" fixed="right" min-width="120">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<el-form-item :prop="`${$index}.productPrice`" class="mb-0px!">
|
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="row.productPrice"
|
v-model="row.productPrice"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
:min="0.0001"
|
:min="0"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
class="!w-100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</div>
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="金额" prop="totalProductPrice" fixed="right" min-width="100">
|
<!-- 金额 -->
|
||||||
<template #default="{ row, $index }">
|
<div class="mobile-item-card__info-row">
|
||||||
<el-form-item :prop="`${$index}.totalProductPrice`" class="mb-0px!">
|
<span class="mobile-item-card__info-label">金额</span>
|
||||||
<el-input
|
<span class="mobile-item-card__info-value">{{ erpPriceInputFormatter(row.totalProductPrice) }}</span>
|
||||||
disabled
|
</div>
|
||||||
v-model="row.totalProductPrice"
|
|
||||||
:formatter="erpPriceInputFormatter"
|
<!-- 税率 -->
|
||||||
/>
|
<div class="mobile-item-card__input-group">
|
||||||
</el-form-item>
|
<el-form-item label="税率(%)" :prop="`${$index}.taxPercent`">
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="税率(%)" fixed="right" min-width="115">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<el-form-item :prop="`${$index}.taxPercent`" class="mb-0px!">
|
|
||||||
<el-input-number
|
<el-input-number
|
||||||
v-model="row.taxPercent"
|
v-model="row.taxPercent"
|
||||||
controls-position="right"
|
controls-position="right"
|
||||||
:min="0"
|
:min="0"
|
||||||
:precision="4"
|
:precision="4"
|
||||||
class="!w-100%"
|
style="width: 100%"
|
||||||
/>
|
/>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</div>
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="税额" prop="taxPrice" fixed="right" min-width="120">
|
<!-- 税额 -->
|
||||||
<template #default="{ row, $index }">
|
<div class="mobile-item-card__info-row">
|
||||||
<el-form-item :prop="`${$index}.taxPrice`" class="mb-0px!">
|
<span class="mobile-item-card__info-label">税额</span>
|
||||||
<el-form-item :prop="`${$index}.taxPrice`" class="mb-0px!">
|
<span class="mobile-item-card__info-value">{{ erpPriceInputFormatter(row.taxPrice) }}</span>
|
||||||
<el-input disabled v-model="row.taxPrice" :formatter="erpPriceInputFormatter" />
|
</div>
|
||||||
</el-form-item>
|
|
||||||
</el-form-item>
|
<!-- 税额合计 -->
|
||||||
</template>
|
<div class="mobile-item-card__info-row mobile-item-card__info-row--highlight">
|
||||||
</el-table-column>
|
<span class="mobile-item-card__info-label">税额合计</span>
|
||||||
<el-table-column label="税额合计" prop="totalPrice" fixed="right" min-width="100">
|
<span class="mobile-item-card__info-value mobile-item-card__info-value--bold">{{ erpPriceInputFormatter(row.totalPrice) }}</span>
|
||||||
<template #default="{ row, $index }">
|
</div>
|
||||||
<el-form-item :prop="`${$index}.totalPrice`" class="mb-0px!">
|
|
||||||
<el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" />
|
<!-- 备注 -->
|
||||||
</el-form-item>
|
<el-form-item label="备注" :prop="`${$index}.remark`">
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
<el-table-column label="备注" min-width="150">
|
|
||||||
<template #default="{ row, $index }">
|
|
||||||
<el-form-item :prop="`${$index}.remark`" class="mb-0px!">
|
|
||||||
<el-input v-model="row.remark" placeholder="请输入备注" />
|
<el-input v-model="row.remark" placeholder="请输入备注" />
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
</template>
|
</div>
|
||||||
</el-table-column>
|
</div>
|
||||||
<el-table-column align="center" fixed="right" label="操作" width="60">
|
|
||||||
<template #default="{ $index }">
|
<!-- 合计 -->
|
||||||
<el-button :disabled="formData.length === 1" @click="handleDelete($index)" link>
|
<div class="mobile-item-summary" v-if="formData.length > 0">
|
||||||
—
|
<div class="mobile-item-summary__row"><span>合计数量</span><span>{{ erpCountInputFormatter(summaryData.count) }}</span></div>
|
||||||
</el-button>
|
<div class="mobile-item-summary__row"><span>合计金额</span><span>{{ erpPriceInputFormatter(summaryData.totalProductPrice) }}</span></div>
|
||||||
</template>
|
<div class="mobile-item-summary__row"><span>合计税额</span><span>{{ erpPriceInputFormatter(summaryData.taxPrice) }}</span></div>
|
||||||
</el-table-column>
|
<div class="mobile-item-summary__row mobile-item-summary__row--total"><span>税额合计</span><span>{{ erpPriceInputFormatter(summaryData.totalPrice) }}</span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<el-form v-else ref="formRef" :model="formData" :rules="formRules" v-loading="formLoading" label-width="0px" :inline-message="true" :disabled="disabled">
|
||||||
|
<el-table :data="formData" show-summary :summary-method="getSummaries" class="-mt-10px">
|
||||||
|
<el-table-column label="序号" type="index" align="center" width="60" />
|
||||||
|
<el-table-column label="仓库名称" min-width="125"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.warehouseId`" :rules="formRules.warehouseId" class="mb-0px!"><el-select v-model="row.warehouseId" clearable filterable placeholder="请选择仓库" @change="onChangeWarehouse($event, row)"><el-option v-for="item in warehouseList" :key="item.id" :label="item.name" :value="item.id" /></el-select></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="产品名称" min-width="180"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.productName" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="库存" min-width="100"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.stockCount" :formatter="erpCountInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="条码" min-width="150"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.productBarCode" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="单位" min-width="80"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.productUnitName" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="已出库" fixed="right" min-width="80" v-if="formData[0]?.inCount != null"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.inCount" :formatter="erpCountInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="已退货" fixed="right" min-width="80" v-if="formData[0]?.returnCount != null"><template #default="{ row }"><el-form-item class="mb-0px!"><el-input disabled v-model="row.returnCount" :formatter="erpCountInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="数量" prop="count" fixed="right" min-width="140"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.count`" :rules="formRules.count" class="mb-0px!"><el-input-number v-model="row.count" controls-position="right" :min="0.0001" :precision="4" class="!w-100%" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="产品单价" fixed="right" min-width="120"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.productPrice`" class="mb-0px!"><el-input-number v-model="row.productPrice" controls-position="right" :min="0" :precision="4" class="!w-100%" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="金额" prop="totalProductPrice" fixed="right" min-width="100"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.totalProductPrice`" class="mb-0px!"><el-input disabled v-model="row.totalProductPrice" :formatter="erpPriceInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="税率(%)" fixed="right" min-width="115"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.taxPercent`" class="mb-0px!"><el-input-number v-model="row.taxPercent" controls-position="right" :min="0" :precision="4" class="!w-100%" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="税额" prop="taxPrice" fixed="right" min-width="120"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.taxPrice`" class="mb-0px!"><el-input disabled v-model="row.taxPrice" :formatter="erpPriceInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="税额合计" prop="totalPrice" fixed="right" min-width="100"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.totalPrice`" class="mb-0px!"><el-input disabled v-model="row.totalPrice" :formatter="erpPriceInputFormatter" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column label="备注" min-width="150"><template #default="{ row, $index }"><el-form-item :prop="`${$index}.remark`" class="mb-0px!"><el-input v-model="row.remark" placeholder="请输入备注" /></el-form-item></template></el-table-column>
|
||||||
|
<el-table-column align="center" fixed="right" label="操作" width="60"><template #default="{ $index }"><el-button :disabled="formData.length === 1" @click="handleDelete($index)" link>—</el-button></template></el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</el-form>
|
</el-form>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
|
||||||
import { StockApi } from '@/api/erp/stock/stock'
|
|
||||||
import {
|
|
||||||
erpCountInputFormatter,
|
|
||||||
erpPriceInputFormatter,
|
|
||||||
erpPriceMultiply,
|
|
||||||
getSumValue
|
|
||||||
} from '@/utils'
|
|
||||||
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
<script setup lang="ts">
|
||||||
items: undefined
|
import { ref, reactive, watch, onMounted, computed } from 'vue'
|
||||||
disabled: false
|
import { StockApi } from '@/api/erp/stock/stock'
|
||||||
}>()
|
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
|
||||||
const formLoading = ref(false) // 表单的加载中
|
import { erpCountInputFormatter, erpPriceInputFormatter, erpPriceMultiply, getSumValue } from '@/utils'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
|
const props = defineProps<{ items: any; disabled: boolean }>()
|
||||||
const formData = ref([])
|
const formData = ref([])
|
||||||
|
const formLoading = ref(false)
|
||||||
|
const warehouseList = ref<WarehouseVO[]>([])
|
||||||
|
const defaultWarehouse = ref<WarehouseVO>(undefined)
|
||||||
|
const formRef = ref<any>(null)
|
||||||
|
|
||||||
const formRules = reactive({
|
const formRules = reactive({
|
||||||
warehouseId: [{ required: true, message: '仓库不能为空', trigger: 'blur' }],
|
warehouseId: [{ required: true, message: '仓库不能为空', trigger: 'blur' }],
|
||||||
productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }],
|
productId: [{ required: true, message: '产品不能为空', trigger: 'blur' }],
|
||||||
count: [{ required: true, message: '产品数量不能为空', trigger: 'blur' }]
|
count: [{ required: true, message: '产品数量不能为空', trigger: 'blur' }]
|
||||||
})
|
})
|
||||||
const formRef = ref([]) // 表单 Ref
|
|
||||||
const warehouseList = ref<WarehouseVO[]>([]) // 仓库列表
|
|
||||||
const defaultWarehouse = ref<WarehouseVO>(undefined) // 默认仓库
|
|
||||||
|
|
||||||
/** 初始化设置出库项 */
|
// 初始化
|
||||||
watch(
|
watch(
|
||||||
() => props.items,
|
() => props.items,
|
||||||
async (val) => {
|
async (val) => {
|
||||||
|
if (!val) return
|
||||||
val.forEach((item) => {
|
val.forEach((item) => {
|
||||||
if (item.warehouseId == null) {
|
if (!item.warehouseId) item.warehouseId = defaultWarehouse.value?.id
|
||||||
item.warehouseId = defaultWarehouse.value?.id
|
if (item.stockCount === null && item.warehouseId != null) setStockCount(item)
|
||||||
}
|
|
||||||
if (item.stockCount === null && item.warehouseId != null) {
|
|
||||||
setStockCount(item)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
formData.value = val
|
formData.value = val
|
||||||
},
|
},
|
||||||
{ immediate: true }
|
{ immediate: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 监听合同产品变化,计算合同产品总价 */
|
// 自动计算金额
|
||||||
watch(
|
watch(
|
||||||
() => formData.value,
|
() => formData.value,
|
||||||
(val) => {
|
(val) => {
|
||||||
if (!val || val.length === 0) {
|
if (!val) return
|
||||||
return
|
|
||||||
}
|
|
||||||
// 循环处理
|
|
||||||
val.forEach((item) => {
|
val.forEach((item) => {
|
||||||
item.totalProductPrice = erpPriceMultiply(item.productPrice, item.count)
|
item.totalProductPrice = erpPriceMultiply(item.productPrice, item.count)
|
||||||
item.taxPrice = erpPriceMultiply(item.totalProductPrice, item.taxPercent / 100.0)
|
item.taxPrice = erpPriceMultiply(item.totalProductPrice, item.taxPercent / 100)
|
||||||
if (item.totalProductPrice != null) {
|
|
||||||
item.totalPrice = item.totalProductPrice + (item.taxPrice || 0)
|
item.totalPrice = item.totalProductPrice + (item.taxPrice || 0)
|
||||||
} else {
|
|
||||||
item.totalPrice = undefined
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
/** 合计 */
|
// 合计 - 移动端
|
||||||
const getSummaries = (param: SummaryMethodProps) => {
|
const summaryData = computed(() =>
|
||||||
|
formData.value.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
acc.count += Number(item.count || 0)
|
||||||
|
acc.totalProductPrice += Number(item.totalProductPrice || 0)
|
||||||
|
acc.taxPrice += Number(item.taxPrice || 0)
|
||||||
|
acc.totalPrice += Number(item.totalPrice || 0)
|
||||||
|
return acc
|
||||||
|
},
|
||||||
|
{ count: 0, totalProductPrice: 0, taxPrice: 0, totalPrice: 0 }
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// 合计 - PC端
|
||||||
|
const getSummaries = (param: any) => {
|
||||||
const { columns, data } = param
|
const { columns, data } = param
|
||||||
const sums: string[] = []
|
const sums: string[] = []
|
||||||
columns.forEach((column, index: number) => {
|
columns.forEach((column, index: number) => {
|
||||||
if (index === 0) {
|
if (index === 0) { sums[index] = '合计'; return }
|
||||||
sums[index] = '合计'
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (['count', 'totalProductPrice', 'taxPrice', 'totalPrice'].includes(column.property)) {
|
if (['count', 'totalProductPrice', 'taxPrice', 'totalPrice'].includes(column.property)) {
|
||||||
const sum = getSumValue(data.map((item) => Number(item[column.property])))
|
const sum = getSumValue(data.map((item) => Number(item[column.property])))
|
||||||
sums[index] =
|
sums[index] = column.property === 'count' ? erpCountInputFormatter(sum) : erpPriceInputFormatter(sum)
|
||||||
column.property === 'count' ? erpCountInputFormatter(sum) : erpPriceInputFormatter(sum)
|
} else { sums[index] = '' }
|
||||||
} else {
|
|
||||||
sums[index] = ''
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return sums
|
return sums
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 新增按钮操作 */
|
// 新增/删除/仓库切换
|
||||||
const handleAdd = () => {
|
const handleAdd = () => {
|
||||||
const row = {
|
formData.value.push({
|
||||||
id: undefined,
|
id: undefined,
|
||||||
productId: undefined,
|
productId: undefined,
|
||||||
productUnitName: undefined, // 产品单位
|
productUnitName: undefined,
|
||||||
productBarCode: undefined, // 产品条码
|
productBarCode: undefined,
|
||||||
productPrice: undefined,
|
productPrice: undefined,
|
||||||
stockCount: undefined,
|
stockCount: undefined,
|
||||||
count: 1,
|
count: 1,
|
||||||
@@ -268,33 +266,45 @@ const handleAdd = () => {
|
|||||||
taxPrice: undefined,
|
taxPrice: undefined,
|
||||||
totalPrice: undefined,
|
totalPrice: undefined,
|
||||||
remark: undefined
|
remark: undefined
|
||||||
}
|
})
|
||||||
formData.value.push(row)
|
|
||||||
}
|
}
|
||||||
|
const handleDelete = (index: number) => formData.value.splice(index, 1)
|
||||||
/** 删除按钮操作 */
|
const onChangeWarehouse = (warehouseId, row) => setStockCount(row)
|
||||||
const handleDelete = (index: number) => {
|
|
||||||
formData.value.splice(index, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 加载库存 */
|
|
||||||
const setStockCount = async (row: any) => {
|
const setStockCount = async (row: any) => {
|
||||||
if (!row.productId) {
|
if (!row.productId) return
|
||||||
return
|
|
||||||
}
|
|
||||||
const count = await StockApi.getStockCount(row.productId)
|
const count = await StockApi.getStockCount(row.productId)
|
||||||
row.stockCount = count || 0
|
row.stockCount = count || 0
|
||||||
}
|
}
|
||||||
|
const validate = () => formRef.value?.validate()
|
||||||
/** 表单校验 */
|
|
||||||
const validate = () => {
|
|
||||||
return formRef.value.validate()
|
|
||||||
}
|
|
||||||
defineExpose({ validate })
|
defineExpose({ validate })
|
||||||
|
|
||||||
/** 初始化 */
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
|
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
|
||||||
defaultWarehouse.value = warehouseList.value.find((item) => item.defaultStatus)
|
defaultWarehouse.value = warehouseList.value.find((item) => item.defaultStatus)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.mobile-item-list { display: flex; flex-direction: column; gap: 10px; }
|
||||||
|
.mobile-item-card { background: #f9f9fb; border-radius: 8px; padding: 12px; border: 1px solid #ebeef5;
|
||||||
|
&__header { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; padding-bottom: 8px; border-bottom: 1px solid #f0f0f0; }
|
||||||
|
&__index { font-size: 12px; color: #909399; font-weight: 600; }
|
||||||
|
&__name { flex: 1; font-size: 14px; font-weight: 600; color: #303133; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
||||||
|
&__body { font-size: 13px; }
|
||||||
|
&__info-row { display: flex; justify-content: space-between; padding: 4px 0;
|
||||||
|
&--highlight { margin-top: 4px; padding-top: 8px; border-top: 1px dashed #e4e7ed; }
|
||||||
|
}
|
||||||
|
&__info-label { color: #909399; flex-shrink: 0; }
|
||||||
|
&__info-value { color: #606266; text-align: right;
|
||||||
|
&--bold { font-weight: 600; color: #303133; }
|
||||||
|
}
|
||||||
|
&__input-group { display: flex; gap: 10px; margin-top: 6px;
|
||||||
|
:deep(.el-form-item) { flex: 1; margin-bottom: 10px; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-item-summary { background: #fff; border-radius: 8px; padding: 12px; border: 1px solid #e6a23c;
|
||||||
|
&__row { display: flex; justify-content: space-between; padding: 4px 0; font-size: 13px; color: #606266;
|
||||||
|
&--total { margin-top: 4px; padding-top: 8px; border-top: 1px solid #f0f0f0; font-weight: 600; font-size: 14px; color: #303133; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,56 +1,80 @@
|
|||||||
<!-- 可退款的采购退货单列表 -->
|
<!-- 可退款的采购退货单列表 -->
|
||||||
<template>
|
<template>
|
||||||
<Dialog
|
<!-- 移动端布局 -->
|
||||||
title="选择采购退货(仅展示可退款)"
|
<el-drawer v-if="isMobile" v-model="dialogVisible" title="选择采购退货(仅展示可退款)" direction="rtl" size="100%" :append-to-body="true" class="mobile-enable-drawer">
|
||||||
v-model="dialogVisible"
|
<!-- 搜索栏 -->
|
||||||
:appendToBody="true"
|
<div class="mobile-enable__search">
|
||||||
:scroll="true"
|
<el-input v-model="queryParams.no" placeholder="搜索退货单号" clearable @keyup.enter="handleQuery" :prefix-icon="Search" />
|
||||||
width="1080"
|
<el-button :icon="Filter" circle @click="enableFilterVisible = true" />
|
||||||
>
|
</div>
|
||||||
|
|
||||||
|
<!-- 卡片列表 -->
|
||||||
|
<div class="mobile-enable__list" v-loading="loading">
|
||||||
|
<div v-if="list.length === 0 && !loading" class="mobile-enable__empty"><el-empty description="暂无可退款退货单" /></div>
|
||||||
|
<div v-for="item in list" :key="item.id" class="mobile-enable__card" :class="{ 'mobile-enable__card--selected': isSelected(item) }" @click="toggleSelect(item)">
|
||||||
|
<div class="mobile-enable__card-check"><el-checkbox :model-value="isSelected(item)" @click.stop /></div>
|
||||||
|
<div class="mobile-enable__card-content">
|
||||||
|
<div class="mobile-enable__card-header"><span class="mobile-enable__card-no">{{ item.no }}</span></div>
|
||||||
|
<div class="mobile-enable__card-row"><span class="mobile-enable__card-label">供应商</span><span class="mobile-enable__card-value">{{ item.supplierName || '-' }}</span></div>
|
||||||
|
<div class="mobile-enable__card-row"><span class="mobile-enable__card-label">产品</span><span class="mobile-enable__card-value mobile-enable__card-value--ellipsis">{{ item.productNames || '-' }}</span></div>
|
||||||
|
<div class="mobile-enable__card-row"><span class="mobile-enable__card-label">退货时间</span><span class="mobile-enable__card-value">{{ formatDate2(item.returnTime) }}</span></div>
|
||||||
|
<div class="mobile-enable__card-row"><span class="mobile-enable__card-label">创建人</span><span class="mobile-enable__card-value">{{ item.creatorName || '-' }}</span></div>
|
||||||
|
<div class="mobile-enable__card-nums">
|
||||||
|
<div class="mobile-enable__card-num"><div class="mobile-enable__card-num-val">¥{{ erpPriceInputFormatter(item.totalPrice) }}</div><div class="mobile-enable__card-num-label">应退</div></div>
|
||||||
|
<div class="mobile-enable__card-num"><div class="mobile-enable__card-num-val" style="color:#67c23a">¥{{ erpPriceInputFormatter(item.refundPrice) }}</div><div class="mobile-enable__card-num-label">已退</div></div>
|
||||||
|
<div class="mobile-enable__card-num"><div class="mobile-enable__card-num-val" :style="{ color: item.refundPrice === item.totalPrice ? '#909399' : '#f56c6c' }">¥{{ erpPriceInputFormatter(item.totalPrice - item.refundPrice) }}</div><div class="mobile-enable__card-num-label">未退</div></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="mobile-enable__pagination" v-if="total > 0">
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选抽屉 -->
|
||||||
|
<el-drawer v-model="enableFilterVisible" title="筛选条件" direction="btt" size="50%" :append-to-body="true">
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
|
<el-form-item label="产品" prop="productId">
|
||||||
|
<el-select v-model="queryParams.productId" clearable filterable placeholder="请选择产品" style="width:100%"><el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" /></el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="退货时间" prop="returnTime">
|
||||||
|
<el-date-picker v-model="queryParams.returnTime" 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')]" style="width:100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
<el-button type="primary" @click="enableFilterVisible = false; handleQuery()">确认筛选</el-button>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="mobile-enable__footer">
|
||||||
|
<span class="mobile-enable__footer-info">已选 {{ selectionList.length }} 项</span>
|
||||||
|
<div>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
<el-button :disabled="!selectionList.length" type="primary" @click="submitForm">确 定</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else title="选择采购退货(仅展示可退款)" v-model="dialogVisible" :appendToBody="true" :scroll="true" width="1080">
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form class="-mb-15px" :model="queryParams" ref="queryFormRef" :inline="true" label-width="68px">
|
||||||
class="-mb-15px"
|
|
||||||
:model="queryParams"
|
|
||||||
ref="queryFormRef"
|
|
||||||
:inline="true"
|
|
||||||
label-width="68px"
|
|
||||||
>
|
|
||||||
<el-form-item label="退货单号" prop="no">
|
<el-form-item label="退货单号" prop="no">
|
||||||
<el-input
|
<el-input v-model="queryParams.no" placeholder="请输入退货单号" clearable @keyup.enter="handleQuery" class="!w-160px" />
|
||||||
v-model="queryParams.no"
|
|
||||||
placeholder="请输入退货单号"
|
|
||||||
clearable
|
|
||||||
@keyup.enter="handleQuery"
|
|
||||||
class="!w-160px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="产品" prop="productId">
|
<el-form-item label="产品" prop="productId">
|
||||||
<el-select
|
<el-select v-model="queryParams.productId" clearable filterable placeholder="请选择产品" class="!w-160px">
|
||||||
v-model="queryParams.productId"
|
<el-option v-for="item in productList" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
clearable
|
|
||||||
filterable
|
|
||||||
placeholder="请选择产品"
|
|
||||||
class="!w-160px"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in productList"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="退货时间" prop="orderTime">
|
<el-form-item label="退货时间" prop="orderTime">
|
||||||
<el-date-picker
|
<el-date-picker v-model="queryParams.returnTime" 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-160px" />
|
||||||
v-model="queryParams.returnTime"
|
|
||||||
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-160px"
|
|
||||||
/>
|
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item>
|
<el-form-item>
|
||||||
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
<el-button @click="handleQuery"><Icon icon="ep:search" class="mr-5px" /> 搜索</el-button>
|
||||||
@@ -60,71 +84,65 @@
|
|||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<el-table
|
<el-table v-loading="loading" :data="list" :show-overflow-tooltip="true" :stripe="true" @selection-change="handleSelectionChange">
|
||||||
v-loading="loading"
|
|
||||||
:data="list"
|
|
||||||
:show-overflow-tooltip="true"
|
|
||||||
:stripe="true"
|
|
||||||
@selection-change="handleSelectionChange"
|
|
||||||
>
|
|
||||||
<el-table-column width="30" label="选择" type="selection" />
|
<el-table-column width="30" label="选择" type="selection" />
|
||||||
<el-table-column min-width="180" label="退货单号" align="center" prop="no" />
|
<el-table-column min-width="180" label="退货单号" align="center" prop="no" />
|
||||||
<el-table-column label="供应商" align="center" prop="supplierName" />
|
<el-table-column label="供应商" align="center" prop="supplierName" />
|
||||||
<el-table-column label="产品信息" align="center" prop="productNames" min-width="200" />
|
<el-table-column label="产品信息" align="center" prop="productNames" min-width="200" />
|
||||||
<el-table-column
|
<el-table-column label="退货时间" align="center" prop="returnTime" :formatter="dateFormatter2" width="120px" />
|
||||||
label="退货时间"
|
|
||||||
align="center"
|
|
||||||
prop="returnTime"
|
|
||||||
:formatter="dateFormatter2"
|
|
||||||
width="120px"
|
|
||||||
/>
|
|
||||||
<el-table-column label="创建人" align="center" prop="creatorName" />
|
<el-table-column label="创建人" align="center" prop="creatorName" />
|
||||||
<el-table-column
|
<el-table-column label="应退金额" align="center" prop="totalPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
label="应退金额"
|
<el-table-column label="已退金额" align="center" prop="refundPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
align="center"
|
|
||||||
prop="totalPrice"
|
|
||||||
:formatter="erpPriceTableColumnFormatter"
|
|
||||||
/>
|
|
||||||
<el-table-column
|
|
||||||
label="已退金额"
|
|
||||||
align="center"
|
|
||||||
prop="refundPrice"
|
|
||||||
:formatter="erpPriceTableColumnFormatter"
|
|
||||||
/>
|
|
||||||
<el-table-column label="未退金额" align="center">
|
<el-table-column label="未退金额" align="center">
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<span v-if="scope.row.refundPrice === scope.row.totalPrice">0</span>
|
<span v-if="scope.row.refundPrice === scope.row.totalPrice">0</span>
|
||||||
<el-tag type="danger" v-else>
|
<el-tag type="danger" v-else>{{ erpPriceInputFormatter(scope.row.totalPrice - scope.row.refundPrice) }}</el-tag>
|
||||||
{{ erpPriceInputFormatter(scope.row.totalPrice - scope.row.refundPrice) }}
|
|
||||||
</el-tag>
|
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<!-- 分页 -->
|
<!-- 分页 -->
|
||||||
<Pagination
|
<Pagination v-model:limit="queryParams.pageSize" v-model:page="queryParams.pageNo" :total="total" @pagination="getList" />
|
||||||
v-model:limit="queryParams.pageSize"
|
|
||||||
v-model:page="queryParams.pageNo"
|
|
||||||
:total="total"
|
|
||||||
@pagination="getList"
|
|
||||||
/>
|
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
<el-button :disabled="!selectionList.length" type="primary" @click="submitForm">
|
<el-button :disabled="!selectionList.length" type="primary" @click="submitForm">确 定</el-button>
|
||||||
确 定
|
|
||||||
</el-button>
|
|
||||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
|
import { ref, reactive, computed } from 'vue'
|
||||||
|
import { Search, Filter } from '@element-plus/icons-vue'
|
||||||
import { ElTable } from 'element-plus'
|
import { ElTable } from 'element-plus'
|
||||||
import { dateFormatter2 } from '@/utils/formatTime'
|
import { dateFormatter2, formatDate } from '@/utils/formatTime'
|
||||||
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
import { erpPriceInputFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
import { PurchaseReturnApi, PurchaseReturnVO } from '@/api/erp/purchase/return'
|
import { PurchaseReturnApi, PurchaseReturnVO } from '@/api/erp/purchase/return'
|
||||||
import { SaleReturnVO } from '@/api/erp/sale/return'
|
import { SaleReturnVO } from '@/api/erp/sale/return'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'PurchaseInPaymentEnableList' })
|
defineOptions({ name: 'PurchaseReturnRefundEnableList' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
const enableFilterVisible = ref(false)
|
||||||
|
|
||||||
|
const formatDate2 = (date: any) => {
|
||||||
|
if (!date) return '-'
|
||||||
|
return formatDate(new Date(date), 'YYYY-MM-DD')
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 移动端选中操作 */
|
||||||
|
const isSelected = (item: PurchaseReturnVO) => {
|
||||||
|
return selectionList.value.some((s) => s.id === item.id)
|
||||||
|
}
|
||||||
|
const toggleSelect = (item: PurchaseReturnVO) => {
|
||||||
|
const idx = selectionList.value.findIndex((s) => s.id === item.id)
|
||||||
|
if (idx >= 0) {
|
||||||
|
selectionList.value.splice(idx, 1)
|
||||||
|
} else {
|
||||||
|
selectionList.value.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const list = ref<PurchaseReturnVO[]>([]) // 列表的数据
|
const list = ref<PurchaseReturnVO[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
@@ -198,3 +216,109 @@ const handleQuery = () => {
|
|||||||
getList()
|
getList()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-enable__search {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
.el-input { flex: 1; }
|
||||||
|
}
|
||||||
|
.mobile-enable__list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
.mobile-enable__empty {
|
||||||
|
padding: 40px 0;
|
||||||
|
}
|
||||||
|
.mobile-enable__card {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
&--selected {
|
||||||
|
border-color: #409eff;
|
||||||
|
background: #f0f7ff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-enable__card-check {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding-top: 2px;
|
||||||
|
}
|
||||||
|
.mobile-enable__card-content {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
.mobile-enable__card-header {
|
||||||
|
margin-bottom: 6px;
|
||||||
|
}
|
||||||
|
.mobile-enable__card-no {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
.mobile-enable__card-row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 2px 0;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
.mobile-enable__card-label {
|
||||||
|
color: #909399;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
.mobile-enable__card-value {
|
||||||
|
color: #606266;
|
||||||
|
text-align: right;
|
||||||
|
&--ellipsis {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
max-width: 180px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.mobile-enable__card-nums {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-around;
|
||||||
|
margin-top: 8px;
|
||||||
|
padding-top: 8px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
.mobile-enable__card-num {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.mobile-enable__card-num-val {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
.mobile-enable__card-num-label {
|
||||||
|
font-size: 11px;
|
||||||
|
color: #909399;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
.mobile-enable__pagination {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
:deep(.el-pagination) { flex-wrap: wrap; justify-content: center; }
|
||||||
|
}
|
||||||
|
.mobile-enable__footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.mobile-enable__footer-info {
|
||||||
|
font-size: 13px;
|
||||||
|
color: #909399;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,15 +1,36 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="mobile-purchase-return">
|
<!-- 移动端布局 -->
|
||||||
<!-- 顶部操作栏 -->
|
<div v-if="isMobile" class="mobile-purchase-return">
|
||||||
<div class="mobile-header">
|
<div class="mobile-header">
|
||||||
<div class="mobile-header__search">
|
<div class="mobile-header__search"><el-input v-model="queryParams.no" placeholder="搜索退货单号" clearable @keyup.enter="handleQuery" :prefix-icon="Search" /></div>
|
||||||
<el-input v-model="queryParams.no" placeholder="搜索退货单号" clearable @keyup.enter="handleQuery" :prefix-icon="Search" />
|
|
||||||
</div>
|
|
||||||
<div class="mobile-header__actions">
|
<div class="mobile-header__actions">
|
||||||
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
||||||
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:purchase-return:create']" />
|
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:purchase-return:create']" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="mobile-header__quick-filter">
|
||||||
|
<div
|
||||||
|
class="quick-filter-item"
|
||||||
|
:class="{ active: queryParams.status === undefined }"
|
||||||
|
@click="handleQuickFilter(undefined)"
|
||||||
|
>
|
||||||
|
全部订单
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="quick-filter-item"
|
||||||
|
:class="{ active: queryParams.status === 10 }"
|
||||||
|
@click="handleQuickFilter(10)"
|
||||||
|
>
|
||||||
|
待审核
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="quick-filter-item"
|
||||||
|
:class="{ active: queryParams.status === 20 }"
|
||||||
|
@click="handleQuickFilter(20)"
|
||||||
|
>
|
||||||
|
已审核
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 卡片列表 -->
|
<!-- 卡片列表 -->
|
||||||
<div class="mobile-list" v-loading="loading">
|
<div class="mobile-list" v-loading="loading">
|
||||||
@@ -114,29 +135,251 @@
|
|||||||
</template>
|
</template>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
|
<ContentWrap>
|
||||||
|
<!-- 搜索工作栏 -->
|
||||||
|
<el-form
|
||||||
|
class="-mb-15px"
|
||||||
|
:model="queryParams"
|
||||||
|
ref="queryFormRef"
|
||||||
|
:inline="true"
|
||||||
|
label-width="68px"
|
||||||
|
>
|
||||||
|
<el-form-item label="退货单号" prop="no">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.no"
|
||||||
|
placeholder="请输入退货单号"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="产品" prop="productId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.productId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择产品"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in productList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="退货时间" prop="inTime">
|
||||||
|
<el-date-picker
|
||||||
|
v-model="queryParams.inTime"
|
||||||
|
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-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="供应商" prop="supplierId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.supplierId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择供供应商"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in supplierList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="仓库" prop="warehouseId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.warehouseId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择仓库"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in warehouseList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="创建人" prop="creator">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.creator"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择创建人"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in userList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.nickname"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="关联订单" prop="orderNo">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.orderNo"
|
||||||
|
placeholder="请输入关联订单"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="结算账户" prop="accountId">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.accountId"
|
||||||
|
clearable
|
||||||
|
filterable
|
||||||
|
placeholder="请选择结算账户"
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option
|
||||||
|
v-for="item in accountList"
|
||||||
|
:key="item.id"
|
||||||
|
:label="item.name"
|
||||||
|
:value="item.id"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="退款状态" prop="refundStatus">
|
||||||
|
<el-select
|
||||||
|
v-model="queryParams.refundStatus"
|
||||||
|
placeholder="请选择退款状态"
|
||||||
|
clearable
|
||||||
|
class="!w-240px"
|
||||||
|
>
|
||||||
|
<el-option label="未退款" value="0" />
|
||||||
|
<el-option label="部分退款" value="1" />
|
||||||
|
<el-option label="全部退款" value="2" />
|
||||||
|
</el-select>
|
||||||
|
</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.ERP_AUDIT_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.label"
|
||||||
|
:value="dict.value"
|
||||||
|
/>
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.remark"
|
||||||
|
placeholder="请输入备注"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
class="!w-240px"
|
||||||
|
/>
|
||||||
|
</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
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
@click="openForm('create')"
|
||||||
|
v-hasPermi="['erp:purchase-return:create']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:plus" class="mr-5px" /> 新增
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="success"
|
||||||
|
plain
|
||||||
|
@click="handleExport"
|
||||||
|
:loading="exportLoading"
|
||||||
|
v-hasPermi="['erp:purchase-return:export']"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:download" class="mr-5px" /> 导出
|
||||||
|
</el-button>
|
||||||
|
<el-button
|
||||||
|
type="danger"
|
||||||
|
plain
|
||||||
|
@click="handleDelete(selectionList.map((item) => item.id))"
|
||||||
|
v-hasPermi="['erp:purchase-return:delete']"
|
||||||
|
:disabled="selectionList.length === 0"
|
||||||
|
>
|
||||||
|
<Icon icon="ep:delete" class="mr-5px" /> 删除
|
||||||
|
</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</ContentWrap>
|
||||||
|
|
||||||
|
<ContentWrap>
|
||||||
|
<el-table v-loading="loading" :data="list" :stripe="true" :show-overflow-tooltip="true" @selection-change="handleSelectionChange" @row-click="handleRowClick">
|
||||||
|
<el-table-column width="30" label="选择" type="selection" />
|
||||||
|
<el-table-column min-width="180" label="退货单号" align="center" prop="no" />
|
||||||
|
<el-table-column label="产品信息" align="center" prop="productNames" min-width="200" />
|
||||||
|
<el-table-column label="供应商" align="center" prop="supplierName" />
|
||||||
|
<el-table-column label="退货时间" align="center" prop="returnTime" :formatter="dateFormatter2" width="120px" sortable />
|
||||||
|
<el-table-column label="创建人" align="center" prop="creatorName" />
|
||||||
|
<el-table-column label="总数量" align="center" prop="totalCount" :formatter="erpCountTableColumnFormatter" />
|
||||||
|
<el-table-column label="应退金额" align="center" prop="totalPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
|
<el-table-column label="已退金额" align="center" prop="refundPrice" :formatter="erpPriceTableColumnFormatter" />
|
||||||
|
<el-table-column label="未退金额" align="center"><template #default="scope"><span v-if="scope.row.refundPrice === scope.row.totalPrice">0</span><el-tag type="danger" v-else>{{ erpPriceInputFormatter(scope.row.totalPrice - scope.row.refundPrice) }}</el-tag></template></el-table-column>
|
||||||
|
<el-table-column label="审核状态" align="center" fixed="right" width="90" prop="status"><template #default="scope"><dict-tag :type="DICT_TYPE.ERP_AUDIT_STATUS" :value="scope.row.status" /></template></el-table-column>
|
||||||
|
<el-table-column label="操作" align="center" fixed="right" width="220">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button link @click="openForm('detail', scope.row.id)" v-hasPermi="['erp:purchase-return:query']">详情</el-button>
|
||||||
|
<el-button link type="primary" @click="openForm('update', scope.row.id)" v-hasPermi="['erp:purchase-return:update']" :disabled="scope.row.status === 20">编辑</el-button>
|
||||||
|
<el-button link type="primary" @click="handleUpdateStatus(scope.row.id, 20)" v-hasPermi="['erp:purchase-return:update-status']" v-if="scope.row.status === 10">审批</el-button>
|
||||||
|
<el-button link type="danger" @click="handleUpdateStatus(scope.row.id, 10)" v-hasPermi="['erp:purchase-return:update-status']" v-if="scope.row.status === 20">反审批</el-button>
|
||||||
|
<el-button link type="danger" @click="handleDelete([scope.row.id])" v-hasPermi="['erp:purchase-return:delete']">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
||||||
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<PurchaseReturnForm ref="formRef" @success="getList" />
|
<PurchaseReturnForm ref="formRef" @success="getList" />
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate, dateFormatter2 } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
import { PurchaseReturnApi, PurchaseReturnVO } from '@/api/erp/purchase/return'
|
import { PurchaseReturnApi, PurchaseReturnVO } from '@/api/erp/purchase/return'
|
||||||
import PurchaseReturnForm from './PurchaseReturnForm.vue'
|
import PurchaseReturnForm from './PurchaseReturnForm.vue'
|
||||||
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
import { ProductApi, ProductVO } from '@/api/erp/product/product'
|
||||||
import { UserVO } from '@/api/system/user'
|
import { UserVO } from '@/api/system/user'
|
||||||
import * as UserApi from '@/api/system/user'
|
import * as UserApi from '@/api/system/user'
|
||||||
import { erpCountInputFormatter, erpPriceInputFormatter } from '@/utils'
|
import { erpCountInputFormatter, erpPriceInputFormatter, erpCountTableColumnFormatter, erpPriceTableColumnFormatter } from '@/utils'
|
||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
|
import { WarehouseApi, WarehouseVO } from '@/api/erp/stock/warehouse'
|
||||||
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
import { AccountApi, AccountVO } from '@/api/erp/finance/account'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 采购退货列表 */
|
/** ERP 采购退货列表 */
|
||||||
defineOptions({ name: 'ErpPurchaseReturn' })
|
defineOptions({ name: 'ErpPurchaseReturn' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage()
|
const message = useMessage()
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
@@ -218,9 +461,27 @@ const handleDelete = async (ids: number[]) => {
|
|||||||
await PurchaseReturnApi.deletePurchaseReturn(ids)
|
await PurchaseReturnApi.deletePurchaseReturn(ids)
|
||||||
message.success(t('common.delSuccess'))
|
message.success(t('common.delSuccess'))
|
||||||
await getList()
|
await getList()
|
||||||
|
selectionList.value = selectionList.value.filter((item) => !ids.includes(item.id))
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 选中操作(PC端) */
|
||||||
|
const selectionList = ref<PurchaseReturnVO[]>([])
|
||||||
|
const handleSelectionChange = (rows: PurchaseReturnVO[]) => {
|
||||||
|
selectionList.value = rows
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 行点击操作(PC端) */
|
||||||
|
const handleRowClick = (row: PurchaseReturnVO, column: any, event: MouseEvent) => {
|
||||||
|
const target = event.target as HTMLElement
|
||||||
|
if (target.tagName === 'BUTTON' || target.tagName === 'A' || target.tagName === 'I' || target.tagName === 'svg' || target.closest('button') || target.closest('a') || target.closest('.el-button') || target.closest('.el-checkbox')) return
|
||||||
|
if (row.status === 20) {
|
||||||
|
openForm('detail', row.id)
|
||||||
|
} else if (row.status === 10) {
|
||||||
|
openForm('update', row.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleUpdateStatus = async (id: number, status: number) => {
|
const handleUpdateStatus = async (id: number, status: number) => {
|
||||||
try {
|
try {
|
||||||
await message.confirm(`确定${status === 20 ? '审批' : '反审批'}该退货吗?`)
|
await message.confirm(`确定${status === 20 ? '审批' : '反审批'}该退货吗?`)
|
||||||
@@ -251,9 +512,41 @@ onMounted(async () => {
|
|||||||
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
|
warehouseList.value = await WarehouseApi.getWarehouseSimpleList()
|
||||||
accountList.value = await AccountApi.getAccountSimpleList()
|
accountList.value = await AccountApi.getAccountSimpleList()
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
/** 快捷分类筛选 */
|
||||||
|
const handleQuickFilter = (status: number | undefined) => {
|
||||||
|
queryParams.status = status
|
||||||
|
queryParams.pageNo = 1
|
||||||
|
getList()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
||||||
|
.mobile-header__quick-filter {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
margin: 8px 0;
|
||||||
|
justify-content: flex-start;
|
||||||
|
|
||||||
|
.quick-filter-item {
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-size: 14px;
|
||||||
|
border-radius: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #909399;
|
||||||
|
background: transparent;
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #fff;
|
||||||
|
background: #409eff;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.mobile-purchase-return {
|
.mobile-purchase-return {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: #f5f5f5;
|
background: #f5f5f5;
|
||||||
|
|||||||
@@ -1,5 +1,79 @@
|
|||||||
<template>
|
<template>
|
||||||
<Dialog :title="dialogTitle" v-model="dialogVisible" width="500">
|
<!-- 移动端使用抽屉 -->
|
||||||
|
<el-drawer
|
||||||
|
v-if="isMobile"
|
||||||
|
v-model="dialogVisible"
|
||||||
|
:title="dialogTitle"
|
||||||
|
direction="rtl"
|
||||||
|
size="100%"
|
||||||
|
>
|
||||||
|
<el-form
|
||||||
|
ref="formRef"
|
||||||
|
:model="formData"
|
||||||
|
:rules="formRules"
|
||||||
|
label-position="top"
|
||||||
|
v-loading="formLoading"
|
||||||
|
>
|
||||||
|
<div class="mobile-form-section">
|
||||||
|
<div class="mobile-form-section__title">基本信息</div>
|
||||||
|
<el-form-item label="司机姓名" prop="driverName">
|
||||||
|
<el-input v-model="formData.driverName" placeholder="请输入司机姓名" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="车牌号" prop="licensePlate">
|
||||||
|
<el-input v-model="formData.licensePlate" placeholder="请输入车牌号" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="联系电话" prop="contactPhone">
|
||||||
|
<el-input v-model="formData.contactPhone" placeholder="请输入联系电话" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mobile-form-section">
|
||||||
|
<div class="mobile-form-section__title">车辆参数</div>
|
||||||
|
<el-row :gutter="12">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="长(米)" prop="carLength">
|
||||||
|
<el-input-number v-model="formData.carLength" :min="0" :precision="2" :step="0.1" controls-position="right" style="width:100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="宽(米)" prop="carWidth">
|
||||||
|
<el-input-number v-model="formData.carWidth" :min="0" :precision="2" :step="0.1" controls-position="right" style="width:100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
<el-form-item label="核定吨位(吨)" prop="ratedTonnage">
|
||||||
|
<el-input-number v-model="formData.ratedTonnage" :min="0" :precision="2" :step="0.1" controls-position="right" style="width:100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mobile-form-section">
|
||||||
|
<div class="mobile-form-section__title">其他信息</div>
|
||||||
|
<el-form-item label="状态" prop="status">
|
||||||
|
<el-radio-group v-model="formData.status">
|
||||||
|
<el-radio
|
||||||
|
v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
|
||||||
|
:key="dict.value"
|
||||||
|
:label="dict.value"
|
||||||
|
>
|
||||||
|
{{ dict.label }}
|
||||||
|
</el-radio>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="备注" prop="remark">
|
||||||
|
<el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<div class="mobile-drawer-footer">
|
||||||
|
<el-button @click="dialogVisible = false" style="flex:1">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitForm" :disabled="formLoading" style="flex:1">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端使用对话框 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible" width="500">
|
||||||
<el-form
|
<el-form
|
||||||
ref="formRef"
|
ref="formRef"
|
||||||
:model="formData"
|
:model="formData"
|
||||||
@@ -46,12 +120,19 @@
|
|||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import * as CarApi from '@/api/erp/purchase/car'
|
import * as CarApi from '@/api/erp/purchase/car'
|
||||||
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
|
||||||
|
|
||||||
|
import { computed } from 'vue'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
defineOptions({ name: 'CarForm' })
|
defineOptions({ name: 'CarForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -137,3 +218,25 @@ const resetForm = () => {
|
|||||||
formRef.value?.resetFields()
|
formRef.value?.resetFields()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-form-section {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #303133;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
border-bottom: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-drawer-footer {
|
||||||
|
display: flex;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -1,4 +1,87 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<!-- 移动端布局 -->
|
||||||
|
<div v-if="isMobile" class="mobile-car-list">
|
||||||
|
<!-- 顶部操作栏 -->
|
||||||
|
<div class="mobile-header">
|
||||||
|
<div class="mobile-header__search">
|
||||||
|
<el-input
|
||||||
|
v-model="queryParams.licensePlate"
|
||||||
|
placeholder="搜索车牌号/司机"
|
||||||
|
clearable
|
||||||
|
@keyup.enter="handleQuery"
|
||||||
|
:prefix-icon="Search"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-header__actions">
|
||||||
|
<el-button :icon="Filter" circle @click="filterVisible = true" />
|
||||||
|
<el-button type="primary" :icon="Plus" circle @click="openForm('create')" v-hasPermi="['erp:car:create']" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 卡片列表 -->
|
||||||
|
<div class="mobile-list" v-loading="loading">
|
||||||
|
<div v-if="list.length === 0 && !loading" class="mobile-empty">
|
||||||
|
<el-empty description="暂无车辆数据" />
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-for="row in list"
|
||||||
|
:key="row.id"
|
||||||
|
class="mobile-card"
|
||||||
|
@click="openForm('update', row.id)"
|
||||||
|
>
|
||||||
|
<div class="mobile-card__header">
|
||||||
|
<span class="mobile-card__plate">{{ row.licensePlate }}</span>
|
||||||
|
<dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__body">
|
||||||
|
<div class="mobile-card__row">
|
||||||
|
<span class="mobile-card__label">司机</span>
|
||||||
|
<span class="mobile-card__value">{{ row.driverName }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__row">
|
||||||
|
<span class="mobile-card__label">电话</span>
|
||||||
|
<span class="mobile-card__value">{{ row.contactPhone || '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__row">
|
||||||
|
<span class="mobile-card__label">核定吨位</span>
|
||||||
|
<span class="mobile-card__value">{{ row.ratedTonnage ? row.ratedTonnage + ' 吨' : '-' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__row">
|
||||||
|
<span class="mobile-card__label">车辆尺寸</span>
|
||||||
|
<span class="mobile-card__value">{{ row.carLength && row.carWidth ? `${row.carLength}m × ${row.carWidth}m` : '-' }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mobile-card__footer" @click.stop>
|
||||||
|
<el-button size="small" type="primary" @click="openForm('update', row.id)" v-hasPermi="['erp:car:update']">编辑</el-button>
|
||||||
|
<el-button size="small" type="danger" @click="handleDelete(row.id)" v-hasPermi="['erp:car:delete']">删除</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 分页 -->
|
||||||
|
<div class="mobile-pagination" v-if="total > 0">
|
||||||
|
<Pagination :total="total" v-model:page="queryParams.pageNo" v-model:limit="queryParams.pageSize" :page-sizes="[10, 20]" layout="total, prev, pager, next" :pager-count="5" @pagination="getList" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 筛选抽屉 -->
|
||||||
|
<el-drawer v-model="filterVisible" title="筛选条件" direction="btt" size="50%">
|
||||||
|
<el-form :model="queryParams" ref="queryFormRef" label-position="top">
|
||||||
|
<el-form-item label="司机姓名" prop="driverName">
|
||||||
|
<el-input v-model="queryParams.driverName" placeholder="请输入司机姓名" clearable style="width:100%" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="车牌号" prop="licensePlate">
|
||||||
|
<el-input v-model="queryParams.licensePlate" placeholder="请输入车牌号" clearable style="width:100%" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="resetQuery">重置</el-button>
|
||||||
|
<el-button type="primary" @click="handleFilterConfirm">确认筛选</el-button>
|
||||||
|
</template>
|
||||||
|
</el-drawer>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<template v-else>
|
||||||
<ContentWrap>
|
<ContentWrap>
|
||||||
<!-- 搜索工作栏 -->
|
<!-- 搜索工作栏 -->
|
||||||
<el-form
|
<el-form
|
||||||
@@ -107,6 +190,7 @@
|
|||||||
@pagination="getList"
|
@pagination="getList"
|
||||||
/>
|
/>
|
||||||
</ContentWrap>
|
</ContentWrap>
|
||||||
|
</template>
|
||||||
|
|
||||||
<!-- 表单弹窗:添加/修改 -->
|
<!-- 表单弹窗:添加/修改 -->
|
||||||
<CarForm ref="formRef" @success="getList" />
|
<CarForm ref="formRef" @success="getList" />
|
||||||
@@ -116,6 +200,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { DICT_TYPE } from '@/utils/dict'
|
import { DICT_TYPE } from '@/utils/dict'
|
||||||
import { formatDate } from '@/utils/formatTime'
|
import { formatDate } from '@/utils/formatTime'
|
||||||
import download from '@/utils/download'
|
import download from '@/utils/download'
|
||||||
@@ -123,16 +208,22 @@ import * as CarApi from '@/api/erp/purchase/car/index'
|
|||||||
import * as CarVO from '@/api/erp/purchase/car/index'
|
import * as CarVO from '@/api/erp/purchase/car/index'
|
||||||
import CarForm from './CarForm.vue'
|
import CarForm from './CarForm.vue'
|
||||||
import CarImportForm from './CarImportForm.vue'
|
import CarImportForm from './CarImportForm.vue'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
import { Search, Filter, Plus } from '@element-plus/icons-vue'
|
||||||
|
|
||||||
/** 车辆管理列表 */
|
/** 车辆管理列表 */
|
||||||
defineOptions({ name: 'ErpCar' })
|
defineOptions({ name: 'ErpCar' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
|
|
||||||
const loading = ref(true) // 列表的加载中
|
const loading = ref(true) // 列表的加载中
|
||||||
const list = ref<any[]>([]) // 列表的数据
|
const list = ref<any[]>([]) // 列表的数据
|
||||||
const total = ref(0) // 列表的总页数
|
const total = ref(0) // 列表的总页数
|
||||||
|
const filterVisible = ref(false) // 筛选抽屉
|
||||||
const queryParams = reactive({
|
const queryParams = reactive({
|
||||||
pageNo: 1,
|
pageNo: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
@@ -165,7 +256,13 @@ const handleQuery = () => {
|
|||||||
|
|
||||||
/** 重置按钮操作 */
|
/** 重置按钮操作 */
|
||||||
const resetQuery = () => {
|
const resetQuery = () => {
|
||||||
queryFormRef.value.resetFields()
|
queryFormRef.value?.resetFields()
|
||||||
|
handleQuery()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 筛选确认 */
|
||||||
|
const handleFilterConfirm = () => {
|
||||||
|
filterVisible.value = false
|
||||||
handleQuery()
|
handleQuery()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,7 +307,87 @@ const handleExport = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 初始化 **/
|
/** 初始化 **/
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getList()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.mobile-car-list {
|
||||||
|
padding: 12px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-header {
|
||||||
|
display: flex;
|
||||||
|
gap: 8px;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
&__search { flex: 1; }
|
||||||
|
&__actions { display: flex; gap: 4px; flex-shrink: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-empty { padding: 40px 0; }
|
||||||
|
|
||||||
|
.mobile-card {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 14px;
|
||||||
|
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.06);
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__plate {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #303133;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__body { font-size: 13px; }
|
||||||
|
|
||||||
|
&__row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
color: #909399;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-right: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__value {
|
||||||
|
color: #606266;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 10px;
|
||||||
|
padding-top: 10px;
|
||||||
|
border-top: 1px solid #f0f0f0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mobile-pagination {
|
||||||
|
margin-top: 12px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
:deep(.el-pagination) { flex-wrap: wrap; justify-content: center; }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|||||||
@@ -1,22 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-drawer
|
<!-- 移动端布局 -->
|
||||||
v-model="dialogVisible"
|
<el-drawer v-if="isMobile" v-model="dialogVisible" :title="dialogTitle" direction="rtl" size="100%" :close-on-press-escape="true" :destroy-on-close="true" :append-to-body="true" class="mobile-form-drawer">
|
||||||
:title="dialogTitle"
|
|
||||||
direction="rtl"
|
|
||||||
size="100%"
|
|
||||||
:close-on-press-escape="true"
|
|
||||||
:destroy-on-close="true"
|
|
||||||
:append-to-body="true"
|
|
||||||
class="mobile-form-drawer"
|
|
||||||
>
|
|
||||||
<div class="mobile-form" v-loading="formLoading">
|
<div class="mobile-form" v-loading="formLoading">
|
||||||
<el-form
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-position="top" :disabled="disabled">
|
||||||
ref="formRef"
|
|
||||||
:model="formData"
|
|
||||||
:rules="formRules"
|
|
||||||
label-position="top"
|
|
||||||
:disabled="disabled"
|
|
||||||
>
|
|
||||||
<div class="mobile-form-section">
|
<div class="mobile-form-section">
|
||||||
<div class="mobile-form-section__title">基础信息</div>
|
<div class="mobile-form-section__title">基础信息</div>
|
||||||
<el-form-item label="名称" prop="name">
|
<el-form-item label="名称" prop="name">
|
||||||
@@ -251,15 +237,62 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-drawer>
|
</el-drawer>
|
||||||
|
|
||||||
|
<!-- PC端布局 -->
|
||||||
|
<Dialog v-else :title="dialogTitle" v-model="dialogVisible" width="800px" class="supplier-form-dialog">
|
||||||
|
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" class="supplier-form" v-loading="formLoading" :disabled="disabled">
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="24"><el-divider class="supplier-section-divider">基础信息</el-divider></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="名称" prop="name"><el-input v-model="formData.name" placeholder="请输入名称" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="企业性质" prop="enterpriseNature"><el-select v-model="formData.enterpriseNature" placeholder="请选择企业性质" class="!w-1/1"><el-option v-for="item in enterpriseNatureOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="供应商类型" prop="type"><el-select v-model="formData.type" placeholder="请选择供应商类型" class="!w-1/1"><el-option label="生产型" :value="1" /><el-option label="贸易型" :value="2" /><el-option label="服务型" :value="3" /><el-option label="其他" :value="4" /></el-select></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="注册资金" prop="registeredCapital"><el-input-number v-model="formData.registeredCapital" :min="0" :precision="2" placeholder="请输入注册资金" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="使用/营业期限" prop="validPeriod"><el-input v-model="formData.validPeriod" placeholder="请输入使用/营业期限" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="邮编" prop="zipCode"><el-input v-model="formData.zipCode" placeholder="请输入邮编" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="联系人" prop="contact"><el-input v-model="formData.contact" placeholder="请输入联系人" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="手机号码" prop="mobile"><el-input v-model="formData.mobile" placeholder="请输入手机号码" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="联系电话" prop="telephone"><el-input v-model="formData.telephone" placeholder="请输入联系电话" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="电子邮箱" prop="email"><el-input v-model="formData.email" placeholder="请输入电子邮箱" /></el-form-item></el-col>
|
||||||
|
<el-col :span="24"><el-divider /></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="纳税人识别号" prop="taxNo"><el-input v-model="formData.taxNo" placeholder="请输入纳税人识别号" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="税率(%)" prop="taxPercent"><el-input-number v-model="formData.taxPercent" :min="0" :precision="4" placeholder="请输入税率" class="!w-1/1" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="开户行" prop="bankName"><el-input v-model="formData.bankName" placeholder="请输入开户行" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="开户账号" prop="bankAccount"><el-input v-model="formData.bankAccount" placeholder="请输入开户账号" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="开户地址" prop="bankAddress"><el-input v-model="formData.bankAddress" placeholder="请输入开户地址" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="银行行号" prop="bankLineNo"><el-input v-model="formData.bankLineNo" placeholder="请输入银行行号" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="付款条件" prop="paymentTerms"><el-select v-model="formData.paymentTerms" placeholder="请选择付款条件" class="!w-1/1"><el-option v-for="item in paymentTermsOptions" :key="item.value" :label="item.label" :value="item.value" /></el-select></el-form-item></el-col>
|
||||||
|
<el-col :span="24"><el-divider /></el-col>
|
||||||
|
<el-col :span="24"><el-form-item label="生产/经营范围" prop="productScope"><el-input type="textarea" v-model="formData.productScope" placeholder="请输入生产产品范围/经营范围" /></el-form-item></el-col>
|
||||||
|
<el-col :span="24"><el-form-item label="生产厂地址" prop="factoryAddress"><el-input type="textarea" v-model="formData.factoryAddress" placeholder="请输入生产厂地址" /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="年度订单数" prop="annualOrderNums"><el-input :model-value="formatInteger(formData.annualOrderNums)" placeholder="系统自动生成" disabled /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="年度订单额" prop="annualOrderAmounts"><el-input :model-value="formatAmount(formData.annualOrderAmounts)" placeholder="系统自动生成" disabled /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="年度已付款" prop="annualPaidAmounts"><el-input :model-value="formatAmount(formData.annualPaidAmounts)" placeholder="系统自动生成" disabled /></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="24" :sm="12"><el-form-item label="累计未付款" prop="totalUnpaidAmounts"><el-input :model-value="formatAmount(formData.totalUnpaidAmounts)" placeholder="系统自动生成" disabled /></el-form-item></el-col>
|
||||||
|
<el-col :span="24"><el-form-item label="备注" prop="remark"><el-input type="textarea" v-model="formData.remark" placeholder="请输入备注" /></el-form-item></el-col>
|
||||||
|
<el-col :span="24"><el-divider class="supplier-section-divider">状态信息</el-divider></el-col>
|
||||||
|
<el-col :span="12" :xs="12" :sm="12"><el-form-item label="开启状态" prop="status"><el-radio-group v-model="formData.status"><el-radio v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio></el-radio-group></el-form-item></el-col>
|
||||||
|
<el-col :span="12" :xs="12" :sm="12"><el-form-item label="排序" prop="sort"><el-input-number v-model="formData.sort" placeholder="请输入排序" class="!w-1/1" :precision="0" /></el-form-item></el-col>
|
||||||
|
</el-row>
|
||||||
|
</el-form>
|
||||||
|
<template #footer>
|
||||||
|
<el-button @click="submitForm" type="primary" :disabled="formLoading" v-if="!disabled">确 定</el-button>
|
||||||
|
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
import { getIntDictOptions, DICT_TYPE } from '@/utils/dict'
|
||||||
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
import { SupplierApi, SupplierVO } from '@/api/erp/purchase/supplier'
|
||||||
import { CommonStatusEnum } from '@/utils/constants'
|
import { CommonStatusEnum } from '@/utils/constants'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
|
||||||
/** ERP 表单 */
|
/** ERP 表单 */
|
||||||
defineOptions({ name: 'SupplierForm' })
|
defineOptions({ name: 'SupplierForm' })
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
const isMobile = computed(() => width.value < 768)
|
||||||
|
|
||||||
const { t } = useI18n() // 国际化
|
const { t } = useI18n() // 国际化
|
||||||
const message = useMessage() // 消息弹窗
|
const message = useMessage() // 消息弹窗
|
||||||
|
|
||||||
@@ -527,4 +560,18 @@ const resetForm = () => {
|
|||||||
:deep(.el-date-editor) {
|
:deep(.el-date-editor) {
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* PC端样式 */
|
||||||
|
:deep(.supplier-form .el-form-item__label) {
|
||||||
|
white-space: nowrap !important;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
:deep(.supplier-form .supplier-section-divider) {
|
||||||
|
margin: 16px 0 12px;
|
||||||
|
}
|
||||||
|
:deep(.supplier-form-dialog .el-dialog__body) {
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-right: 12px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user