收据设置,招商人员
This commit is contained in:
parent
50a376b14b
commit
12b6d3fe79
@ -1,5 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"html2canvas": "^1.4.1",
|
||||
"mammoth": "^1.9.0",
|
||||
"pdf-lib": "^1.17.1",
|
||||
"pdfjs-dist": "^5.1.91",
|
||||
"qrcode.vue": "^3.6.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
NODE_ENV = 'development'
|
||||
|
||||
# 开发环境API地址
|
||||
VUE_APP_BASE_API = http://192.168.137.3:8080/api
|
||||
VUE_APP_BASE_API = http://192.168.137.38:8080
|
@ -90,4 +90,171 @@ export function deleteFeeType(id) {
|
||||
url: `/finance/type/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
// 收据编号规则相关接口
|
||||
export function listReceiptRule(params) {
|
||||
return request({
|
||||
url: '/receipt/number-rule/page',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function addReceiptRule(data) {
|
||||
return request({
|
||||
url: '/receipt/number-rule',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteReceiptRule(id) {
|
||||
return request({
|
||||
url: `/receipt/number-rule/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function getNextReceiptNumber(ruleId) {
|
||||
return request({
|
||||
url: `/receipt/number/next/${ruleId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getBatchReceiptNumbers(ruleId, count) {
|
||||
return request({
|
||||
url: `/receipt/numbers/${ruleId}/${count}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 收据模板相关接口
|
||||
export function getReceiptTemplateList(params) {
|
||||
return request({
|
||||
url: '/receipt/template/page',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function addReceiptTemplate(data) {
|
||||
return request({
|
||||
url: '/receipt/template',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateReceiptTemplate(id, data) {
|
||||
return request({
|
||||
url: `/receipt/template/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteReceiptTemplate(id) {
|
||||
return request({
|
||||
url: `/receipt/template/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function previewReceiptTemplate(id) {
|
||||
return request({
|
||||
url: `/receipt/template/preview/${id}`,
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
export function downloadReceiptTemplate(id) {
|
||||
return request({
|
||||
url: `/receipt/template/download/${id}`,
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
export function downloadExampleTemplate() {
|
||||
return request({
|
||||
url: '/receipt/template/download-example',
|
||||
method: 'get',
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
export function getReceiptKeywords() {
|
||||
return request({
|
||||
url: '/receipt/template/keywords',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function checkTemplateName(params) {
|
||||
return request({
|
||||
url: '/receipt/template/check-name',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 收款方信息相关接口
|
||||
export function getPayeeInfo(params) {
|
||||
if (typeof params === 'object') {
|
||||
// 查询列表
|
||||
return request({
|
||||
url: '/receipt/payee/page',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
} else {
|
||||
// 查询详情
|
||||
return request({
|
||||
url: `/receipt/payee/${params}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function addPayeeInfo(data) {
|
||||
return request({
|
||||
url: '/receipt/payee',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updatePayeeInfo(id, data) {
|
||||
return request({
|
||||
url: `/receipt/payee/${id}`,
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deletePayeeInfo(id) {
|
||||
return request({
|
||||
url: `/receipt/payee/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 项目列表
|
||||
export function getProjectList() {
|
||||
return request({
|
||||
url: '/project/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取楼宇列表
|
||||
export function getBuildingList(projectId) {
|
||||
return request({
|
||||
url: `/business/building/list/${projectId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
255
pc/src/api/merchant.js
Normal file
255
pc/src/api/merchant.js
Normal file
@ -0,0 +1,255 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
// 招商团队相关API
|
||||
export function getMerchantTeamList(params) {
|
||||
return request({
|
||||
url: '/business/team/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function addMerchantTeam(data) {
|
||||
return request({
|
||||
url: '/business/team',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateMerchantTeam(data) {
|
||||
return request({
|
||||
url: '/business/team',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteMerchantTeam(id) {
|
||||
return request({
|
||||
url: `/business/team/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 招商人员相关API
|
||||
export function getMerchantPersonnelList(params) {
|
||||
return request({
|
||||
url: '/business/personnel/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getPersonnelByTeamId(teamId) {
|
||||
return request({
|
||||
url: `/business/personnel/team/${teamId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getPersonnelDetail(id) {
|
||||
return request({
|
||||
url: `/business/personnel/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addMerchantPersonnel(data) {
|
||||
return request({
|
||||
url: '/business/personnel',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function removeMerchantPersonnel(id) {
|
||||
return request({
|
||||
url: `/business/personnel/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
// 成员管理相关API
|
||||
export function getDepartmentTree() {
|
||||
return request({
|
||||
url: '/business/member/dept/tree',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getDepartmentMemberTree() {
|
||||
return request({
|
||||
url: '/business/member/tree',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getManagerList() {
|
||||
return request({
|
||||
url: '/business/member/managers',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getMemberList(params) {
|
||||
return request({
|
||||
url: '/business/member/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getMembersByDeptId(deptId) {
|
||||
return request({
|
||||
url: `/business/member/dept/${deptId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getMemberDetail(memberId) {
|
||||
return request({
|
||||
url: `/business/member/${memberId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取项目列表
|
||||
export function getProjectList() {
|
||||
return request({
|
||||
url: '/project/list',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取线索数量
|
||||
export function getClueCount(personnelId) {
|
||||
return request({
|
||||
url: `/merchant/clue/count/${personnelId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取意向客户数量
|
||||
export function getIntentCustomerCount(personnelId) {
|
||||
return request({
|
||||
url: `/merchant/intent-customer/count/${personnelId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 获取合同数量
|
||||
export function getContractCount(personnelId) {
|
||||
return request({
|
||||
url: `/merchant/contract/count/${personnelId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 标签分组相关API
|
||||
export function getTagGroupList(params) {
|
||||
return request({
|
||||
url: '/business/tag-group/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getAllTagGroups() {
|
||||
return request({
|
||||
url: '/business/tag-group/all',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getTagGroupDetail(id) {
|
||||
return request({
|
||||
url: `/business/tag-group/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addTagGroup(data) {
|
||||
return request({
|
||||
url: '/business/tag-group',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTagGroup(data) {
|
||||
return request({
|
||||
url: '/business/tag-group',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTagGroup(id) {
|
||||
return request({
|
||||
url: `/business/tag-group/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function checkTagGroupName(params) {
|
||||
return request({
|
||||
url: '/business/tag-group/check-name',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
// 标签相关API
|
||||
export function getTagList(params) {
|
||||
return request({
|
||||
url: '/business/tag/list',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
||||
|
||||
export function getTagListByGroupId(groupId) {
|
||||
return request({
|
||||
url: `/business/tag/group/${groupId}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function getTagDetail(id) {
|
||||
return request({
|
||||
url: `/business/tag/${id}`,
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
export function addTag(data) {
|
||||
return request({
|
||||
url: '/business/tag',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function updateTag(data) {
|
||||
return request({
|
||||
url: '/business/tag',
|
||||
method: 'put',
|
||||
data
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteTag(id) {
|
||||
return request({
|
||||
url: `/business/tag/${id}`,
|
||||
method: 'delete'
|
||||
})
|
||||
}
|
||||
|
||||
export function checkTagName(params) {
|
||||
return request({
|
||||
url: '/business/tag/check-name',
|
||||
method: 'get',
|
||||
params
|
||||
})
|
||||
}
|
655
pc/src/components/MemberSelector/index.vue
Normal file
655
pc/src/components/MemberSelector/index.vue
Normal file
@ -0,0 +1,655 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
title="请选择成员"
|
||||
:visible.sync="visible"
|
||||
width="550px"
|
||||
class="member-dialog"
|
||||
append-to-body
|
||||
@close="handleClose"
|
||||
>
|
||||
<div class="member-dialog-content">
|
||||
<!-- 左侧部分 -->
|
||||
<div class="member-list-area">
|
||||
<div class="member-search-header">
|
||||
<el-select v-model="memberType" placeholder="成员" class="member-type-select">
|
||||
<el-option label="成员" value="member"></el-option>
|
||||
</el-select>
|
||||
<el-input
|
||||
v-model="memberSearchKeyword"
|
||||
placeholder="请输入姓名"
|
||||
class="search-input"
|
||||
prefix-icon="el-icon-search"
|
||||
clearable
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="member-list">
|
||||
<div class="all-select">
|
||||
<el-checkbox
|
||||
v-model="selectAll"
|
||||
:disabled="selectionMode === 'single'"
|
||||
@change="handleSelectAllChange"
|
||||
>全选</el-checkbox>
|
||||
</div>
|
||||
|
||||
<div v-for="department in departmentList" :key="department.id" class="department-item">
|
||||
<div class="department-name" @click="toggleDepartment(department)">
|
||||
<i class="el-icon-user-solid department-icon"></i>
|
||||
{{ department.name }}
|
||||
<i :class="['el-icon-arrow-right department-expand-icon', {'is-expanded': department.expanded}]"></i>
|
||||
</div>
|
||||
|
||||
<div v-show="department.expanded">
|
||||
<!-- 部门直属成员 -->
|
||||
<template v-if="selectionMode === 'single'">
|
||||
<div v-for="member in filteredMembers(department.id)" :key="member.id" class="member-item">
|
||||
<el-radio
|
||||
v-model="tempSelectedMemberId"
|
||||
:label="member.id"
|
||||
@change="handleMemberSelected(member, department)"
|
||||
>{{ member.name }}</el-radio>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-for="member in filteredMembers(department.id)" :key="member.id" class="member-item">
|
||||
<el-checkbox
|
||||
v-model="member.checked"
|
||||
@change="(val) => handleMemberCheckChange(val, member, department)"
|
||||
>{{ member.name }}</el-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 子部门 -->
|
||||
<div v-if="department.children && department.children.length">
|
||||
<div v-for="subDept in department.children" :key="subDept.id" class="department-item sub-dept">
|
||||
<div class="department-name" @click.stop="toggleDepartment(subDept)">
|
||||
<i class="el-icon-office-building department-icon"></i>
|
||||
{{ subDept.name }}
|
||||
<i :class="['el-icon-arrow-right department-expand-icon', {'is-expanded': subDept.expanded}]"></i>
|
||||
</div>
|
||||
|
||||
<div v-show="subDept.expanded">
|
||||
<template v-if="selectionMode === 'single'">
|
||||
<div v-for="member in filteredMembers(subDept.id)" :key="member.id" class="member-item">
|
||||
<el-radio
|
||||
v-model="tempSelectedMemberId"
|
||||
:label="member.id"
|
||||
@change="handleMemberSelected(member, subDept)"
|
||||
>{{ member.name }}</el-radio>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-for="member in filteredMembers(subDept.id)" :key="member.id" class="member-item">
|
||||
<el-checkbox
|
||||
v-model="member.checked"
|
||||
@change="(val) => handleMemberCheckChange(val, member, subDept)"
|
||||
>{{ member.name }}</el-checkbox>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧已选择部分 -->
|
||||
<div class="selected-area">
|
||||
<div class="selected-header">
|
||||
已选择:<span class="clear-btn" @click="clearSelectedMembers">清空</span>
|
||||
</div>
|
||||
<div class="selected-content">
|
||||
<template v-if="selectionMode === 'single'">
|
||||
<div v-if="tempSelectedMember" class="selected-item">
|
||||
<div class="member-avatar">{{ tempSelectedMember.name.slice(-1) }}</div>
|
||||
<span class="member-name">{{ tempSelectedMember.name }}</span>
|
||||
<i class="el-icon-close" @click="clearSelectedMembers"></i>
|
||||
</div>
|
||||
<div v-else class="no-selected">未选择</div>
|
||||
</template>
|
||||
<template v-else>
|
||||
<div v-if="tempSelectedMembers.length > 0">
|
||||
<div v-for="member in tempSelectedMembers" :key="member.id" class="selected-item">
|
||||
<div class="member-avatar">{{ member.name.slice(-1) }}</div>
|
||||
<span class="member-name">{{ member.name }}</span>
|
||||
<i class="el-icon-close" @click="removeTempSelectedMember(member)"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="no-selected">未选择</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="handleCancel">取 消</el-button>
|
||||
<el-button type="primary" @click="handleConfirm">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'MemberSelector',
|
||||
props: {
|
||||
visible: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 单选模式或多选模式
|
||||
selectionMode: {
|
||||
type: String,
|
||||
default: 'single', // 'single' 或 'multiple'
|
||||
validator: (value) => ['single', 'multiple'].includes(value)
|
||||
},
|
||||
// 预选成员(单选模式下为对象,多选模式下为数组)
|
||||
selectedMember: {
|
||||
type: Object,
|
||||
default: () => null
|
||||
},
|
||||
selectedMembers: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 部门数据
|
||||
departments: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
},
|
||||
// 成员数据
|
||||
members: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
memberType: 'member',
|
||||
memberSearchKeyword: '',
|
||||
selectAll: false,
|
||||
|
||||
// 临时选中数据
|
||||
tempSelectedMemberId: null,
|
||||
tempSelectedMember: null,
|
||||
tempSelectedMembers: [],
|
||||
|
||||
// 内部使用的部门和成员数据(复制一份props数据)
|
||||
departmentList: [],
|
||||
allMembers: []
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 计算是否应该显示为全选状态
|
||||
isAllSelected() {
|
||||
if (this.allMembers.length === 0) return false
|
||||
return this.tempSelectedMembers.length === this.allMembers.length
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
visible(val) {
|
||||
if (val) {
|
||||
this.initData()
|
||||
}
|
||||
},
|
||||
selectedMember: {
|
||||
handler(val) {
|
||||
if (this.selectionMode === 'single' && val) {
|
||||
this.tempSelectedMember = { ...val }
|
||||
this.tempSelectedMemberId = val.id
|
||||
|
||||
// 当selectedMember变化时,确保radio选中状态正确
|
||||
this.$nextTick(() => {
|
||||
if (val.id) {
|
||||
// 直接设置单选框选中值
|
||||
this.tempSelectedMemberId = val.id;
|
||||
|
||||
// 打印调试信息
|
||||
console.log('selectedMember updated:', val.name, 'ID:', val.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
immediate: true,
|
||||
deep: true
|
||||
},
|
||||
selectedMembers: {
|
||||
handler(val) {
|
||||
if (this.selectionMode === 'multiple' && val.length > 0) {
|
||||
this.tempSelectedMembers = [...val]
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
// 监听选中状态变化,更新全选状态
|
||||
tempSelectedMembers: {
|
||||
handler(val) {
|
||||
if (this.selectionMode === 'multiple') {
|
||||
this.selectAll = this.isAllSelected
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
departments: {
|
||||
handler(val) {
|
||||
if (val.length > 0) {
|
||||
this.departmentList = JSON.parse(JSON.stringify(val))
|
||||
// 默认展开所有顶级部门
|
||||
this.departmentList.forEach(dept => {
|
||||
this.$set(dept, 'expanded', true)
|
||||
if (dept.children) {
|
||||
dept.children.forEach(child => {
|
||||
this.$set(child, 'expanded', false)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
},
|
||||
members: {
|
||||
handler(val) {
|
||||
if (val.length > 0) {
|
||||
this.allMembers = JSON.parse(JSON.stringify(val))
|
||||
// 为成员添加checked属性
|
||||
this.resetMembersCheckStatus()
|
||||
}
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 初始化数据
|
||||
initData() {
|
||||
// 重置搜索关键词
|
||||
this.memberSearchKeyword = ''
|
||||
console.log('MemberSelector initData called, selectedMember:', this.selectedMember)
|
||||
|
||||
if (this.selectionMode === 'single') {
|
||||
// 单选模式
|
||||
if (this.selectedMember) {
|
||||
this.tempSelectedMember = { ...this.selectedMember }
|
||||
this.tempSelectedMemberId = this.selectedMember.id
|
||||
console.log('单选模式:设置tempSelectedMemberId:', this.tempSelectedMemberId)
|
||||
|
||||
// 将选中成员添加到临时选择列表,用于右侧显示
|
||||
this.tempSelectedMembers = [{ ...this.selectedMember }]
|
||||
|
||||
// 立即设置单选模式下的radio选中状态
|
||||
this.$nextTick(() => {
|
||||
|
||||
// 确保所有成员数据已经加载
|
||||
if (this.tempSelectedMemberId && this.allMembers.length > 0) {
|
||||
// 找到对应成员并设置选中状态
|
||||
const member = this.allMembers.find(m => m.id == this.tempSelectedMemberId);
|
||||
if (member) {
|
||||
member.checked = true;
|
||||
this.$set(member, 'checked', true);
|
||||
console.log('单选模式已选择成员:', member.name, 'ID:', this.tempSelectedMemberId, 'checked:', member.checked);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.tempSelectedMember = null
|
||||
this.tempSelectedMemberId = null
|
||||
this.tempSelectedMembers = []
|
||||
}
|
||||
this.selectAll = false
|
||||
} else {
|
||||
// 多选模式
|
||||
this.tempSelectedMember = null
|
||||
this.tempSelectedMemberId = null
|
||||
this.tempSelectedMembers = [...this.selectedMembers]
|
||||
}
|
||||
|
||||
// 设置展开状态
|
||||
this.departmentList.forEach(dept => {
|
||||
dept.expanded = true
|
||||
if (dept.children) {
|
||||
dept.children.forEach(child => {
|
||||
child.expanded = false
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
// 重置选中状态
|
||||
this.resetMembersCheckStatus()
|
||||
|
||||
// 如果有已选成员,设置选中状态
|
||||
if (this.selectionMode === 'multiple' && this.tempSelectedMembers.length > 0) {
|
||||
this.setMembersCheckedByIds(this.tempSelectedMembers.map(m => m.id))
|
||||
|
||||
// 检查是否全选
|
||||
this.selectAll = this.tempSelectedMembers.length === this.allMembers.length
|
||||
} else if (this.selectionMode === 'single' && this.tempSelectedMemberId) {
|
||||
// 单选模式下,根据ID设置选中状态
|
||||
const ids = [this.tempSelectedMemberId];
|
||||
this.setMembersCheckedByIds(ids);
|
||||
console.log('单选模式:设置选中状态, ids:', ids)
|
||||
}
|
||||
},
|
||||
|
||||
// 重置所有成员的选中状态
|
||||
resetMembersCheckStatus() {
|
||||
this.allMembers.forEach(member => {
|
||||
member.checked = false
|
||||
})
|
||||
},
|
||||
|
||||
// 根据ID设置成员的选中状态
|
||||
setMembersCheckedByIds(ids) {
|
||||
this.allMembers.forEach(member => {
|
||||
member.checked = ids.includes(member.id)
|
||||
})
|
||||
},
|
||||
|
||||
// 根据部门ID筛选成员
|
||||
filteredMembers(deptId) {
|
||||
if (this.memberSearchKeyword) {
|
||||
return this.allMembers.filter(member =>
|
||||
member.deptId === deptId &&
|
||||
member.name.includes(this.memberSearchKeyword)
|
||||
)
|
||||
}
|
||||
return this.allMembers.filter(member => member.deptId === deptId)
|
||||
},
|
||||
|
||||
// 展开/收起部门
|
||||
toggleDepartment(department) {
|
||||
department.expanded = !department.expanded
|
||||
},
|
||||
|
||||
// 单选模式:选择成员
|
||||
handleMemberSelected(member, department) {
|
||||
console.log('单选成员:', member.name, 'ID:', member.id);
|
||||
|
||||
// 清除其他成员的选中状态
|
||||
this.allMembers.forEach(m => {
|
||||
m.checked = m.id === member.id;
|
||||
});
|
||||
|
||||
// 更新临时选中成员
|
||||
this.tempSelectedMember = {
|
||||
id: member.id,
|
||||
name: member.name,
|
||||
phone: member.phone,
|
||||
department: department.name
|
||||
}
|
||||
|
||||
// 更新右侧已选择列表
|
||||
this.tempSelectedMembers = [this.tempSelectedMember];
|
||||
},
|
||||
|
||||
// 多选模式:处理复选框变化
|
||||
handleMemberCheckChange(checked, member, department) {
|
||||
if (checked) {
|
||||
// 添加到已选列表
|
||||
const exists = this.tempSelectedMembers.some(m => m.id === member.id)
|
||||
if (!exists) {
|
||||
this.tempSelectedMembers.push({
|
||||
id: member.id,
|
||||
name: member.name,
|
||||
phone: member.phone,
|
||||
department: department.name
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// 从已选列表移除
|
||||
const index = this.tempSelectedMembers.findIndex(m => m.id === member.id)
|
||||
if (index !== -1) {
|
||||
this.tempSelectedMembers.splice(index, 1)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 从已选列表中移除成员
|
||||
removeTempSelectedMember(member) {
|
||||
// 从已选列表移除
|
||||
const index = this.tempSelectedMembers.findIndex(m => m.id === member.id)
|
||||
if (index !== -1) {
|
||||
this.tempSelectedMembers.splice(index, 1)
|
||||
}
|
||||
|
||||
// 更新复选框状态
|
||||
const targetMember = this.allMembers.find(m => m.id === member.id)
|
||||
if (targetMember) {
|
||||
targetMember.checked = false
|
||||
}
|
||||
},
|
||||
|
||||
// 清空选择的成员
|
||||
clearSelectedMembers() {
|
||||
this.tempSelectedMembers = []
|
||||
this.tempSelectedMember = null
|
||||
this.tempSelectedMemberId = null
|
||||
|
||||
// 重置所有复选框
|
||||
this.resetMembersCheckStatus()
|
||||
},
|
||||
|
||||
// 取消选择
|
||||
handleCancel() {
|
||||
this.$emit('update:visible', false)
|
||||
this.$emit('cancel')
|
||||
},
|
||||
|
||||
// 关闭弹窗
|
||||
handleClose() {
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
// 确认选择
|
||||
handleConfirm() {
|
||||
if (this.selectionMode === 'single') {
|
||||
// 单选模式
|
||||
if (!this.tempSelectedMember) {
|
||||
this.$message.warning('请选择成员')
|
||||
return
|
||||
}
|
||||
this.$emit('confirm', this.tempSelectedMember)
|
||||
} else {
|
||||
// 多选模式
|
||||
if (this.tempSelectedMembers.length === 0) {
|
||||
this.$message.warning('请选择成员')
|
||||
return
|
||||
}
|
||||
this.$emit('confirm', this.tempSelectedMembers)
|
||||
}
|
||||
|
||||
this.$emit('update:visible', false)
|
||||
},
|
||||
|
||||
// 处理全选变化
|
||||
handleSelectAllChange(checked) {
|
||||
if (this.selectionMode !== 'multiple') return
|
||||
|
||||
// 更新所有复选框状态
|
||||
this.allMembers.forEach(member => {
|
||||
member.checked = checked
|
||||
})
|
||||
|
||||
if (checked) {
|
||||
// 全选: 将所有成员添加到已选列表
|
||||
this.tempSelectedMembers = this.allMembers.map(member => {
|
||||
// 获取成员所在部门的名称
|
||||
let departmentName = ''
|
||||
for (const dept of this.departmentList) {
|
||||
if (dept.id === member.deptId) {
|
||||
departmentName = dept.name
|
||||
break
|
||||
}
|
||||
|
||||
if (dept.children) {
|
||||
for (const subDept of dept.children) {
|
||||
if (subDept.id === member.deptId) {
|
||||
departmentName = subDept.name
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
id: member.id,
|
||||
name: member.name,
|
||||
phone: member.phone || '',
|
||||
department: departmentName
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 取消全选: 清空已选列表
|
||||
this.tempSelectedMembers = []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.member-dialog {
|
||||
.member-dialog-content {
|
||||
display: flex;
|
||||
height: 350px;
|
||||
|
||||
.member-list-area {
|
||||
width: 65%;
|
||||
border-right: 1px solid #EBEEF5;
|
||||
padding-right: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.member-search-header {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.member-type-select {
|
||||
width: 90px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.department-nav {
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.nav-item {
|
||||
display: inline-block;
|
||||
padding: 8px 0;
|
||||
cursor: pointer;
|
||||
color: #409EFF;
|
||||
}
|
||||
}
|
||||
|
||||
.member-list {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
.all-select {
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.department-item {
|
||||
margin-bottom: 5px;
|
||||
|
||||
&.sub-dept {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.department-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
padding: 5px 0;
|
||||
|
||||
.department-icon {
|
||||
margin-right: 5px;
|
||||
color: #5c6b77;
|
||||
}
|
||||
|
||||
.department-expand-icon {
|
||||
margin-left: auto;
|
||||
transition: transform 0.3s;
|
||||
|
||||
&.is-expanded {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.member-item {
|
||||
padding: 3px 0 3px 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.selected-area {
|
||||
width: 35%;
|
||||
padding-left: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.selected-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 10px;
|
||||
color: #606266;
|
||||
|
||||
.clear-btn {
|
||||
color: #409EFF;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.selected-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
|
||||
.selected-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 10px;
|
||||
|
||||
.member-avatar {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background-color: #409EFF;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-right: 10px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
flex: 1;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.el-icon-close {
|
||||
color: #C0C4CC;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
color: #F56C6C;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.no-selected {
|
||||
color: #909399;
|
||||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
@ -4,6 +4,7 @@ import Layout from '@/layout/index'
|
||||
import systemRoutes from './modules/system'
|
||||
import projectRoutes from './modules/project'
|
||||
import financeRoutes from './modules/finance'
|
||||
import merchantRoutes from './modules/merchant'
|
||||
|
||||
Vue.use(VueRouter)
|
||||
|
||||
@ -30,6 +31,7 @@ const routes = [
|
||||
systemRoutes,
|
||||
projectRoutes,
|
||||
financeRoutes,
|
||||
merchantRoutes,
|
||||
{
|
||||
path: '/asset',
|
||||
component: Layout,
|
||||
|
@ -12,6 +12,12 @@ export default {
|
||||
component: () => import('@/views/finance/feeType/index.vue'),
|
||||
name: 'FeeType',
|
||||
meta: { title: '费用类型', icon: 'el-icon-tickets' }
|
||||
},
|
||||
{
|
||||
path: 'receiptSetting',
|
||||
component: () => import('@/views/finance/receiptSetting/index.vue'),
|
||||
name: 'ReceiptSetting',
|
||||
meta: { title: '收据设置', icon: 'el-icon-document' }
|
||||
}
|
||||
]
|
||||
}
|
33
pc/src/router/modules/merchant.js
Normal file
33
pc/src/router/modules/merchant.js
Normal file
@ -0,0 +1,33 @@
|
||||
export default {
|
||||
path: '/merchant',
|
||||
component: () => import('@/layout/index'),
|
||||
redirect: '/merchant/merchant-personnel',
|
||||
name: 'Merchant',
|
||||
meta: { title: '招商管理', icon: 'el-icon-user-solid' },
|
||||
children: [
|
||||
{
|
||||
path: 'merchant-personnel',
|
||||
component: () => import('@/views/merchant/merchant-personnel/index.vue'),
|
||||
name: 'MerchantPersonnel',
|
||||
meta: { title: '招商人员', icon: 'el-icon-user' }
|
||||
},
|
||||
{
|
||||
path: 'tag-management',
|
||||
component: () => import('@/views/merchant/tag-management/index.vue'),
|
||||
name: 'TagManagement',
|
||||
meta: { title: '标签管理', icon: 'el-icon-collection-tag' }
|
||||
},
|
||||
{
|
||||
path: 'clue-management',
|
||||
component: () => import('@/views/merchant/clue-management/index.vue'),
|
||||
name: 'ClueManagement',
|
||||
meta: { title: '线索管理', icon: 'el-icon-chat-line-square' }
|
||||
},
|
||||
{
|
||||
path: 'intent-customer',
|
||||
component: () => import('@/views/merchant/intent-customer/index.vue'),
|
||||
name: 'IntentCustomer',
|
||||
meta: { title: '意向客户', icon: 'el-icon-s-custom' }
|
||||
}
|
||||
]
|
||||
}
|
5
pc/src/utils/constants.js
Normal file
5
pc/src/utils/constants.js
Normal file
@ -0,0 +1,5 @@
|
||||
// API响应状态码
|
||||
export const API_SUCCESS_CODE = '0000000000000000'
|
||||
|
||||
|
||||
// 其他常量可以在此添加
|
@ -1,9 +1,10 @@
|
||||
import axios from 'axios'
|
||||
import { Message } from 'element-ui'
|
||||
import { API_SUCCESS_CODE } from './constants'
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
// baseURL: '/api', // 修改为相对路径,使用代理
|
||||
// baseURL: '/', // 修改为相对路径,使用代理
|
||||
baseURL: process.env.VUE_APP_BASE_API, // 使用环境变量中的接口地址
|
||||
|
||||
timeout: 10000 // 请求超时时间
|
||||
@ -30,8 +31,8 @@ service.interceptors.response.use(
|
||||
}
|
||||
|
||||
const res = response.data
|
||||
// 如果返回的状态码不是000000,则判断为错误
|
||||
if (res.code !== '000000') {
|
||||
// 如果返回的状态码不是成功码,则判断为错误
|
||||
if (res.code !== API_SUCCESS_CODE) {
|
||||
Message({
|
||||
message: res.msg || res.message || '系统错误',
|
||||
type: 'error',
|
||||
|
@ -162,6 +162,7 @@ import {
|
||||
updateFeeType,
|
||||
deleteFeeType
|
||||
} from '@/api/finance'
|
||||
import { API_SUCCESS_CODE } from '@/utils/constants'
|
||||
|
||||
export default {
|
||||
name: 'FeeTypeManagement',
|
||||
@ -238,7 +239,7 @@ export default {
|
||||
// 获取分类列表
|
||||
getCategoryList() {
|
||||
getAllFeeCategories().then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
this.categoryList = response.data
|
||||
if (this.categoryList.length > 0 && !this.selectedCategoryId) {
|
||||
this.handleCategorySelect(this.categoryList[0].id.toString())
|
||||
@ -253,7 +254,7 @@ export default {
|
||||
if (this.categoryQuery) {
|
||||
const params = { categoryName: this.categoryQuery, pageNum: 1, pageSize: 100 }
|
||||
getFeeCategories(params).then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
this.categoryList = response.data.list
|
||||
} else {
|
||||
this.$message.error(response.message || '查询失败')
|
||||
@ -275,7 +276,7 @@ export default {
|
||||
getTypeList() {
|
||||
this.loading = true
|
||||
getFeeTypes(this.queryParams).then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
this.typeList = response.data.list
|
||||
this.total = response.data.total
|
||||
} else {
|
||||
@ -329,7 +330,7 @@ export default {
|
||||
// 编辑分类操作
|
||||
handleEditCategory(row) {
|
||||
getFeeCategory(row.id).then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
const categoryData = response.data
|
||||
// 确保保证金类型字段是字符串
|
||||
if (categoryData.feeTpNo !== undefined) {
|
||||
@ -353,7 +354,7 @@ export default {
|
||||
if (valid) {
|
||||
if (this.categoryForm.id) {
|
||||
updateFeeCategory(this.categoryForm.id, this.categoryForm).then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('修改成功')
|
||||
this.categoryOpen = false
|
||||
this.getCategoryList()
|
||||
@ -363,7 +364,7 @@ export default {
|
||||
})
|
||||
} else {
|
||||
addFeeCategory(this.categoryForm).then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('新增成功')
|
||||
this.categoryOpen = false
|
||||
this.getCategoryList()
|
||||
@ -383,7 +384,7 @@ export default {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteFeeCategory(row.id).then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('删除成功')
|
||||
this.getCategoryList()
|
||||
// 如果删除的是当前选中的分类,重置选中状态
|
||||
@ -423,7 +424,7 @@ export default {
|
||||
// 修改费用类型按钮操作
|
||||
handleEditType(row) {
|
||||
getFeeType(row.id).then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
// 先设置数据
|
||||
const typeData = response.data
|
||||
// 确保所有字段都是字符串类型
|
||||
@ -461,7 +462,7 @@ export default {
|
||||
|
||||
if (this.typeForm.id) {
|
||||
updateFeeType(this.typeForm.id, submitData).then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('修改成功')
|
||||
this.typeOpen = false
|
||||
this.getTypeList()
|
||||
@ -471,7 +472,7 @@ export default {
|
||||
})
|
||||
} else {
|
||||
addFeeType(submitData).then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('新增成功')
|
||||
this.typeOpen = false
|
||||
this.getTypeList()
|
||||
@ -491,7 +492,7 @@ export default {
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteFeeType(row.id).then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('删除成功')
|
||||
this.getTypeList()
|
||||
} else {
|
||||
@ -519,7 +520,7 @@ export default {
|
||||
// 加载完整的分类列表
|
||||
loadFullCategoryList() {
|
||||
getAllFeeCategories().then(response => {
|
||||
if (response.code === '000000') {
|
||||
if (response.code === API_SUCCESS_CODE) {
|
||||
this.categoryList = response.data
|
||||
}
|
||||
})
|
||||
|
582
pc/src/views/finance/receiptSetting/PayeeInfo.vue
Normal file
582
pc/src/views/finance/receiptSetting/PayeeInfo.vue
Normal file
@ -0,0 +1,582 @@
|
||||
<template>
|
||||
<div class="payee-info-container">
|
||||
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="filter-container">
|
||||
<el-input v-model="queryParams.payeeNameUnitName" placeholder="请输入收款方单位名称" clearable style="width: 250px;" class="filter-item" />
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
<el-button type="primary" icon="el-icon-plus" style="float: right;" @click="handleAdd">新增收款方信息</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 收款方信息列表 -->
|
||||
<el-table v-loading="loading" :data="payeeList" border style="margin-top: 15px;">
|
||||
<el-table-column prop="companyId" label="关联公司">
|
||||
<template slot-scope="scope">
|
||||
{{ formatCompanyName(scope.row.companyId) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="payeeNameUnitName" label="收款方单位名称" />
|
||||
<el-table-column prop="payeeName" label="收款人" />
|
||||
<el-table-column prop="addr" label="地址" />
|
||||
<el-table-column prop="contTel" label="电话" />
|
||||
<el-table-column prop="buildingIds" label="应用楼宇">
|
||||
<template slot-scope="scope">
|
||||
{{ formatBuildingNames(scope.row.buildingIds) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="handleEdit(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="small" @click="handleDelete(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
|
||||
:current-page="queryParams.current" :page-sizes="[10, 20, 30, 50]" :page-size="queryParams.size"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="total" />
|
||||
</div>
|
||||
|
||||
<!-- 编辑收款方信息对话框 -->
|
||||
<el-dialog :title="isEdit ? '编辑收款方信息' : '新增收款方信息'" :visible.sync="dialogVisible" width="550px" append-to-body>
|
||||
<el-form :model="form" :rules="rules" ref="payeeFormRef" label-width="150px">
|
||||
<el-form-item label="关联公司" prop="companyId">
|
||||
<el-select v-model="form.companyId" placeholder="请选择关联公司" style="width: 100%;">
|
||||
<el-option
|
||||
v-for="item in companyOptions"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="收款方单位名称" prop="payeeNameUnitName">
|
||||
<el-input v-model="form.payeeNameUnitName" placeholder="请输入收款方单位名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="收款人" prop="payeeName">
|
||||
<el-input v-model="form.payeeName" placeholder="请输入收款人" />
|
||||
</el-form-item>
|
||||
<el-form-item label="地址" prop="addr">
|
||||
<el-input v-model="form.addr" placeholder="请输入地址" />
|
||||
</el-form-item>
|
||||
<el-form-item label="电话" prop="contTel">
|
||||
<el-input v-model="form.contTel" placeholder="请输入电话" />
|
||||
</el-form-item>
|
||||
<el-form-item label="应用楼宇" prop="buildingIds">
|
||||
<el-popover
|
||||
placement="bottom-start"
|
||||
width="400"
|
||||
trigger="click"
|
||||
v-model="buildingPopoverVisible">
|
||||
<el-tree
|
||||
ref="buildingTree"
|
||||
:data="buildingTreeData"
|
||||
node-key="id"
|
||||
:props="buildingProps"
|
||||
show-checkbox
|
||||
:default-expanded-keys="['parent_node']"
|
||||
@check="handleBuildingCheck">
|
||||
<span slot-scope="{ node, data }" class="custom-tree-node">
|
||||
<i v-if="data.icon" :class="data.icon"></i>
|
||||
<span>{{ node.label }}</span>
|
||||
</span>
|
||||
</el-tree>
|
||||
<el-input
|
||||
slot="reference"
|
||||
v-model="form.buildingNames"
|
||||
placeholder="请选择应用楼宇"
|
||||
readonly
|
||||
suffix-icon="el-icon-arrow-down">
|
||||
</el-input>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitForm">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<el-dialog title="提示" :visible.sync="deleteDialogVisible" width="360px" append-to-body>
|
||||
<div class="delete-confirm">
|
||||
<i class="el-icon-warning"></i>
|
||||
<span>确定要删除该收款方信息吗?</span>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="deleteDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="confirmDelete">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getPayeeInfo, updatePayeeInfo, addPayeeInfo, deletePayeeInfo } from '@/api/finance'
|
||||
import { getBuildingList } from '@/api/building'
|
||||
import { API_SUCCESS_CODE } from '@/utils/constants'
|
||||
|
||||
export default {
|
||||
name: 'PayeeInfo',
|
||||
data() {
|
||||
return {
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
payeeNameUnitName: ''
|
||||
},
|
||||
|
||||
// 加载状态
|
||||
loading: false,
|
||||
|
||||
// 收款方信息列表
|
||||
payeeList: [],
|
||||
total: 0,
|
||||
|
||||
// 收款方信息
|
||||
payeeInfo: {
|
||||
companyId: '',
|
||||
payeeNameUnitName: '',
|
||||
payeeName: '',
|
||||
addr: '',
|
||||
contTel: '',
|
||||
buildingIds: []
|
||||
},
|
||||
|
||||
// 楼宇选项
|
||||
buildingOptions: [],
|
||||
|
||||
// 公司选项
|
||||
companyOptions: [],
|
||||
|
||||
// 楼宇树形数据
|
||||
buildingTreeData: [],
|
||||
buildingPopoverVisible: false,
|
||||
buildingProps: {
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
},
|
||||
|
||||
// 对话框显示状态
|
||||
dialogVisible: false,
|
||||
deleteDialogVisible: false,
|
||||
|
||||
// 是否为编辑模式
|
||||
isEdit: false,
|
||||
|
||||
// 当前操作的ID
|
||||
currentId: null,
|
||||
|
||||
// 表单数据
|
||||
form: {
|
||||
companyId: '',
|
||||
payeeNameUnitName: '',
|
||||
payeeName: '',
|
||||
addr: '',
|
||||
contTel: '',
|
||||
buildingIds: [],
|
||||
buildingNames: ''
|
||||
},
|
||||
|
||||
// 表单校验规则
|
||||
rules: {
|
||||
companyId: [
|
||||
{ required: true, message: '请选择关联公司', trigger: 'change' }
|
||||
],
|
||||
payeeNameUnitName: [
|
||||
{ required: true, message: '请输入收款方单位名称', trigger: 'blur' }
|
||||
],
|
||||
payeeName: [
|
||||
{ required: true, message: '请输入收款人', trigger: 'blur' }
|
||||
],
|
||||
contTel: [
|
||||
{ pattern: /^(\d{3,4}-\d{7,8}(-\d{1,4})?|1[3-9]\d{9})$/, message: '电话格式不正确', trigger: 'blur' }
|
||||
],
|
||||
buildingIds: [
|
||||
{ required: true, message: '请选择应用楼宇', trigger: 'change' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getPayeeList()
|
||||
this.getBuildingOptions()
|
||||
this.getCompanyOptions()
|
||||
},
|
||||
methods: {
|
||||
// 获取收款方信息列表
|
||||
getPayeeList() {
|
||||
this.loading = true
|
||||
getPayeeInfo(this.queryParams).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.payeeList = res.data.data || []
|
||||
this.total = res.data.total || 0
|
||||
} else {
|
||||
this.$message.error(res.message || '获取收款方信息列表失败')
|
||||
|
||||
// 模拟数据
|
||||
this.payeeList = [{
|
||||
id: 1,
|
||||
companyId: 'COM001',
|
||||
payeeNameUnitName: '智慧园区管理有限公司',
|
||||
payeeName: '张三',
|
||||
addr: '北京市海淀区中关村大街1号',
|
||||
contTel: '13800138000',
|
||||
buildingIds: 'BLD001,BLD002'
|
||||
}]
|
||||
this.total = 1
|
||||
}
|
||||
this.loading = false
|
||||
}).catch(error => {
|
||||
console.error('获取收款方信息列表失败', error)
|
||||
this.$message.error('获取收款方信息列表失败')
|
||||
|
||||
// 模拟数据
|
||||
this.payeeList = [{
|
||||
id: 1,
|
||||
companyId: 'COM001',
|
||||
payeeNameUnitName: '智慧园区管理有限公司',
|
||||
payeeName: '张三',
|
||||
addr: '北京市海淀区中关村大街1号',
|
||||
contTel: '13800138000',
|
||||
buildingIds: 'BLD001,BLD002'
|
||||
}]
|
||||
this.total = 1
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 获取收款方详情
|
||||
getPayeeDetail(id) {
|
||||
getPayeeInfo(id).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.payeeInfo = res.data || {}
|
||||
} else {
|
||||
this.$message.error(res.message || '获取收款方信息详情失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取收款方信息详情失败', error)
|
||||
this.$message.error('获取收款方信息详情失败')
|
||||
})
|
||||
},
|
||||
|
||||
// 获取楼宇选项
|
||||
getBuildingOptions() {
|
||||
getBuildingList().then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.buildingOptions = res.data || []
|
||||
this.generateBuildingTree()
|
||||
} else {
|
||||
// 模拟数据
|
||||
this.buildingOptions = [
|
||||
{ id: 'BLD001', name: 'A栋办公楼' },
|
||||
{ id: 'BLD002', name: 'B栋办公楼' },
|
||||
{ id: 'BLD003', name: 'C栋研发中心' },
|
||||
{ id: 'BLD004', name: 'D栋会议中心' },
|
||||
{ id: 'BLD005', name: 'E栋商业配套' }
|
||||
]
|
||||
this.generateBuildingTree()
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取楼宇列表失败', error)
|
||||
// 模拟数据
|
||||
this.buildingOptions = [
|
||||
{ id: 'BLD001', name: 'A栋办公楼' },
|
||||
{ id: 'BLD002', name: 'B栋办公楼' },
|
||||
{ id: 'BLD003', name: 'C栋研发中心' },
|
||||
{ id: 'BLD004', name: 'D栋会议中心' },
|
||||
{ id: 'BLD005', name: 'E栋商业配套' }
|
||||
]
|
||||
this.generateBuildingTree()
|
||||
})
|
||||
},
|
||||
|
||||
// 生成楼宇树形数据
|
||||
generateBuildingTree() {
|
||||
// 创建树形结构
|
||||
const parentNode = {
|
||||
id: 'parent_node',
|
||||
label: '小红马演示园区3',
|
||||
children: this.buildingOptions.map(item => ({
|
||||
id: item.id,
|
||||
label: item.name,
|
||||
icon: 'el-icon-office-building'
|
||||
}))
|
||||
}
|
||||
|
||||
this.buildingTreeData = [parentNode]
|
||||
},
|
||||
|
||||
// 处理楼宇选择
|
||||
handleBuildingCheck() {
|
||||
// 获取所有选中的叶子节点
|
||||
const checkedNodes = this.$refs.buildingTree.getCheckedNodes(false, false)
|
||||
const leafNodes = checkedNodes.filter(node => !node.children || node.children.length === 0)
|
||||
|
||||
// 过滤掉父节点,只保留叶子节点
|
||||
const selectedBuildings = leafNodes.filter(node => node.id !== 'parent_node')
|
||||
|
||||
// 更新选中楼宇ID和名称
|
||||
this.form.buildingIds = selectedBuildings.map(node => node.id)
|
||||
this.form.buildingNames = selectedBuildings.map(node => node.label).join(',')
|
||||
},
|
||||
|
||||
// 获取公司选项
|
||||
getCompanyOptions() {
|
||||
// 模拟公司数据
|
||||
this.companyOptions = [
|
||||
{ id: 'COM001', name: '智慧园区管理有限公司' },
|
||||
{ id: 'COM002', name: '科技创新服务有限公司' },
|
||||
{ id: 'COM003', name: '数字产业发展有限公司' },
|
||||
{ id: 'COM004', name: '绿色能源科技有限公司' },
|
||||
{ id: 'COM005', name: '城市更新建设有限公司' }
|
||||
]
|
||||
},
|
||||
|
||||
// 搜索
|
||||
handleSearch() {
|
||||
this.queryParams.current = 1
|
||||
this.getPayeeList()
|
||||
},
|
||||
|
||||
// 重置查询
|
||||
resetQuery() {
|
||||
this.queryParams = {
|
||||
current: 1,
|
||||
size: 10,
|
||||
payeeNameUnitName: ''
|
||||
}
|
||||
this.getPayeeList()
|
||||
},
|
||||
|
||||
// 处理分页大小变化
|
||||
handleSizeChange(val) {
|
||||
this.queryParams.size = val
|
||||
this.getPayeeList()
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleCurrentChange(val) {
|
||||
this.queryParams.current = val
|
||||
this.getPayeeList()
|
||||
},
|
||||
|
||||
// 打开新增对话框
|
||||
handleAdd() {
|
||||
this.isEdit = false
|
||||
this.currentId = null
|
||||
this.form = {
|
||||
companyId: '',
|
||||
payeeNameUnitName: '',
|
||||
payeeName: '',
|
||||
addr: '',
|
||||
contTel: '',
|
||||
buildingIds: [],
|
||||
buildingNames: ''
|
||||
}
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.payeeFormRef) {
|
||||
this.$refs.payeeFormRef.clearValidate()
|
||||
}
|
||||
// 重置树的选中状态
|
||||
if (this.$refs.buildingTree) {
|
||||
this.$refs.buildingTree.setCheckedKeys([])
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 打开编辑对话框
|
||||
handleEdit(row) {
|
||||
this.isEdit = true
|
||||
this.currentId = row.id
|
||||
|
||||
// 如果buildingIds是字符串,将其转换为数组
|
||||
let buildingIds = row.buildingIds
|
||||
if (typeof buildingIds === 'string') {
|
||||
buildingIds = buildingIds.split(',').filter(id => id)
|
||||
} else if (!Array.isArray(buildingIds)) {
|
||||
buildingIds = []
|
||||
}
|
||||
|
||||
// 查找楼宇名称
|
||||
let buildingNames = ''
|
||||
if (Array.isArray(buildingIds) && buildingIds.length > 0) {
|
||||
const selectedBuildingNames = []
|
||||
this.buildingTreeData.forEach(parent => {
|
||||
if (parent.children) {
|
||||
parent.children.forEach(building => {
|
||||
if (buildingIds.includes(building.id)) {
|
||||
selectedBuildingNames.push(building.label)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
buildingNames = selectedBuildingNames.join(',')
|
||||
}
|
||||
|
||||
this.form = {
|
||||
...row,
|
||||
buildingIds: buildingIds,
|
||||
buildingNames: buildingNames
|
||||
}
|
||||
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.payeeFormRef) {
|
||||
this.$refs.payeeFormRef.clearValidate()
|
||||
}
|
||||
// 设置树的默认选中状态
|
||||
if (this.$refs.buildingTree) {
|
||||
this.$refs.buildingTree.setCheckedKeys(buildingIds)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 提交表单
|
||||
submitForm() {
|
||||
this.$refs.payeeFormRef.validate(valid => {
|
||||
if (valid) {
|
||||
// 处理数据,将buildingIds数组转为逗号分隔的字符串
|
||||
const formData = { ...this.form }
|
||||
if (Array.isArray(formData.buildingIds)) {
|
||||
formData.buildingIds = formData.buildingIds.join(',')
|
||||
}
|
||||
|
||||
if (this.isEdit) {
|
||||
// 编辑模式
|
||||
updatePayeeInfo(this.currentId, formData).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('更新收款方信息成功')
|
||||
this.dialogVisible = false
|
||||
this.getPayeeList()
|
||||
} else {
|
||||
this.$message.error(res.message || '更新收款方信息失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('更新收款方信息失败', error)
|
||||
this.$message.error('更新收款方信息失败')
|
||||
})
|
||||
} else {
|
||||
// 新增模式
|
||||
addPayeeInfo(formData).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('新增收款方信息成功')
|
||||
this.dialogVisible = false
|
||||
this.getPayeeList()
|
||||
} else {
|
||||
this.$message.error(res.message || '新增收款方信息失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('新增收款方信息失败', error)
|
||||
this.$message.error('新增收款方信息失败')
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 删除
|
||||
handleDelete(row) {
|
||||
this.currentId = row.id
|
||||
this.deleteDialogVisible = true
|
||||
},
|
||||
|
||||
// 确认删除
|
||||
confirmDelete() {
|
||||
if (!this.currentId) return
|
||||
|
||||
deletePayeeInfo(this.currentId).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('删除成功')
|
||||
this.getPayeeList()
|
||||
} else {
|
||||
this.$message.error(res.message || '删除失败')
|
||||
}
|
||||
this.deleteDialogVisible = false
|
||||
}).catch(error => {
|
||||
console.error('删除失败', error)
|
||||
this.$message.error('删除失败')
|
||||
this.deleteDialogVisible = false
|
||||
})
|
||||
},
|
||||
|
||||
// 格式化公司名称
|
||||
formatCompanyName(companyId) {
|
||||
if (!companyId) return '-'
|
||||
|
||||
const company = this.companyOptions.find(item => item.id === companyId)
|
||||
return company ? company.name : companyId
|
||||
},
|
||||
|
||||
// 格式化楼宇名称
|
||||
formatBuildingNames(buildingIds) {
|
||||
if (!buildingIds) return '-'
|
||||
|
||||
// 如果buildingIds是字符串,将其转换为数组
|
||||
let buildingIdArray = buildingIds
|
||||
if (typeof buildingIds === 'string') {
|
||||
buildingIdArray = buildingIds.split(',').filter(id => id)
|
||||
}
|
||||
|
||||
if (!Array.isArray(buildingIdArray) || buildingIdArray.length === 0) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const buildingNames = []
|
||||
this.buildingTreeData.forEach(parent => {
|
||||
if (parent.children) {
|
||||
parent.children.forEach(building => {
|
||||
if (buildingIdArray.includes(building.id)) {
|
||||
buildingNames.push(building.label)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return buildingNames.length > 0 ? buildingNames.join(',') : '-'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.payee-info-container {
|
||||
.header-actions,
|
||||
.filter-container {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.info-card {
|
||||
margin-bottom: 20px;
|
||||
|
||||
.card-header {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.info-content {
|
||||
padding: 10px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.delete-confirm {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
font-size: 24px;
|
||||
color: #e6a23c;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
940
pc/src/views/finance/receiptSetting/ReceiptTemplate.vue
Normal file
940
pc/src/views/finance/receiptSetting/ReceiptTemplate.vue
Normal file
@ -0,0 +1,940 @@
|
||||
<template>
|
||||
<div class="receipt-template-container">
|
||||
|
||||
<!-- 搜索栏 -->
|
||||
<div class="filter-container">
|
||||
<el-input v-model="queryParams.templateName" placeholder="请输入模板名称" clearable style="width: 250px;" class="filter-item" />
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
|
||||
<el-button style="float: right;" icon="el-icon-plus" type="primary" @click="handleUploadTemplate">新增收据模版</el-button>
|
||||
<el-button style="float: right;" @click="handleDownloadExample">下载样例模板</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 模板列表 -->
|
||||
<el-table v-loading="loading" :data="templateList" border style="margin-top: 15px;">
|
||||
<el-table-column prop="id" label="ID" width="80" />
|
||||
<el-table-column prop="templateName" label="模板名称" />
|
||||
<el-table-column label="应用楼宇">
|
||||
<template slot-scope="scope">
|
||||
{{ formatBuildingNames(scope.row.buildingIds) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="250">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="handlePreviewTemplate(scope.row)">预览</el-button>
|
||||
<el-button type="text" size="small" @click="handleDownloadTemplate(scope.row)">下载</el-button>
|
||||
<el-button type="text" size="small" @click="handleEditTemplate(scope.row)">编辑</el-button>
|
||||
<el-button type="text" size="small" @click="handleDeleteTemplate(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
|
||||
:current-page="queryParams.current" :page-sizes="[10, 20, 30, 50]" :page-size="queryParams.size"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="total" />
|
||||
</div>
|
||||
|
||||
<!-- 上传/编辑模板对话框 -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" append-to-body>
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="复制关键字" name="keywords">
|
||||
<div class="keywords-container">
|
||||
<div class="keyword-section">
|
||||
<h4>收据信息</h4>
|
||||
<div class="keyword-list">
|
||||
<div v-for="(item, index) in keywords.receiptInfo" :key="'receipt-'+index" class="keyword-item" @click="copyKeyword(item)">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="keyword-section">
|
||||
<h4>交收款方</h4>
|
||||
<div class="keyword-list">
|
||||
<div v-for="(item, index) in keywords.payerInfo" :key="'payer-'+index" class="keyword-item" @click="copyKeyword(item)">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="keyword-section">
|
||||
<h4>房源信息</h4>
|
||||
<div class="keyword-list">
|
||||
<div v-for="(item, index) in keywords.propertyInfo" :key="'property-'+index" class="keyword-item" @click="copyKeyword(item)">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="keyword-section">
|
||||
<h4>账单信息</h4>
|
||||
<div class="keyword-list">
|
||||
<div v-for="(item, index) in keywords.billInfo" :key="'bill-'+index" class="keyword-item" @click="copyKeyword(item)">
|
||||
{{ item }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="上传收据打印模板" name="upload">
|
||||
<el-form :model="templateForm" :rules="rules" ref="templateFormRef" label-width="120px">
|
||||
<el-form-item label="模板名称" prop="templateName">
|
||||
<el-input v-model="templateForm.templateName" placeholder="请输入模板名称" />
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="模板文件" prop="file" v-if="!isEdit || uploadNewFile">
|
||||
<el-upload
|
||||
:action="'#'"
|
||||
:auto-upload="false"
|
||||
:limit="1"
|
||||
:on-change="handleFileChange"
|
||||
:on-remove="handleFileRemove"
|
||||
:file-list="fileList"
|
||||
accept=".docx">
|
||||
<el-button size="small" type="primary">点击上传</el-button>
|
||||
<div slot="tip" class="el-upload__tip">仅支持docx格式,大小不超过5M</div>
|
||||
</el-upload>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item v-if="isEdit && !uploadNewFile">
|
||||
<el-checkbox v-model="uploadNewFile">更新模板文件</el-checkbox>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="应用楼宇" prop="buildingIds">
|
||||
<el-popover
|
||||
placement="bottom-start"
|
||||
width="400"
|
||||
trigger="click"
|
||||
v-model="buildingPopoverVisible">
|
||||
<el-tree
|
||||
ref="buildingTree"
|
||||
:data="buildingTreeData"
|
||||
node-key="id"
|
||||
:props="buildingProps"
|
||||
show-checkbox
|
||||
:default-expanded-keys="['parent_node']"
|
||||
@check="handleBuildingCheck">
|
||||
<span slot-scope="{ node, data }" class="custom-tree-node">
|
||||
<i v-if="data.icon" :class="data.icon"></i>
|
||||
<span>{{ node.label }}</span>
|
||||
</span>
|
||||
</el-tree>
|
||||
<el-input
|
||||
slot="reference"
|
||||
v-model="templateForm.buildingNames"
|
||||
placeholder="请选择应用楼宇"
|
||||
readonly
|
||||
suffix-icon="el-icon-arrow-down">
|
||||
</el-input>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitTemplateForm" v-if="activeTab === 'upload'">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<el-dialog title="提示" :visible.sync="deleteDialogVisible" width="360px" append-to-body>
|
||||
<div class="delete-confirm">
|
||||
<i class="el-icon-warning"></i>
|
||||
<span>确定要删除该收据模板吗?</span>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="deleteDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="confirmDelete">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 预览模板对话框 -->
|
||||
<el-dialog title="收据模板预览" :visible.sync="previewDialogVisible" width="800px" append-to-body>
|
||||
<div class="preview-container" v-loading="previewLoading">
|
||||
<div v-if="previewUrl" class="preview-iframe">
|
||||
<iframe :src="previewUrl" width="100%" height="500px" frameborder="0"></iframe>
|
||||
</div>
|
||||
<div v-else-if="!previewLoading" class="preview-error">
|
||||
<i class="el-icon-warning"></i>
|
||||
<p>预览加载失败,请尝试下载后查看</p>
|
||||
<div class="preview-actions">
|
||||
<el-button type="primary" @click="downloadCurrentPreview">下载文档</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="previewDialogVisible = false">关 闭</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getReceiptTemplateList,
|
||||
addReceiptTemplate,
|
||||
updateReceiptTemplate,
|
||||
deleteReceiptTemplate,
|
||||
previewReceiptTemplate,
|
||||
downloadReceiptTemplate,
|
||||
downloadExampleTemplate,
|
||||
getReceiptKeywords,
|
||||
checkTemplateName
|
||||
} from '@/api/finance'
|
||||
import { getBuildingList } from '@/api/building'
|
||||
import { API_SUCCESS_CODE } from '@/utils/constants'
|
||||
|
||||
export default {
|
||||
name: 'ReceiptTemplate',
|
||||
data() {
|
||||
// 自定义模板名称校验器
|
||||
const validateTemplateName = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
return callback(new Error('请输入模板名称'))
|
||||
}
|
||||
|
||||
// 编辑时不检查当前名称
|
||||
if (this.isEdit && value === this.originalName) {
|
||||
return callback()
|
||||
}
|
||||
|
||||
checkTemplateName({
|
||||
templateName: value,
|
||||
id: this.isEdit ? this.currentId : undefined
|
||||
}).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
if (res.data) {
|
||||
callback(new Error('模板名称已存在'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}).catch(() => {
|
||||
callback()
|
||||
})
|
||||
}
|
||||
|
||||
// 自定义文件校验器
|
||||
const validateFile = (rule, value, callback) => {
|
||||
// 编辑模式且不更新文件时,不验证文件
|
||||
if (this.isEdit && !this.uploadNewFile) {
|
||||
return callback()
|
||||
}
|
||||
|
||||
if (this.fileList.length === 0) {
|
||||
return callback(new Error('请上传模板文件'))
|
||||
}
|
||||
|
||||
callback()
|
||||
}
|
||||
|
||||
return {
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
templateName: ''
|
||||
},
|
||||
|
||||
// 当前激活的tab
|
||||
activeTab: 'upload',
|
||||
|
||||
// 原始模板名称(编辑时使用)
|
||||
originalName: '',
|
||||
|
||||
// 加载状态
|
||||
loading: false,
|
||||
|
||||
// 模板列表数据
|
||||
templateList: [],
|
||||
total: 0,
|
||||
|
||||
// 楼宇选项
|
||||
buildingOptions: [],
|
||||
|
||||
// 楼宇树形数据
|
||||
buildingTreeData: [],
|
||||
buildingPopoverVisible: false,
|
||||
buildingProps: {
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
},
|
||||
|
||||
// 文件列表
|
||||
fileList: [],
|
||||
|
||||
// 对话框显示控制
|
||||
dialogVisible: false,
|
||||
deleteDialogVisible: false,
|
||||
previewDialogVisible: false,
|
||||
|
||||
// 是否为编辑模式
|
||||
isEdit: false,
|
||||
uploadNewFile: false,
|
||||
|
||||
// 当前操作的模板ID
|
||||
currentId: null,
|
||||
|
||||
// 模板表单
|
||||
templateForm: {
|
||||
templateName: '',
|
||||
buildingIds: [],
|
||||
buildingNames: ''
|
||||
},
|
||||
|
||||
// 表单校验规则
|
||||
rules: {
|
||||
templateName: [
|
||||
{ required: true, message: '请输入模板名称', trigger: 'blur' },
|
||||
{ validator: validateTemplateName, trigger: 'blur' }
|
||||
],
|
||||
file: [
|
||||
{ validator: validateFile, trigger: 'change' }
|
||||
],
|
||||
buildingIds: [
|
||||
{ required: true, message: '请选择应用楼宇', trigger: 'change' }
|
||||
]
|
||||
},
|
||||
|
||||
// 关键字数据
|
||||
keywords: {
|
||||
receiptInfo: ['${收据编号}', '${汇款方式}', '${费用类型}', '${费用名称}', '${开据金额}', '${开据金额(大写)}', '${开据时间}', '${开据人}', '${租赁面积}'],
|
||||
payerInfo: ['${交款单位}', '${交款人}', '${交款方电话}', '${交款方地址}', '${收款单位}', '${收款人}', '${收款方电话}', '${收款方地址}'],
|
||||
propertyInfo: ['${项目名称}', '${项目地址}', '${楼宇名称}', '${楼层房号}'],
|
||||
billInfo: ['${账单编号}', '${楼宇名称}']
|
||||
},
|
||||
|
||||
// 预览相关
|
||||
previewBlob: null,
|
||||
currentPreviewName: '',
|
||||
previewLoading: false,
|
||||
previewUrl: '', // PDF的URL
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
dialogTitle() {
|
||||
return this.isEdit ? '编辑收据模板' : '新增收据模板'
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getTemplateList()
|
||||
this.getBuildingOptions()
|
||||
this.fetchKeywords()
|
||||
},
|
||||
methods: {
|
||||
// 获取模板列表
|
||||
getTemplateList() {
|
||||
this.loading = true
|
||||
getReceiptTemplateList(this.queryParams).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.templateList = res.data.data || []
|
||||
this.total = res.data.total || 0
|
||||
} else {
|
||||
this.$message.error(res.message || '获取收据模板列表失败')
|
||||
this.templateList = []
|
||||
this.total = 0
|
||||
}
|
||||
this.loading = false
|
||||
}).catch(error => {
|
||||
console.error('获取收据模板列表失败', error)
|
||||
this.$message.error('获取收据模板列表失败')
|
||||
this.templateList = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 生成楼宇树形数据
|
||||
generateBuildingTree() {
|
||||
// 创建树形结构
|
||||
const parentNode = {
|
||||
id: 'parent_node',
|
||||
label: '小红马演示园区3',
|
||||
children: this.buildingOptions.map(item => ({
|
||||
id: item.id,
|
||||
label: item.name,
|
||||
icon: 'el-icon-office-building'
|
||||
}))
|
||||
}
|
||||
|
||||
this.buildingTreeData = [parentNode]
|
||||
},
|
||||
|
||||
// 处理楼宇选择
|
||||
handleBuildingCheck() {
|
||||
// 获取所有选中的叶子节点
|
||||
const checkedNodes = this.$refs.buildingTree.getCheckedNodes(false, false)
|
||||
const leafNodes = checkedNodes.filter(node => !node.children || node.children.length === 0)
|
||||
|
||||
// 过滤掉父节点,只保留叶子节点
|
||||
const selectedBuildings = leafNodes.filter(node => node.id !== 'parent_node')
|
||||
|
||||
// 更新选中楼宇ID和名称
|
||||
this.templateForm.buildingIds = selectedBuildings.map(node => node.id)
|
||||
this.templateForm.buildingNames = selectedBuildings.map(node => node.label).join(',')
|
||||
},
|
||||
|
||||
// 获取楼宇选项
|
||||
getBuildingOptions() {
|
||||
getBuildingList().then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.buildingOptions = res.data || []
|
||||
this.generateBuildingTree()
|
||||
} else {
|
||||
// 模拟数据
|
||||
this.buildingOptions = [
|
||||
{ id: 'BLD001', name: 'A栋办公楼' },
|
||||
{ id: 'BLD002', name: 'B栋办公楼' },
|
||||
{ id: 'BLD003', name: 'C栋研发中心' },
|
||||
{ id: 'BLD004', name: 'D栋会议中心' },
|
||||
{ id: 'BLD005', name: 'E栋商业配套' }
|
||||
]
|
||||
this.generateBuildingTree()
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取楼宇列表失败', error)
|
||||
// 模拟数据
|
||||
this.buildingOptions = [
|
||||
{ id: 'BLD001', name: 'A栋办公楼' },
|
||||
{ id: 'BLD002', name: 'B栋办公楼' },
|
||||
{ id: 'BLD003', name: 'C栋研发中心' },
|
||||
{ id: 'BLD004', name: 'D栋会议中心' },
|
||||
{ id: 'BLD005', name: 'E栋商业配套' }
|
||||
]
|
||||
this.generateBuildingTree()
|
||||
})
|
||||
},
|
||||
|
||||
// 搜索
|
||||
handleSearch() {
|
||||
this.queryParams.current = 1
|
||||
this.getTemplateList()
|
||||
},
|
||||
|
||||
// 重置查询
|
||||
resetQuery() {
|
||||
this.queryParams = {
|
||||
current: 1,
|
||||
size: 10,
|
||||
templateName: ''
|
||||
}
|
||||
this.getTemplateList()
|
||||
},
|
||||
|
||||
// 处理分页大小变化
|
||||
handleSizeChange(val) {
|
||||
this.queryParams.size = val
|
||||
this.getTemplateList()
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleCurrentChange(val) {
|
||||
this.queryParams.current = val
|
||||
this.getTemplateList()
|
||||
},
|
||||
|
||||
// 处理文件变更
|
||||
handleFileChange(file) {
|
||||
// 验证文件类型
|
||||
const isDocx = file.raw.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
if (!isDocx) {
|
||||
this.$message.error('请上传docx格式的文件')
|
||||
this.fileList = []
|
||||
return false
|
||||
}
|
||||
|
||||
// 验证文件大小
|
||||
const isLt5M = file.size / 1024 / 1024 < 5
|
||||
if (!isLt5M) {
|
||||
this.$message.error('文件大小不能超过5MB')
|
||||
this.fileList = []
|
||||
return false
|
||||
}
|
||||
|
||||
this.fileList = [file]
|
||||
},
|
||||
|
||||
// 处理文件移除
|
||||
handleFileRemove() {
|
||||
this.fileList = []
|
||||
},
|
||||
|
||||
// 打开上传模板对话框
|
||||
handleUploadTemplate() {
|
||||
this.isEdit = false
|
||||
this.uploadNewFile = true
|
||||
this.currentId = null
|
||||
this.originalName = ''
|
||||
this.templateForm = {
|
||||
templateName: '',
|
||||
buildingIds: [],
|
||||
buildingNames: ''
|
||||
}
|
||||
this.fileList = []
|
||||
this.activeTab = 'upload'
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.templateFormRef) {
|
||||
this.$refs.templateFormRef.clearValidate()
|
||||
}
|
||||
// 重置树的选中状态
|
||||
if (this.$refs.buildingTree) {
|
||||
this.$refs.buildingTree.setCheckedKeys([])
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 编辑模板
|
||||
handleEditTemplate(row) {
|
||||
this.isEdit = true
|
||||
this.uploadNewFile = false
|
||||
this.currentId = row.id
|
||||
this.originalName = row.templateName
|
||||
|
||||
// 如果buildingIds是字符串,转换为数组
|
||||
let buildingIds = row.buildingIds
|
||||
if (typeof buildingIds === 'string') {
|
||||
buildingIds = buildingIds.split(',').filter(id => id)
|
||||
} else if (!Array.isArray(buildingIds)) {
|
||||
buildingIds = []
|
||||
}
|
||||
|
||||
// 查找楼宇名称
|
||||
let buildingNames = ''
|
||||
if (Array.isArray(buildingIds) && buildingIds.length > 0) {
|
||||
const selectedBuildingNames = []
|
||||
this.buildingTreeData.forEach(parent => {
|
||||
if (parent.children) {
|
||||
parent.children.forEach(building => {
|
||||
if (buildingIds.includes(building.id)) {
|
||||
selectedBuildingNames.push(building.label)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
buildingNames = selectedBuildingNames.join(',')
|
||||
}
|
||||
|
||||
this.templateForm = {
|
||||
templateName: row.templateName,
|
||||
buildingIds: buildingIds,
|
||||
buildingNames: buildingNames
|
||||
}
|
||||
|
||||
this.fileList = []
|
||||
this.activeTab = 'upload'
|
||||
this.dialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.templateFormRef) {
|
||||
this.$refs.templateFormRef.clearValidate()
|
||||
}
|
||||
// 设置树的默认选中状态
|
||||
if (this.$refs.buildingTree) {
|
||||
this.$refs.buildingTree.setCheckedKeys(buildingIds)
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 提交模板表单
|
||||
submitTemplateForm() {
|
||||
this.$refs.templateFormRef.validate(valid => {
|
||||
if (valid) {
|
||||
// 构建FormData
|
||||
const formData = new FormData()
|
||||
formData.append('templateName', this.templateForm.templateName)
|
||||
|
||||
// 处理buildingIds,按接口要求传递数组
|
||||
if (Array.isArray(this.templateForm.buildingIds)) {
|
||||
this.templateForm.buildingIds.forEach(id => {
|
||||
formData.append('buildingIds', id)
|
||||
})
|
||||
}
|
||||
|
||||
// 上传文件
|
||||
if (!this.isEdit || this.uploadNewFile) {
|
||||
formData.append('file', this.fileList[0].raw)
|
||||
}
|
||||
|
||||
if (this.isEdit) {
|
||||
// 编辑模式
|
||||
updateReceiptTemplate(this.currentId, formData).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('更新收据模板成功')
|
||||
this.dialogVisible = false
|
||||
this.getTemplateList()
|
||||
} else {
|
||||
this.$message.error(res.message || '更新收据模板失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('更新收据模板失败', error)
|
||||
this.$message.error('更新收据模板失败')
|
||||
})
|
||||
} else {
|
||||
// 新增模式
|
||||
addReceiptTemplate(formData).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('新增收据模板成功')
|
||||
this.dialogVisible = false
|
||||
this.getTemplateList()
|
||||
} else {
|
||||
this.$message.error(res.message || '新增收据模板失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('新增收据模板失败', error)
|
||||
this.$message.error('新增收据模板失败')
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 预览模板
|
||||
async handlePreviewTemplate(row) {
|
||||
this.previewLoading = true
|
||||
this.previewDialogVisible = true
|
||||
this.currentPreviewName = row.templateName || '收据模板'
|
||||
this.previewUrl = ''
|
||||
|
||||
try {
|
||||
// 调用预览API获取PDF内容
|
||||
const response = await previewReceiptTemplate(row.id)
|
||||
|
||||
// 处理返回的二进制数据 - 现在是PDF格式
|
||||
const blob = new Blob([response.data], { type: 'application/pdf' })
|
||||
this.previewBlob = blob
|
||||
|
||||
// 创建URL用于预览
|
||||
this.previewUrl = URL.createObjectURL(blob)
|
||||
this.previewLoading = false
|
||||
} catch (error) {
|
||||
console.error('预览失败', error)
|
||||
this.$message.error('获取预览数据失败')
|
||||
this.previewLoading = false
|
||||
}
|
||||
},
|
||||
|
||||
// 下载当前预览的文档
|
||||
downloadCurrentPreview() {
|
||||
if (!this.previewBlob) {
|
||||
this.$message.error('没有可下载的文档')
|
||||
return
|
||||
}
|
||||
|
||||
const url = URL.createObjectURL(this.previewBlob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `${this.currentPreviewName}.pdf`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
},
|
||||
|
||||
// 下载模板
|
||||
handleDownloadTemplate(row) {
|
||||
downloadReceiptTemplate(row.id).then(response => {
|
||||
// 处理文件下载
|
||||
const blob = new Blob([response.data], {
|
||||
type: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
})
|
||||
const url = URL.createObjectURL(blob)
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', `${row.templateName}.docx`)
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
URL.revokeObjectURL(url)
|
||||
}).catch(error => {
|
||||
console.error('下载模板失败', error)
|
||||
this.$message.error('下载模板失败')
|
||||
})
|
||||
},
|
||||
|
||||
// 下载样例模板
|
||||
handleDownloadExample() {
|
||||
downloadExampleTemplate().then(response => {
|
||||
// 处理文件下载
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]))
|
||||
const link = document.createElement('a')
|
||||
link.href = url
|
||||
link.setAttribute('download', '收据模板样例.docx')
|
||||
document.body.appendChild(link)
|
||||
link.click()
|
||||
document.body.removeChild(link)
|
||||
}).catch(error => {
|
||||
console.error('下载样例模板失败', error)
|
||||
this.$message.error('下载样例模板失败')
|
||||
})
|
||||
},
|
||||
|
||||
// 删除模板
|
||||
handleDeleteTemplate(row) {
|
||||
this.currentId = row.id
|
||||
this.deleteDialogVisible = true
|
||||
},
|
||||
|
||||
// 确认删除
|
||||
confirmDelete() {
|
||||
if (!this.currentId) return
|
||||
|
||||
deleteReceiptTemplate(this.currentId).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('删除成功')
|
||||
this.getTemplateList()
|
||||
} else {
|
||||
this.$message.error(res.message || '删除失败')
|
||||
}
|
||||
this.deleteDialogVisible = false
|
||||
}).catch(error => {
|
||||
console.error('删除失败', error)
|
||||
this.$message.error('删除失败')
|
||||
this.deleteDialogVisible = false
|
||||
})
|
||||
},
|
||||
|
||||
// 获取关键字
|
||||
fetchKeywords() {
|
||||
getReceiptKeywords().then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.keywords = res.data
|
||||
} else {
|
||||
this.$message.error(res.message || '获取关键字失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取关键字失败', error)
|
||||
this.$message.error('获取关键字失败')
|
||||
})
|
||||
},
|
||||
|
||||
// 复制关键字
|
||||
copyKeyword(keyword) {
|
||||
const textarea = document.createElement('textarea')
|
||||
textarea.value = keyword
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select()
|
||||
document.execCommand('copy')
|
||||
document.body.removeChild(textarea)
|
||||
this.$message.success('已复制到剪贴板')
|
||||
},
|
||||
|
||||
// 打开复制关键字对话框
|
||||
handleShowKeywords() {
|
||||
// 先获取最新的关键字
|
||||
this.fetchKeywords()
|
||||
|
||||
// 重置表单等信息
|
||||
this.isEdit = false
|
||||
this.uploadNewFile = true
|
||||
this.currentId = null
|
||||
this.originalName = ''
|
||||
this.templateForm = {
|
||||
templateName: '',
|
||||
buildingIds: [],
|
||||
buildingNames: ''
|
||||
}
|
||||
this.fileList = []
|
||||
|
||||
// 设置active tab并打开对话框
|
||||
this.activeTab = 'keywords'
|
||||
this.dialogVisible = true
|
||||
},
|
||||
|
||||
// 格式化楼宇名称
|
||||
formatBuildingNames(buildingIds) {
|
||||
if (!buildingIds) return '-'
|
||||
|
||||
// 如果buildingIds是字符串,将其转换为数组
|
||||
let buildingIdArray = buildingIds
|
||||
if (typeof buildingIds === 'string') {
|
||||
buildingIdArray = buildingIds.split(',').filter(id => id)
|
||||
}
|
||||
|
||||
if (!Array.isArray(buildingIdArray) || buildingIdArray.length === 0) {
|
||||
return '-'
|
||||
}
|
||||
|
||||
const buildingNames = []
|
||||
this.buildingTreeData.forEach(parent => {
|
||||
if (parent.children) {
|
||||
parent.children.forEach(building => {
|
||||
if (buildingIdArray.includes(building.id)) {
|
||||
buildingNames.push(building.label)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
return buildingNames.length > 0 ? buildingNames.join(',') : '-'
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.receipt-template-container {
|
||||
.action-bar,
|
||||
.filter-container {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.delete-confirm {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
font-size: 24px;
|
||||
color: #e6a23c;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.keywords-container {
|
||||
padding: 15px;
|
||||
|
||||
.keyword-section {
|
||||
margin-bottom: 20px;
|
||||
|
||||
h4 {
|
||||
margin: 0 0 10px 0;
|
||||
font-weight: bold;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid #EBEEF5;
|
||||
}
|
||||
|
||||
.keyword-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
.keyword-item {
|
||||
margin: 5px;
|
||||
padding: 8px 15px;
|
||||
background-color: #f5f7fa;
|
||||
border: 1px solid #DCDFE6;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
font-size: 14px;
|
||||
|
||||
&:hover {
|
||||
background-color: #ecf5ff;
|
||||
color: #409EFF;
|
||||
border-color: #c6e2ff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 深度选择器 */
|
||||
:deep(.el-tree-node__content) {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
:deep(.el-tree-node__label) {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:deep(.custom-tree-node) {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-popover) {
|
||||
padding: 10px;
|
||||
|
||||
.el-tree {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.el-form-item__content .el-input) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.preview-container {
|
||||
min-height: 500px;
|
||||
max-height: 700px;
|
||||
overflow: auto;
|
||||
|
||||
.preview-content {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background-color: #fff;
|
||||
|
||||
p, h1, h2, h3, h4, h5, h6, table, ul, ol {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
width: 100%;
|
||||
|
||||
th, td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f2f2f2;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-iframe {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.preview-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 300px;
|
||||
color: #909399;
|
||||
|
||||
i {
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.preview-actions {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.preview-mode-switch {
|
||||
float: left;
|
||||
margin-right: 20px;
|
||||
}
|
||||
</style>
|
474
pc/src/views/finance/receiptSetting/index.vue
Normal file
474
pc/src/views/finance/receiptSetting/index.vue
Normal file
@ -0,0 +1,474 @@
|
||||
<template>
|
||||
<div class="receipt-setting-container">
|
||||
<el-tabs v-model="activeTab">
|
||||
<el-tab-pane label="收据编号" name="receiptNum">
|
||||
<!-- 收据编号规则查询 -->
|
||||
<div class="filter-container">
|
||||
<el-input v-model="queryParams.ruleName" placeholder="请输入收据编号规则名称" clearable style="width: 250px;"
|
||||
class="filter-item" />
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
|
||||
<el-button @click="resetQuery">重置</el-button>
|
||||
<el-button type="primary" icon="el-icon-plus" style="float: right;" @click="handleAddRule">新增收据编号规则</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 收据编号规则列表 -->
|
||||
<el-table v-loading="loading" :data="ruleList" border>
|
||||
<el-table-column prop="ruleName" label="收据编号规则名称" />
|
||||
<el-table-column prop="prefix" label="收据编号前缀" />
|
||||
<el-table-column prop="startNumber" label="开始编号" />
|
||||
<el-table-column prop="endNumber" label="结束编号" />
|
||||
<el-table-column prop="currentNumber" label="当前编号" />
|
||||
<el-table-column label="应用项目">
|
||||
<template slot-scope="scope">
|
||||
{{ formatProjectNames(scope.row.projectList) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="100">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" size="small" @click="handleDeleteRule(scope.row)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination background @size-change="handleSizeChange" @current-change="handleCurrentChange"
|
||||
:current-page="queryParams.current" :page-sizes="[10, 20, 30, 50]" :page-size="queryParams.size"
|
||||
layout="total, sizes, prev, pager, next, jumper" :total="total" />
|
||||
</div>
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="收款方信息" name="payeeInfo">
|
||||
<payee-info />
|
||||
</el-tab-pane>
|
||||
|
||||
<el-tab-pane label="收据模板" name="receiptTemplate">
|
||||
<receipt-template />
|
||||
</el-tab-pane>
|
||||
</el-tabs>
|
||||
|
||||
<!-- 新增收据编号规则对话框 -->
|
||||
<el-dialog title="新增收据编号规则" :visible.sync="ruleDialogVisible" width="500px" append-to-body>
|
||||
<el-form :model="ruleForm" :rules="ruleRules" ref="ruleFormRef" label-width="120px">
|
||||
<el-form-item label="收据编号名称" prop="ruleName">
|
||||
<el-input v-model="ruleForm.ruleName" placeholder="请输入收据编号名称" />
|
||||
</el-form-item>
|
||||
<el-form-item label="收据编号前缀" prop="prefix">
|
||||
<el-input v-model="ruleForm.prefix" placeholder="请输入收据编号前缀" />
|
||||
</el-form-item>
|
||||
<el-form-item label="开始编号" prop="startNumber">
|
||||
<el-input v-model="ruleForm.startNumber" placeholder="请输入开始编号" type="number" />
|
||||
</el-form-item>
|
||||
<el-form-item label="结束编号" prop="endNumber">
|
||||
<el-input v-model="ruleForm.endNumber" placeholder="请输入结束编号" type="number" />
|
||||
</el-form-item>
|
||||
<el-form-item label="应用项目" prop="projectIds">
|
||||
<el-popover
|
||||
placement="bottom-start"
|
||||
width="400"
|
||||
trigger="click"
|
||||
v-model="projectPopoverVisible">
|
||||
<el-tree
|
||||
ref="projectTree"
|
||||
:data="projectTreeData"
|
||||
node-key="id"
|
||||
:props="projectProps"
|
||||
show-checkbox
|
||||
:default-expanded-keys="['parent_node']"
|
||||
@check="handleProjectCheck">
|
||||
<span slot-scope="{ node, data }" class="custom-tree-node">
|
||||
<i v-if="data.icon" :class="data.icon"></i>
|
||||
<span>{{ node.label }}</span>
|
||||
</span>
|
||||
</el-tree>
|
||||
<el-input
|
||||
slot="reference"
|
||||
v-model="ruleForm.projectNames"
|
||||
placeholder="请选择应用项目"
|
||||
readonly
|
||||
suffix-icon="el-icon-arrow-down">
|
||||
</el-input>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="ruleDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitRuleForm">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 删除确认对话框 -->
|
||||
<el-dialog title="提示" :visible.sync="deleteDialogVisible" width="360px" append-to-body>
|
||||
<div class="delete-confirm">
|
||||
<i class="el-icon-warning"></i>
|
||||
<span>确定要删除该收据编号规则吗?</span>
|
||||
</div>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="deleteDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="confirmDelete">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
listReceiptRule,
|
||||
addReceiptRule,
|
||||
deleteReceiptRule,
|
||||
getProjectList
|
||||
} from '@/api/finance'
|
||||
import { API_SUCCESS_CODE } from '@/utils/constants'
|
||||
import PayeeInfo from './PayeeInfo.vue'
|
||||
import ReceiptTemplate from './ReceiptTemplate.vue'
|
||||
|
||||
export default {
|
||||
name: 'ReceiptSetting',
|
||||
components: {
|
||||
PayeeInfo,
|
||||
ReceiptTemplate
|
||||
},
|
||||
data() {
|
||||
// 校验结束编号
|
||||
const validateEndNum = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
callback(new Error('请输入结束编号'))
|
||||
} else if (!/^\d+$/.test(value)) {
|
||||
callback(new Error('结束编号必须为数字'))
|
||||
} else if (parseInt(value) <= parseInt(this.ruleForm.startNumber)) {
|
||||
callback(new Error('结束编号必须大于开始编号'))
|
||||
} else if (value.length !== this.ruleForm.startNumber.length) {
|
||||
callback(new Error('结束编号和开始编号的位数必须相同'))
|
||||
} else {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
// 选项卡
|
||||
activeTab: 'receiptNum',
|
||||
|
||||
// 查询参数
|
||||
queryParams: {
|
||||
current: 1,
|
||||
size: 10,
|
||||
ruleName: '',
|
||||
},
|
||||
|
||||
// 加载状态
|
||||
loading: false,
|
||||
|
||||
// 列表数据
|
||||
ruleList: [],
|
||||
total: 0,
|
||||
|
||||
// 项目原始数据
|
||||
projectOptions: [],
|
||||
|
||||
// 项目树形数据
|
||||
projectTreeData: [],
|
||||
projectPopoverVisible: false,
|
||||
projectProps: {
|
||||
label: 'label',
|
||||
children: 'children'
|
||||
},
|
||||
|
||||
// 对话框显示状态
|
||||
ruleDialogVisible: false,
|
||||
deleteDialogVisible: false,
|
||||
|
||||
// 当前要删除的规则ID
|
||||
currentRuleId: null,
|
||||
|
||||
// 规则表单
|
||||
ruleForm: {
|
||||
ruleName: '',
|
||||
prefix: '',
|
||||
startNumber: '',
|
||||
endNumber: '',
|
||||
projectIds: [],
|
||||
projectNames: ''
|
||||
},
|
||||
|
||||
// 表单校验规则
|
||||
ruleRules: {
|
||||
ruleName: [
|
||||
{ required: true, message: '请输入收据编号名称', trigger: 'blur' }
|
||||
],
|
||||
startNumber: [
|
||||
{ required: true, message: '请输入开始编号', trigger: 'blur' },
|
||||
{ pattern: /^\d+$/, message: '开始编号必须为数字', trigger: 'blur' }
|
||||
],
|
||||
endNumber: [
|
||||
{ required: true, validator: validateEndNum, trigger: 'blur' }
|
||||
],
|
||||
projectIds: [
|
||||
{ required: true, message: '请选择应用项目', trigger: 'change' }
|
||||
]
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getRuleList()
|
||||
this.getProjects()
|
||||
},
|
||||
methods: {
|
||||
// 获取收据编号规则列表
|
||||
getRuleList() {
|
||||
this.loading = true
|
||||
listReceiptRule(this.queryParams).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.ruleList = res.data.data || []
|
||||
this.total = res.data.total || 0
|
||||
} else {
|
||||
this.$message.error(res.message || '获取收据编号规则列表失败')
|
||||
this.ruleList = []
|
||||
this.total = 0
|
||||
}
|
||||
this.loading = false
|
||||
}).catch(error => {
|
||||
console.error('获取收据编号规则列表失败', error)
|
||||
this.$message.error('获取收据编号规则列表失败')
|
||||
this.ruleList = []
|
||||
this.total = 0
|
||||
this.loading = false
|
||||
})
|
||||
},
|
||||
|
||||
// 获取项目列表
|
||||
getProjects() {
|
||||
getProjectList().then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.projectOptions = res.data || []
|
||||
this.generateProjectTree()
|
||||
} else {
|
||||
// 模拟一些项目数据
|
||||
this.projectOptions = [
|
||||
{ id: '2', name: '高新科技园区' },
|
||||
{ id: '3', name: '软件产业园' },
|
||||
{ id: '4', name: '文创产业园' },
|
||||
{ id: '5', name: '生物医药园区' },
|
||||
{ id: '8', name: '智能制造基地' }
|
||||
]
|
||||
this.generateProjectTree()
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('获取项目列表失败', error)
|
||||
// 模拟一些项目数据
|
||||
this.projectOptions = [
|
||||
{ id: '2', name: '高新科技园区' },
|
||||
{ id: '3', name: '软件产业园' },
|
||||
{ id: '4', name: '文创产业园' },
|
||||
{ id: '5', name: '生物医药园区' },
|
||||
{ id: '8', name: '智能制造基地' }
|
||||
]
|
||||
this.generateProjectTree()
|
||||
})
|
||||
},
|
||||
|
||||
// 生成项目树形数据
|
||||
generateProjectTree() {
|
||||
// 模拟树形结构,实际开发中应该根据接口返回的数据格式来处理
|
||||
// 这里假设有个固定的父节点"小红马演示园区3"
|
||||
const parentNode = {
|
||||
id: 'parent_node',
|
||||
label: '小红马演示园区3',
|
||||
children: this.projectOptions.map(item => ({
|
||||
id: item.id,
|
||||
label: item.name,
|
||||
icon: 'el-icon-office-building'
|
||||
}))
|
||||
}
|
||||
|
||||
this.projectTreeData = [parentNode]
|
||||
},
|
||||
|
||||
// 处理项目选择
|
||||
handleProjectCheck() {
|
||||
// 获取所有选中的叶子节点
|
||||
const checkedNodes = this.$refs.projectTree.getCheckedNodes(false, false)
|
||||
const leafNodes = checkedNodes.filter(node => !node.children || node.children.length === 0)
|
||||
|
||||
// 过滤掉父节点,只保留叶子节点
|
||||
const selectedProjects = leafNodes.filter(node => node.id !== 'parent_node')
|
||||
|
||||
// 更新选中项目ID和名称
|
||||
this.ruleForm.projectIds = selectedProjects.map(node => node.id)
|
||||
this.ruleForm.projectNames = selectedProjects.map(node => node.label).join(',')
|
||||
},
|
||||
|
||||
// 重写打开新增规则对话框方法
|
||||
handleAddRule() {
|
||||
this.ruleForm = {
|
||||
ruleName: '',
|
||||
prefix: '',
|
||||
startNumber: '',
|
||||
endNumber: '',
|
||||
projectIds: [],
|
||||
projectNames: ''
|
||||
}
|
||||
this.ruleDialogVisible = true
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.ruleFormRef) {
|
||||
this.$refs.ruleFormRef.clearValidate()
|
||||
}
|
||||
// 重置树的选中状态
|
||||
if (this.$refs.projectTree) {
|
||||
this.$refs.projectTree.setCheckedKeys([])
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 提交规则表单
|
||||
submitRuleForm() {
|
||||
this.$refs.ruleFormRef.validate(valid => {
|
||||
if (valid) {
|
||||
// 准备提交数据,将projectIds转换为逗号分隔的字符串
|
||||
const submitData = {
|
||||
...this.ruleForm,
|
||||
projectIds: Array.isArray(this.ruleForm.projectIds) ?
|
||||
this.ruleForm.projectIds.join(',') :
|
||||
this.ruleForm.projectIds
|
||||
}
|
||||
|
||||
addReceiptRule(submitData).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('新增收据编号规则成功')
|
||||
this.ruleDialogVisible = false
|
||||
this.getRuleList()
|
||||
} else {
|
||||
this.$message.error(res.message || '新增收据编号规则失败')
|
||||
}
|
||||
}).catch(error => {
|
||||
console.error('新增收据编号规则失败', error)
|
||||
this.$message.error('新增收据编号规则失败')
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 删除规则
|
||||
handleDeleteRule(row) {
|
||||
this.currentRuleId = row.id
|
||||
this.deleteDialogVisible = true
|
||||
},
|
||||
|
||||
// 确认删除
|
||||
confirmDelete() {
|
||||
if (!this.currentRuleId) return
|
||||
|
||||
deleteReceiptRule(this.currentRuleId).then(res => {
|
||||
if (res && res.code === API_SUCCESS_CODE) {
|
||||
this.$message.success('删除成功')
|
||||
this.getRuleList()
|
||||
} else {
|
||||
this.$message.error(res.message || '删除失败')
|
||||
}
|
||||
this.deleteDialogVisible = false
|
||||
}).catch(error => {
|
||||
console.error('删除失败', error)
|
||||
this.$message.error('删除失败')
|
||||
this.deleteDialogVisible = false
|
||||
})
|
||||
},
|
||||
|
||||
// 格式化项目名称
|
||||
formatProjectNames(projectList) {
|
||||
if (!projectList || !Array.isArray(projectList) || projectList.length === 0 ) {
|
||||
return '-'
|
||||
}
|
||||
return projectList.map(item => item && item.projectName||'').join(',')
|
||||
},
|
||||
|
||||
// 搜索
|
||||
handleSearch() {
|
||||
this.queryParams.current = 1
|
||||
this.getRuleList()
|
||||
},
|
||||
|
||||
// 重置
|
||||
resetQuery() {
|
||||
this.queryParams = {
|
||||
current: 1,
|
||||
size: 10,
|
||||
ruleName: ''
|
||||
}
|
||||
this.getRuleList()
|
||||
},
|
||||
|
||||
// 处理分页大小变化
|
||||
handleSizeChange(val) {
|
||||
this.queryParams.size = val
|
||||
this.getRuleList()
|
||||
},
|
||||
|
||||
// 处理页码变化
|
||||
handleCurrentChange(val) {
|
||||
this.queryParams.current = val
|
||||
this.getRuleList()
|
||||
},
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.receipt-setting-container {
|
||||
padding: 20px;
|
||||
|
||||
.filter-container {
|
||||
margin: 15px 0;
|
||||
|
||||
.filter-item {
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.delete-confirm {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
font-size: 24px;
|
||||
color: #e6a23c;
|
||||
margin-right: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
:v-deep .el-tree-node__content {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
:v-deep .el-tree-node__label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
:v-deep .custom-tree-node {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
i {
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
:v-deep .el-popover {
|
||||
padding: 10px;
|
||||
|
||||
.el-tree {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
}
|
||||
|
||||
:v-deep .el-form-item__content .el-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
29
pc/src/views/merchant/clue-management/index.vue
Normal file
29
pc/src/views/merchant/clue-management/index.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="clue-management-container">
|
||||
<h1>线索管理页面</h1>
|
||||
<p>这里是线索管理的内容,根据需求继续完善</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'ClueManagement',
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.clue-management-container {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
29
pc/src/views/merchant/intent-customer/index.vue
Normal file
29
pc/src/views/merchant/intent-customer/index.vue
Normal file
@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="intent-customer-container">
|
||||
<h1>意向客户页面</h1>
|
||||
<p>这里是意向客户的内容,根据需求继续完善</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'IntentCustomer',
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
created() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.intent-customer-container {
|
||||
padding: 20px;
|
||||
}
|
||||
</style>
|
1559
pc/src/views/merchant/merchant-personnel/index.vue
Normal file
1559
pc/src/views/merchant/merchant-personnel/index.vue
Normal file
File diff suppressed because it is too large
Load Diff
844
pc/src/views/merchant/tag-management/index.vue
Normal file
844
pc/src/views/merchant/tag-management/index.vue
Normal file
@ -0,0 +1,844 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-row :gutter="20">
|
||||
<!-- 左侧分组区域 -->
|
||||
<el-col :span="6">
|
||||
<div class="group-container">
|
||||
<div class="group-header">
|
||||
<h3>分组管理</h3>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="mini"
|
||||
icon="el-icon-plus"
|
||||
@click="handleAddGroup">
|
||||
新增
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="group-search">
|
||||
<el-input
|
||||
v-model="groupSearchInput"
|
||||
placeholder="请输入分组名称"
|
||||
clearable
|
||||
size="small"
|
||||
@keyup.enter.native="handleGroupSearch">
|
||||
<el-button slot="append" icon="el-icon-search" @click="handleGroupSearch"></el-button>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="group-menu">
|
||||
<el-menu
|
||||
:default-active="activeGroupId"
|
||||
class="el-menu-vertical-demo"
|
||||
@select="handleGroupSelect">
|
||||
<el-menu-item
|
||||
v-for="group in filteredGroupList"
|
||||
:key="group.id"
|
||||
:index="group.id.toString()"
|
||||
class="group-item">
|
||||
<span>{{ group.groupName }}</span>
|
||||
<div class="group-actions">
|
||||
<el-tooltip content="编辑" placement="top">
|
||||
<i class="el-icon-edit" @click.stop="handleEditGroup(group)"></i>
|
||||
</el-tooltip>
|
||||
<el-tooltip content="删除" placement="top">
|
||||
<i class="el-icon-delete" @click.stop="handleDeleteGroup(group)"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-menu-item>
|
||||
</el-menu>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
|
||||
<!-- 右侧标签区域 -->
|
||||
<el-col :span="18">
|
||||
<div class="tag-container">
|
||||
<div class="tag-header">
|
||||
<div class="header-title">
|
||||
<h3>标签管理</h3>
|
||||
<span v-if="activeGroup">- {{ activeGroup.groupName }}</span>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
icon="el-icon-plus"
|
||||
@click="handleAddTag"
|
||||
:disabled="!activeGroupId">
|
||||
新增标签
|
||||
</el-button>
|
||||
</div>
|
||||
|
||||
<!-- 标签搜索区域 -->
|
||||
<div class="tag-search">
|
||||
<el-form :inline="true" :model="tagQueryParams" class="search-form">
|
||||
<el-form-item label="标签名称">
|
||||
<el-input
|
||||
v-model="tagQueryParams.tagName"
|
||||
placeholder="请输入标签名称"
|
||||
clearable
|
||||
@keyup.enter.native="handleTagQuery">
|
||||
</el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" @click="handleTagQuery">查询</el-button>
|
||||
<el-button icon="el-icon-refresh" @click="resetTagQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
|
||||
<!-- 标签表格 -->
|
||||
<el-table
|
||||
v-loading="tableLoading"
|
||||
:data="paginationTagList"
|
||||
style="width: 100%"
|
||||
border>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
width="55">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="tagName"
|
||||
label="标签名称">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="intentionCustomerCount"
|
||||
label="意向客户数量"
|
||||
width="120">
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
label="操作"
|
||||
width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
@click="handleEditTag(scope.row)">
|
||||
<i class="el-icon-edit"></i> 编辑
|
||||
</el-button>
|
||||
<el-button
|
||||
size="mini"
|
||||
type="text"
|
||||
class="delete-btn"
|
||||
@click="handleDeleteTag(scope.row)">
|
||||
<i class="el-icon-delete"></i> 删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<!-- 分页组件 -->
|
||||
<div class="pagination-container">
|
||||
<el-pagination
|
||||
v-show="total > 0"
|
||||
@size-change="handleSizeChange"
|
||||
@current-change="handleCurrentChange"
|
||||
:current-page="tagQueryParams.pageNum"
|
||||
:page-sizes="[10, 20, 30, 50]"
|
||||
:page-size="tagQueryParams.pageSize"
|
||||
layout="total, sizes, prev, pager, next, jumper"
|
||||
:total="total">
|
||||
</el-pagination>
|
||||
</div>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<!-- 分组弹窗 -->
|
||||
<el-dialog :title="groupDialogType === 'add' ? '新增分组' : '编辑分组'" :visible.sync="groupDialogVisible" width="500px" append-to-body>
|
||||
<el-form :model="groupForm" :rules="groupRules" ref="groupForm" label-width="100px">
|
||||
<el-form-item label="分组名称" prop="groupName">
|
||||
<el-input v-model="groupForm.groupName" placeholder="请输入分组名称" style="width: 100%"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="groupDialogType === 'add'" label="标签名称" prop="tagNames">
|
||||
<el-select
|
||||
v-model="groupForm.tagNames"
|
||||
multiple
|
||||
allow-create
|
||||
filterable
|
||||
default-first-option
|
||||
placeholder="请输入标签名称,按回车确认"
|
||||
style="width: 100%">
|
||||
</el-select>
|
||||
<div class="el-form-item-tip">按回车键可输入多个标签名称</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="groupDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitGroupForm">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 标签弹窗 -->
|
||||
<el-dialog :title="tagDialogType === 'add' ? '新增标签' : '编辑标签'" :visible.sync="tagDialogVisible" width="500px" append-to-body>
|
||||
<el-form :model="tagForm" :rules="tagRules" ref="tagForm" label-width="100px">
|
||||
<el-form-item label="选择分组" prop="groupId">
|
||||
<el-select v-model="tagForm.groupId" placeholder="请选择分组" style="width: 100%">
|
||||
<el-option
|
||||
v-for="group in groupList"
|
||||
:key="group.id"
|
||||
:label="group.groupName"
|
||||
:value="group.id">
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="标签名称" prop="names">
|
||||
<el-select
|
||||
v-model="tagForm.names"
|
||||
multiple
|
||||
allow-create
|
||||
filterable
|
||||
default-first-option
|
||||
placeholder="请输入标签名称,按回车确认"
|
||||
style="width: 100%">
|
||||
</el-select>
|
||||
<div class="el-form-item-tip">按回车键可输入多个标签名称</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button @click="tagDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitTagForm">确 定</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {
|
||||
getTagGroupList,
|
||||
getAllTagGroups,
|
||||
getTagGroupDetail,
|
||||
addTagGroup,
|
||||
updateTagGroup,
|
||||
deleteTagGroup,
|
||||
checkTagGroupName,
|
||||
getTagList,
|
||||
getTagListByGroupId,
|
||||
getTagDetail,
|
||||
addTag,
|
||||
updateTag,
|
||||
deleteTag,
|
||||
checkTagName
|
||||
} from '@/api/merchant'
|
||||
|
||||
export default {
|
||||
name: 'TagManagement',
|
||||
data() {
|
||||
// 校验分组名称是否重复
|
||||
const validateGroupName = (rule, value, callback) => {
|
||||
if (!value) {
|
||||
callback(new Error('请输入分组名称'))
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑时不需要校验自身名称
|
||||
if (this.groupDialogType === 'edit' && this.groupForm.originalName === value) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
const params = {
|
||||
groupName: value
|
||||
}
|
||||
|
||||
if (this.groupDialogType === 'edit') {
|
||||
params.id = this.groupForm.id
|
||||
}
|
||||
|
||||
checkTagGroupName(params).then(res => {
|
||||
if (res.success && res.data) {
|
||||
callback()
|
||||
} else {
|
||||
callback(new Error('分组名称已存在'))
|
||||
}
|
||||
}).catch(() => {
|
||||
callback(new Error('校验分组名称失败'))
|
||||
})
|
||||
}
|
||||
|
||||
// 校验标签名称是否重复
|
||||
const validateTagName = (rule, value, callback) => {
|
||||
if (!value || value.length === 0) {
|
||||
callback(new Error('请输入至少一个标签名称'))
|
||||
return
|
||||
}
|
||||
|
||||
// 多标签情况下,暂不支持前端检查
|
||||
if (value.length > 1 || this.tagDialogType === 'add') {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
// 编辑时不需要校验自身名称
|
||||
if (this.tagDialogType === 'edit' && this.tagForm.originalName === value[0]) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
|
||||
const params = {
|
||||
groupId: this.tagForm.groupId,
|
||||
tagName: value[0]
|
||||
}
|
||||
|
||||
if (this.tagDialogType === 'edit') {
|
||||
params.id = this.tagForm.id
|
||||
}
|
||||
|
||||
checkTagName(params).then(res => {
|
||||
if (res.success && res.data) {
|
||||
callback()
|
||||
} else {
|
||||
callback(new Error('标签名称在该分组下已存在'))
|
||||
}
|
||||
}).catch(() => {
|
||||
callback(new Error('校验标签名称失败'))
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
// 分组相关数据
|
||||
groupList: [],
|
||||
groupSearchInput: '',
|
||||
activeGroupId: '',
|
||||
activeGroup: null,
|
||||
groupDialogVisible: false,
|
||||
groupDialogType: 'add', // add或edit
|
||||
groupForm: {
|
||||
id: '',
|
||||
groupName: '',
|
||||
tagNames: [],
|
||||
originalName: '' // 用于校验
|
||||
},
|
||||
groupRules: {
|
||||
groupName: [
|
||||
{ required: true, message: '请输入分组名称', trigger: 'blur' },
|
||||
{ min: 1, max: 50, message: '长度在 1 到 50 个字符', trigger: 'blur' },
|
||||
{ validator: validateGroupName, trigger: 'blur' }
|
||||
],
|
||||
tagNames: [
|
||||
{ required: true, message: '请输入至少一个标签名称', trigger: 'change' }
|
||||
]
|
||||
},
|
||||
|
||||
// 标签相关数据
|
||||
tagList: [],
|
||||
tableLoading: false,
|
||||
total: 0,
|
||||
tagQueryParams: {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
tagName: undefined,
|
||||
groupId: null
|
||||
},
|
||||
tagDialogVisible: false,
|
||||
tagDialogType: 'add', // add或edit
|
||||
tagForm: {
|
||||
id: '',
|
||||
groupId: '',
|
||||
tagName: '',
|
||||
names: [],
|
||||
originalName: '' // 用于校验
|
||||
},
|
||||
tagRules: {
|
||||
groupId: [
|
||||
{ required: true, message: '请选择分组', trigger: 'change' }
|
||||
],
|
||||
names: [
|
||||
{ required: true, message: '请输入至少一个标签名称', trigger: 'change' },
|
||||
{ validator: validateTagName, trigger: 'blur' }
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 过滤后的分组列表
|
||||
filteredGroupList() {
|
||||
if (!this.groupSearchInput) {
|
||||
return this.groupList
|
||||
}
|
||||
return this.groupList.filter(group =>
|
||||
group.groupName.toLowerCase().includes(this.groupSearchInput.toLowerCase())
|
||||
)
|
||||
},
|
||||
// 分页后的标签列表
|
||||
paginationTagList() {
|
||||
let list = this.tagList
|
||||
if (this.tagQueryParams.tagName) {
|
||||
list = list.filter(tag =>
|
||||
tag.tagName.toLowerCase().includes(this.tagQueryParams.tagName.toLowerCase())
|
||||
)
|
||||
}
|
||||
this.total = list.length
|
||||
const start = (this.tagQueryParams.pageNum - 1) * this.tagQueryParams.pageSize
|
||||
const end = start + this.tagQueryParams.pageSize
|
||||
return list.slice(start, end)
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.fetchGroupList()
|
||||
},
|
||||
methods: {
|
||||
// 分组相关方法
|
||||
async fetchGroupList() {
|
||||
try {
|
||||
const res = await getAllTagGroups()
|
||||
if (res.success) {
|
||||
this.groupList = res.data || []
|
||||
if (this.groupList.length > 0 && !this.activeGroupId) {
|
||||
this.activeGroupId = this.groupList[0].id.toString()
|
||||
this.activeGroup = this.groupList[0]
|
||||
this.tagQueryParams.groupId = parseInt(this.activeGroupId)
|
||||
this.fetchTagList(this.activeGroupId)
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.message || '获取分组列表失败')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取分组列表失败', error)
|
||||
this.$message.error('获取分组列表失败')
|
||||
}
|
||||
},
|
||||
// 分组搜索
|
||||
handleGroupSearch() {
|
||||
// 分组搜索功能已通过计算属性filteredGroupList实现
|
||||
},
|
||||
handleGroupSelect(index) {
|
||||
this.activeGroupId = index
|
||||
this.activeGroup = this.groupList.find(group => group.id.toString() === index)
|
||||
this.tagQueryParams.groupId = parseInt(index)
|
||||
this.tagQueryParams.pageNum = 1
|
||||
this.fetchTagList(index)
|
||||
},
|
||||
handleAddGroup() {
|
||||
this.groupDialogType = 'add'
|
||||
this.groupForm = {
|
||||
id: '',
|
||||
groupName: '',
|
||||
tagNames: [],
|
||||
originalName: ''
|
||||
}
|
||||
this.groupDialogVisible = true
|
||||
},
|
||||
handleEditGroup(group) {
|
||||
this.groupDialogType = 'edit'
|
||||
this.groupForm = {
|
||||
id: group.id,
|
||||
groupName: group.groupName,
|
||||
tagNames: [],
|
||||
originalName: group.groupName
|
||||
}
|
||||
this.groupDialogVisible = true
|
||||
},
|
||||
handleDeleteGroup(group) {
|
||||
this.$confirm('此操作将永久删除该分组, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
// 判断分组下是否有标签
|
||||
if (group.tagCount > 0) {
|
||||
return this.$message.warning('该分组下存在标签,不能删除')
|
||||
}
|
||||
|
||||
deleteTagGroup(group.id).then(res => {
|
||||
if (res.success) {
|
||||
this.$message.success('删除成功')
|
||||
this.fetchGroupList()
|
||||
if (this.activeGroupId === group.id.toString()) {
|
||||
this.tagList = []
|
||||
this.total = 0
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.message || '删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.error('删除失败')
|
||||
})
|
||||
}).catch(() => {
|
||||
this.$message.info('已取消删除')
|
||||
})
|
||||
},
|
||||
submitGroupForm() {
|
||||
this.$refs.groupForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
const data = {
|
||||
groupName: this.groupForm.groupName
|
||||
}
|
||||
|
||||
if (this.groupDialogType === 'add') {
|
||||
const res = await addTagGroup(data)
|
||||
if (res.success) {
|
||||
this.$message.success('新增成功')
|
||||
// 如果有标签名称,添加标签
|
||||
if (this.groupForm.tagNames && this.groupForm.tagNames.length > 0) {
|
||||
// 获取新建的分组信息
|
||||
await this.fetchGroupList()
|
||||
const newGroup = this.groupList.find(g => g.groupName === this.groupForm.groupName)
|
||||
if (newGroup) {
|
||||
// 为新分组添加标签
|
||||
for (const tagName of this.groupForm.tagNames) {
|
||||
await addTag({
|
||||
groupId: newGroup.id,
|
||||
tagName: tagName
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.$message.error(res.message || '新增分组失败')
|
||||
}
|
||||
} else {
|
||||
data.id = this.groupForm.id
|
||||
const res = await updateTagGroup(data)
|
||||
if (res.success) {
|
||||
this.$message.success('更新成功')
|
||||
} else {
|
||||
this.$message.error(res.message || '更新分组失败')
|
||||
}
|
||||
}
|
||||
this.groupDialogVisible = false
|
||||
await this.fetchGroupList()
|
||||
} catch (error) {
|
||||
console.error(this.groupDialogType === 'add' ? '新增分组失败' : '更新分组失败', error)
|
||||
this.$message.error(this.groupDialogType === 'add' ? '新增分组失败' : '更新分组失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 标签相关方法
|
||||
async fetchTagList(groupId) {
|
||||
if (!groupId) return
|
||||
|
||||
this.tableLoading = true
|
||||
try {
|
||||
const res = await getTagListByGroupId(groupId)
|
||||
if (res.success) {
|
||||
this.tagList = res.data || []
|
||||
this.total = this.tagList.length
|
||||
this.tagQueryParams.pageNum = 1
|
||||
} else {
|
||||
this.$message.error(res.message || '获取标签列表失败')
|
||||
this.tagList = []
|
||||
this.total = 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取标签列表失败', error)
|
||||
this.$message.error('获取标签列表失败')
|
||||
this.tagList = []
|
||||
this.total = 0
|
||||
} finally {
|
||||
this.tableLoading = false
|
||||
}
|
||||
},
|
||||
// 标签查询
|
||||
handleTagQuery() {
|
||||
// 如果需要服务端查询
|
||||
if (this.tagQueryParams.tagName) {
|
||||
this.fetchTagsByParams()
|
||||
} else {
|
||||
this.fetchTagList(this.activeGroupId)
|
||||
}
|
||||
},
|
||||
// 服务端查询标签
|
||||
async fetchTagsByParams() {
|
||||
this.tableLoading = true
|
||||
try {
|
||||
const params = {
|
||||
...this.tagQueryParams,
|
||||
pageNum: 1,
|
||||
pageSize: 1000 // 暂时取大量数据本地分页
|
||||
}
|
||||
|
||||
const res = await getTagList(params)
|
||||
if (res.success) {
|
||||
this.tagList = res.data.list || []
|
||||
this.total = res.data.total || 0
|
||||
} else {
|
||||
this.$message.error(res.message || '查询标签失败')
|
||||
this.tagList = []
|
||||
this.total = 0
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('查询标签失败', error)
|
||||
this.$message.error('查询标签失败')
|
||||
this.tagList = []
|
||||
this.total = 0
|
||||
} finally {
|
||||
this.tableLoading = false
|
||||
}
|
||||
},
|
||||
// 重置标签查询
|
||||
resetTagQuery() {
|
||||
this.tagQueryParams.tagName = undefined
|
||||
this.tagQueryParams.pageNum = 1
|
||||
this.fetchTagList(this.activeGroupId)
|
||||
},
|
||||
handleAddTag() {
|
||||
this.tagDialogType = 'add'
|
||||
this.tagForm = {
|
||||
id: '',
|
||||
groupId: this.activeGroupId,
|
||||
tagName: '',
|
||||
names: [],
|
||||
originalName: ''
|
||||
}
|
||||
this.tagDialogVisible = true
|
||||
},
|
||||
handleEditTag(tag) {
|
||||
this.tagDialogType = 'edit'
|
||||
this.tagForm = {
|
||||
id: tag.id,
|
||||
groupId: tag.groupId,
|
||||
tagName: tag.tagName,
|
||||
names: [tag.tagName],
|
||||
originalName: tag.tagName
|
||||
}
|
||||
this.tagDialogVisible = true
|
||||
},
|
||||
handleDeleteTag(tag) {
|
||||
// 判断标签是否有关联的意向客户
|
||||
if (tag.intentionCustomerCount > 0) {
|
||||
return this.$message.warning('该标签已绑定意向客户,不能删除')
|
||||
}
|
||||
|
||||
this.$confirm('此操作将永久删除该标签, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
deleteTag(tag.id).then(res => {
|
||||
if (res.success) {
|
||||
this.$message.success('删除成功')
|
||||
this.fetchTagList(this.activeGroupId)
|
||||
} else {
|
||||
this.$message.error(res.message || '删除失败')
|
||||
}
|
||||
}).catch(() => {
|
||||
this.$message.error('删除失败')
|
||||
})
|
||||
}).catch(() => {
|
||||
this.$message.info('已取消删除')
|
||||
})
|
||||
},
|
||||
submitTagForm() {
|
||||
this.$refs.tagForm.validate(async (valid) => {
|
||||
if (valid) {
|
||||
try {
|
||||
if (this.tagDialogType === 'add') {
|
||||
// 多标签添加
|
||||
if (this.tagForm.names.length > 1) {
|
||||
let success = 0
|
||||
let fail = 0
|
||||
|
||||
for (const name of this.tagForm.names) {
|
||||
try {
|
||||
const res = await addTag({
|
||||
groupId: this.tagForm.groupId,
|
||||
tagName: name
|
||||
})
|
||||
|
||||
if (res.success) {
|
||||
success++
|
||||
} else {
|
||||
fail++
|
||||
}
|
||||
} catch (error) {
|
||||
fail++
|
||||
}
|
||||
}
|
||||
|
||||
if (success > 0) {
|
||||
this.$message.success(`成功添加${success}个标签`)
|
||||
}
|
||||
|
||||
if (fail > 0) {
|
||||
this.$message.warning(`${fail}个标签添加失败`)
|
||||
}
|
||||
} else {
|
||||
// 单标签添加
|
||||
const res = await addTag({
|
||||
groupId: this.tagForm.groupId,
|
||||
tagName: this.tagForm.names[0]
|
||||
})
|
||||
|
||||
if (res.success) {
|
||||
this.$message.success('新增成功')
|
||||
} else {
|
||||
this.$message.error(res.message || '新增标签失败')
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 编辑标签
|
||||
const res = await updateTag({
|
||||
id: this.tagForm.id,
|
||||
groupId: this.tagForm.groupId,
|
||||
tagName: this.tagForm.names[0]
|
||||
})
|
||||
|
||||
if (res.success) {
|
||||
this.$message.success('更新成功')
|
||||
} else {
|
||||
this.$message.error(res.message || '更新标签失败')
|
||||
}
|
||||
}
|
||||
|
||||
this.tagDialogVisible = false
|
||||
await this.fetchTagList(this.tagForm.groupId)
|
||||
|
||||
// 如果切换了分组,需要更新当前选中的分组
|
||||
if (this.activeGroupId !== this.tagForm.groupId) {
|
||||
this.activeGroupId = this.tagForm.groupId
|
||||
this.activeGroup = this.groupList.find(group => group.id.toString() === this.tagForm.groupId)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(this.tagDialogType === 'add' ? '新增标签失败' : '更新标签失败', error)
|
||||
this.$message.error(this.tagDialogType === 'add' ? '新增标签失败' : '更新标签失败')
|
||||
}
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 分页相关方法
|
||||
handleSizeChange(val) {
|
||||
this.tagQueryParams.pageSize = val
|
||||
},
|
||||
handleCurrentChange(val) {
|
||||
this.tagQueryParams.pageNum = val
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.app-container {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.group-container {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
height: calc(100vh - 140px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.group-header {
|
||||
padding: 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.group-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.group-search {
|
||||
padding: 10px 15px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
.group-menu {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.group-item {
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.group-actions {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
}
|
||||
|
||||
.group-item:hover .group-actions {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.group-actions i {
|
||||
margin-left: 8px;
|
||||
font-size: 16px;
|
||||
cursor: pointer;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.group-actions i:hover {
|
||||
color: #409EFF;
|
||||
}
|
||||
|
||||
.group-actions .el-icon-delete:hover {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.tag-container {
|
||||
background-color: #fff;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
|
||||
padding: 15px;
|
||||
min-height: calc(100vh - 140px);
|
||||
}
|
||||
|
||||
.tag-header {
|
||||
margin-bottom: 15px;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.header-title h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.tag-search {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.search-form {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.el-table {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.pagination-container {
|
||||
margin-top: 15px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.delete-btn {
|
||||
color: #F56C6C;
|
||||
}
|
||||
|
||||
.el-form-item-tip {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
line-height: 1;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
/* 确保弹窗样式一致 */
|
||||
.el-dialog {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.dialog-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
@ -10,14 +10,14 @@ module.exports = {
|
||||
warnings: false,
|
||||
errors: true
|
||||
},
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:8080',
|
||||
changeOrigin: true,
|
||||
pathRewrite: {
|
||||
'^/api': '/api'
|
||||
}
|
||||
}
|
||||
}
|
||||
// proxy: {
|
||||
// '/': {
|
||||
// target: 'http://192.168.137.3:8080/api',
|
||||
// changeOrigin: true,
|
||||
// pathRewrite: {
|
||||
// '^/api': '/api'
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
}
|
||||
}
|
BIN
pc/web (2).zip
BIN
pc/web (2).zip
Binary file not shown.
BIN
pc/web.zip
BIN
pc/web.zip
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user