修复已知问题
This commit is contained in:
5
.vscode/extensions.json
vendored
5
.vscode/extensions.json
vendored
@@ -1,3 +1,6 @@
|
|||||||
{
|
{
|
||||||
"recommendations": ["Vue.volar"]
|
"recommendations": [
|
||||||
|
"Vue.volar",
|
||||||
|
"google.geminicodeassist"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -1,8 +1,14 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@/*": [
|
||||||
|
"./src/*"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
@@ -1,194 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog v-model="dialogVisible" :title="isEditing ? '📝 编辑工单任务' : '✨ 创建新工单'" width="550px" class="modern-dialog"
|
|
||||||
destroy-on-close @close="handleClose">
|
|
||||||
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="compact-form">
|
|
||||||
|
|
||||||
<div class="form-section">
|
|
||||||
<div class="section-title">业务基础信息</div>
|
|
||||||
<el-form-item label="网站地址" prop="website">
|
|
||||||
<el-input v-model="form.website" placeholder="请输入网站 URL" clearable>
|
|
||||||
<template #prefix>
|
|
||||||
<el-icon><Link /></el-icon>
|
|
||||||
</template>
|
|
||||||
</el-input>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-section people-grid">
|
|
||||||
<div class="section-title column-full">权责分配</div>
|
|
||||||
|
|
||||||
<el-form-item label="客户单位 (外方)" prop="customer_id" class="client-item">
|
|
||||||
<el-select v-model="current_customer_id" placeholder="请选择单位" style="width: 100%" filterable @change="handleCustomerChange">
|
|
||||||
<el-option
|
|
||||||
v-for="item in customers"
|
|
||||||
:key="item.customer_id"
|
|
||||||
:label="item.company_name"
|
|
||||||
:value="item.customer_id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="运营人员 (外方)" prop="operator" class="client-item">
|
|
||||||
<el-select
|
|
||||||
v-model="form.operator"
|
|
||||||
placeholder="请选择人员"
|
|
||||||
style="width: 100%"
|
|
||||||
filterable
|
|
||||||
:disabled="!current_customer_id"
|
|
||||||
>
|
|
||||||
<el-option
|
|
||||||
v-for="item in filteredEmployees"
|
|
||||||
:key="item.employee_id"
|
|
||||||
:label="item.employee_name"
|
|
||||||
:value="item.employee_name"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="负责人 (我方)" prop="staff_id" class="owner-item column-full">
|
|
||||||
<el-select v-model="form.staff_id" placeholder="请选择我方负责人" style="width: 100%" filterable>
|
|
||||||
<el-option
|
|
||||||
v-for="item in staffs"
|
|
||||||
:key="item.id"
|
|
||||||
:label="item.name"
|
|
||||||
:value="item.id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-section">
|
|
||||||
<el-form-item label="当前进度">
|
|
||||||
<el-radio-group v-model="form.is_complete" class="modern-radio-group">
|
|
||||||
<el-radio-button label="未分配">未分配</el-radio-button>
|
|
||||||
<el-radio-button label="进行中">进行中</el-radio-button>
|
|
||||||
<el-radio-button label="已完成">已完成</el-radio-button>
|
|
||||||
<el-radio-button label="已完结">已完结</el-radio-button>
|
|
||||||
</el-radio-group>
|
|
||||||
</el-form-item>
|
|
||||||
|
|
||||||
<el-form-item label="备注说明" prop="remark">
|
|
||||||
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="填写相关的补充信息或注意事项..." resize="none" />
|
|
||||||
</el-form-item>
|
|
||||||
</div>
|
|
||||||
</el-form>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<div class="dialog-footer">
|
|
||||||
<el-button @click="dialogVisible = false" plain>取消</el-button>
|
|
||||||
<el-button type="primary" @click="handleSubmit" :loading="submitting" class="submit-btn">
|
|
||||||
{{ isEditing ? '保存修改' : '立即创建' }}
|
|
||||||
</el-button>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, reactive, watch, computed, nextTick } from 'vue'
|
|
||||||
import { Link } from '@element-plus/icons-vue'
|
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'submit'])
|
|
||||||
|
|
||||||
const props = defineProps({
|
|
||||||
modelValue: Boolean,
|
|
||||||
formData: Object,
|
|
||||||
customers: Array, // 外部客户单位列表
|
|
||||||
customerEmployees: Array, // 外部客户员工列表
|
|
||||||
staffs: Array, // 我方员工列表
|
|
||||||
isEditing: Boolean
|
|
||||||
})
|
|
||||||
|
|
||||||
const dialogVisible = ref(false)
|
|
||||||
const formRef = ref()
|
|
||||||
const submitting = ref(false)
|
|
||||||
const current_customer_id = ref(null) // 级联用的中间变量
|
|
||||||
|
|
||||||
const form = reactive({
|
|
||||||
website: '',
|
|
||||||
operator: '',
|
|
||||||
staff_id: null,
|
|
||||||
is_complete: "未分配",
|
|
||||||
remark: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
const rules = {
|
|
||||||
website: [{ required: true, message: '请输入网站地址', trigger: 'blur' }],
|
|
||||||
operator: [{ required: true, message: '请选择运营人员', trigger: 'change' }],
|
|
||||||
staff_id: [{ required: true, message: '请选择负责人', trigger: 'change' }]
|
|
||||||
}
|
|
||||||
|
|
||||||
// 核心逻辑:级联过滤
|
|
||||||
const filteredEmployees = computed(() => {
|
|
||||||
if (!current_customer_id.value) return []
|
|
||||||
return props.customerEmployees.filter(emp => emp.customer_id === current_customer_id.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 单位切换时清空人员
|
|
||||||
const handleCustomerChange = () => {
|
|
||||||
form.operator = ''
|
|
||||||
}
|
|
||||||
|
|
||||||
// 监听弹窗显隐与数据回显
|
|
||||||
watch(() => props.modelValue, (val) => {
|
|
||||||
dialogVisible.value = val
|
|
||||||
if (val) {
|
|
||||||
nextTick(() => {
|
|
||||||
resetForm()
|
|
||||||
if (props.isEditing && props.formData) {
|
|
||||||
Object.assign(form, props.formData)
|
|
||||||
// 回显逻辑:根据 operator 姓名反查所属 customer_id
|
|
||||||
const employee = props.customerEmployees.find(e => e.employee_name === props.formData.operator)
|
|
||||||
if (employee) {
|
|
||||||
current_customer_id.value = employee.customer_id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
watch(() => dialogVisible.value, (val) => emit('update:modelValue', val))
|
|
||||||
|
|
||||||
const resetForm = () => {
|
|
||||||
if (formRef.value) formRef.value.resetFields()
|
|
||||||
current_customer_id.value = null
|
|
||||||
Object.assign(form, {
|
|
||||||
website: '',
|
|
||||||
operator: '',
|
|
||||||
staff_id: null,
|
|
||||||
is_complete: "未分配",
|
|
||||||
remark: ''
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleClose = () => resetForm()
|
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
|
||||||
if (!formRef.value) return
|
|
||||||
try {
|
|
||||||
await formRef.value.validate()
|
|
||||||
submitting.value = true
|
|
||||||
emit('submit', { ...form })
|
|
||||||
} catch (e) {
|
|
||||||
console.log('表单校验失败')
|
|
||||||
} finally {
|
|
||||||
submitting.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
:deep(.modern-dialog) { border-radius: 12px; overflow: hidden; }
|
|
||||||
.form-section { margin-bottom: 24px; }
|
|
||||||
.section-title { font-size: 14px; font-weight: 600; color: #1e293b; margin-bottom: 12px; display: flex; align-items: center; }
|
|
||||||
.section-title::before { content: ""; width: 4px; height: 14px; background: #409eff; margin-right: 8px; border-radius: 2px; }
|
|
||||||
.people-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
|
||||||
.column-full { grid-column: span 2; }
|
|
||||||
:deep(.client-item .el-form-item__label) { color: #909399 !important; }
|
|
||||||
:deep(.owner-item .el-form-item__label) { color: #409eff !important; }
|
|
||||||
.modern-radio-group { box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); border-radius: 8px; overflow: hidden; }
|
|
||||||
.dialog-footer { padding-top: 10px; }
|
|
||||||
.submit-btn { padding-left: 24px; padding-right: 24px; font-weight: 600; }
|
|
||||||
:deep(.el-form-item) { margin-bottom: 18px; }
|
|
||||||
:deep(.el-form-item__label) { padding-bottom: 4px !important; font-size: 13px; }
|
|
||||||
</style>
|
|
||||||
@@ -4,7 +4,11 @@
|
|||||||
<div class="search-container">
|
<div class="search-container">
|
||||||
<div class="filter-main">
|
<div class="filter-main">
|
||||||
<el-form-item label="工单状态">
|
<el-form-item label="工单状态">
|
||||||
<el-radio-group v-model="localFilters.status" size="default" @change="handleSearch">
|
<el-radio-group
|
||||||
|
v-model="localFilters.status"
|
||||||
|
size="default"
|
||||||
|
@change="handleSearch"
|
||||||
|
>
|
||||||
<el-radio-button :label="null">全部</el-radio-button>
|
<el-radio-button :label="null">全部</el-radio-button>
|
||||||
<el-radio-button :label="0">未分配</el-radio-button>
|
<el-radio-button :label="0">未分配</el-radio-button>
|
||||||
<el-radio-button :label="1">进行中</el-radio-button>
|
<el-radio-button :label="1">进行中</el-radio-button>
|
||||||
@@ -23,12 +27,12 @@
|
|||||||
class="w-150"
|
class="w-150"
|
||||||
@change="handleSearch"
|
@change="handleSearch"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="op in operators"
|
v-for="op in operators"
|
||||||
:key="op.id"
|
:key="op.id"
|
||||||
:label="`${op.name} (${op.company})`"
|
:label="`${op.name} (${op.company})`"
|
||||||
:value="op.name"
|
:value="op.name"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -69,7 +73,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="filter-actions">
|
<div class="filter-actions">
|
||||||
<el-button type="primary" :icon="Search" @click="handleSearch" class="search-btn">
|
<el-button
|
||||||
|
type="primary"
|
||||||
|
:icon="Search"
|
||||||
|
@click="handleSearch"
|
||||||
|
class="search-btn"
|
||||||
|
>
|
||||||
执行筛选
|
执行筛选
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button @click="$emit('reset')" plain>重置条件</el-button>
|
<el-button @click="$emit('reset')" plain>重置条件</el-button>
|
||||||
@@ -78,8 +87,12 @@
|
|||||||
<el-button icon="Sort">排序</el-button>
|
<el-button icon="Sort">排序</el-button>
|
||||||
<template #dropdown>
|
<template #dropdown>
|
||||||
<el-dropdown-menu>
|
<el-dropdown-menu>
|
||||||
<el-dropdown-item @click="setSort('desc')">创建时间 (新→旧)</el-dropdown-item>
|
<el-dropdown-item @click="setSort('desc')"
|
||||||
<el-dropdown-item @click="setSort('asc')">创建时间 (旧→新)</el-dropdown-item>
|
>创建时间 (新→旧)</el-dropdown-item
|
||||||
|
>
|
||||||
|
<el-dropdown-item @click="setSort('asc')"
|
||||||
|
>创建时间 (旧→新)</el-dropdown-item
|
||||||
|
>
|
||||||
</el-dropdown-menu>
|
</el-dropdown-menu>
|
||||||
</template>
|
</template>
|
||||||
</el-dropdown>
|
</el-dropdown>
|
||||||
@@ -90,48 +103,48 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, watch } from 'vue'
|
import { reactive, watch } from "vue";
|
||||||
import { Search } from '@element-plus/icons-vue'
|
import { Search } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
filters: {
|
filters: {
|
||||||
type: Object,
|
type: Object,
|
||||||
required: true
|
required: true,
|
||||||
},
|
},
|
||||||
// 传入的是所有客户员工的名字数组 [ "张三", "李四", ... ]
|
// 传入的是所有客户员工的名字数组 [ "张三", "李四", ... ]
|
||||||
operators: {
|
operators: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
},
|
},
|
||||||
// 传入的是我方员工对象数组 [ {id: 1, name: "管理员"}, ... ]
|
// 传入的是我方员工对象数组 [ {id: 1, name: "管理员"}, ... ]
|
||||||
staffs: {
|
staffs: {
|
||||||
type: Array,
|
type: Array,
|
||||||
default: () => []
|
default: () => [],
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['filter-change', 'reset'])
|
const emit = defineEmits(["filter-change", "reset"]);
|
||||||
|
|
||||||
// 本地副本
|
// 本地副本
|
||||||
const localFilters = reactive({ ...props.filters })
|
const localFilters = reactive({ ...props.filters });
|
||||||
|
|
||||||
// 深度监听父组件 filters,用于同步重置状态
|
// 深度监听父组件 filters,用于同步重置状态
|
||||||
watch(
|
watch(
|
||||||
() => props.filters,
|
() => props.filters,
|
||||||
(newFilters) => {
|
(newFilters) => {
|
||||||
Object.assign(localFilters, newFilters)
|
Object.assign(localFilters, newFilters);
|
||||||
},
|
},
|
||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
);
|
||||||
|
|
||||||
const handleSearch = () => {
|
const handleSearch = () => {
|
||||||
emit('filter-change', { ...localFilters })
|
emit("filter-change", { ...localFilters });
|
||||||
}
|
};
|
||||||
|
|
||||||
const setSort = (order) => {
|
const setSort = (order) => {
|
||||||
localFilters.sort_order = order
|
localFilters.sort_order = order;
|
||||||
handleSearch()
|
handleSearch();
|
||||||
}
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -165,17 +178,38 @@ const setSort = (order) => {
|
|||||||
gap: 16px;
|
gap: 16px;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
.w-150 { width: 160px; }
|
.w-150 {
|
||||||
.search-input { width: 280px; }
|
width: 160px;
|
||||||
.divider { margin: 0 4px; color: #dcdfe6; }
|
}
|
||||||
.filter-actions { display: flex; gap: 10px; padding-top: 4px; }
|
.search-input {
|
||||||
.search-btn { padding: 12px 24px; font-weight: bold; letter-spacing: 1px; }
|
width: 280px;
|
||||||
|
}
|
||||||
|
.divider {
|
||||||
|
margin: 0 4px;
|
||||||
|
color: #dcdfe6;
|
||||||
|
}
|
||||||
|
.filter-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
padding-top: 4px;
|
||||||
|
}
|
||||||
|
.search-btn {
|
||||||
|
padding: 12px 24px;
|
||||||
|
font-weight: bold;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 1024px) {
|
@media (max-width: 1024px) {
|
||||||
.search-input { width: 200px; }
|
.search-input {
|
||||||
|
width: 200px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.search-container { flex-direction: column; }
|
.search-container {
|
||||||
.search-input { width: 100%; }
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.search-input {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
262
src/components/ticket/TicketForm.vue
Normal file
262
src/components/ticket/TicketForm.vue
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
<template>
|
||||||
|
<el-dialog v-model="dialogVisible" :title="isEditing ? '📝 编辑工单任务' : '✨ 创建新工单'" width="550px" class="modern-dialog"
|
||||||
|
destroy-on-close @close="handleClose">
|
||||||
|
<el-form ref="formRef" :model="form" :rules="rules" label-position="top" class="compact-form">
|
||||||
|
<div class="form-section">
|
||||||
|
<div class="section-title">业务基础信息</div>
|
||||||
|
<el-form-item label="网站地址" prop="website">
|
||||||
|
<el-input v-model="form.website" placeholder="请输入网站 URL" clearable>
|
||||||
|
<template #prefix>
|
||||||
|
<el-icon>
|
||||||
|
<Link />
|
||||||
|
</el-icon>
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section people-grid">
|
||||||
|
<div class="section-title column-full">权责分配</div>
|
||||||
|
|
||||||
|
<el-form-item label="客户单位 (外方)" prop="customer_id" class="client-item">
|
||||||
|
<el-select v-model="current_customer_id" placeholder="请选择单位" style="width: 100%" filterable
|
||||||
|
@change="handleCustomerChange">
|
||||||
|
<el-option v-for="item in customers" :key="item.customer_id" :label="item.company_name"
|
||||||
|
:value="item.customer_id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="运营人员 (外方)" prop="operator" class="client-item">
|
||||||
|
<el-select v-model="form.operator" placeholder="请选择人员" style="width: 100%" filterable
|
||||||
|
:disabled="!current_customer_id">
|
||||||
|
<el-option v-for="item in filteredEmployees" :key="item.employee_id" :label="item.employee_name"
|
||||||
|
:value="item.employee_name" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item v-if="isEditing" label="负责人 (我方)" prop="staff_id" class="owner-item column-full">
|
||||||
|
<el-select v-model="form.staff_id" placeholder="请选择我方负责人" style="width: 100%" filterable>
|
||||||
|
<el-option v-for="item in staffs" :key="item.id" :label="item.name" :value="item.id" />
|
||||||
|
</el-select>
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-section">
|
||||||
|
<el-form-item label="当前进度">
|
||||||
|
<el-radio-group v-model="form.is_complete" class="modern-radio-group">
|
||||||
|
<el-radio-button label="未分配">未分配</el-radio-button>
|
||||||
|
<el-radio-button label="进行中">进行中</el-radio-button>
|
||||||
|
<el-radio-button label="已完成">已完成</el-radio-button>
|
||||||
|
<el-radio-button label="已完结">已完结</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="备注说明" prop="remark">
|
||||||
|
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="填写相关的补充信息或注意事项..." resize="none" />
|
||||||
|
</el-form-item>
|
||||||
|
</div>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false" plain>取消</el-button>
|
||||||
|
<el-button type="primary" @click="handleSubmit" :loading="submitting" class="submit-btn">
|
||||||
|
{{ isEditing ? "保存修改" : "立即创建" }}
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive, watch, computed, nextTick } from "vue";
|
||||||
|
import { Link } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
|
const emit = defineEmits(["update:modelValue", "submit"]);
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
modelValue: Boolean,
|
||||||
|
formData: Object,
|
||||||
|
customers: Array, // 外部客户单位列表
|
||||||
|
customerEmployees: Array, // 外部客户员工列表
|
||||||
|
staffs: Array, // 我方员工列表
|
||||||
|
isEditing: Boolean,
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const formRef = ref();
|
||||||
|
const submitting = ref(false);
|
||||||
|
const current_customer_id = ref(null); // 级联用的中间变量
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
website: "",
|
||||||
|
operator: "",
|
||||||
|
staff_id: null,
|
||||||
|
is_complete: "未分配",
|
||||||
|
remark: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
// 使用 computed 动态切换校验逻辑
|
||||||
|
const rules = computed(() => {
|
||||||
|
const baseRules = {
|
||||||
|
website: [{ required: true, message: "请输入网站地址", trigger: "blur" }],
|
||||||
|
operator: [{ required: true, message: "请选择运营人员", trigger: "change" }],
|
||||||
|
};
|
||||||
|
|
||||||
|
// 仅在编辑模式下,将 staff_id 设为必填
|
||||||
|
if (props.isEditing) {
|
||||||
|
baseRules.staff_id = [{ required: true, message: "请选择负责人", trigger: "change" }];
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseRules;
|
||||||
|
});
|
||||||
|
|
||||||
|
// 核心逻辑:级联过滤
|
||||||
|
const filteredEmployees = computed(() => {
|
||||||
|
if (!current_customer_id.value) return [];
|
||||||
|
return props.customerEmployees.filter(
|
||||||
|
(emp) => emp.customer_id === current_customer_id.value
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// 单位切换时清空人员
|
||||||
|
const handleCustomerChange = () => {
|
||||||
|
form.operator = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听弹窗显隐与数据回显
|
||||||
|
watch(
|
||||||
|
() => props.modelValue,
|
||||||
|
(val) => {
|
||||||
|
dialogVisible.value = val;
|
||||||
|
if (val) {
|
||||||
|
nextTick(() => {
|
||||||
|
resetForm();
|
||||||
|
if (props.isEditing && props.formData) {
|
||||||
|
Object.assign(form, props.formData);
|
||||||
|
// 回显逻辑:根据 operator 姓名反查所属 customer_id
|
||||||
|
const employee = props.customerEmployees.find(
|
||||||
|
(e) => e.employee_name === props.formData.operator
|
||||||
|
);
|
||||||
|
if (employee) {
|
||||||
|
current_customer_id.value = employee.customer_id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => dialogVisible.value,
|
||||||
|
(val) => emit("update:modelValue", val)
|
||||||
|
);
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
if (formRef.value) formRef.value.resetFields();
|
||||||
|
current_customer_id.value = null;
|
||||||
|
Object.assign(form, {
|
||||||
|
website: "",
|
||||||
|
operator: "",
|
||||||
|
staff_id: null,
|
||||||
|
is_complete: "未分配",
|
||||||
|
remark: "",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleClose = () => resetForm();
|
||||||
|
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!formRef.value) return;
|
||||||
|
try {
|
||||||
|
await formRef.value.validate();
|
||||||
|
submitting.value = true;
|
||||||
|
|
||||||
|
// 复制一份表单数据进行处理
|
||||||
|
const submitData = { ...form };
|
||||||
|
|
||||||
|
// 如果是新增模式,强制将 staff_id 设为 null
|
||||||
|
if (!props.isEditing) {
|
||||||
|
submitData.staff_id = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit("submit", submitData);
|
||||||
|
} catch (e) {
|
||||||
|
console.log("表单校验失败", e);
|
||||||
|
} finally {
|
||||||
|
submitting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
:deep(.modern-dialog) {
|
||||||
|
border-radius: 12px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-section {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title::before {
|
||||||
|
content: "";
|
||||||
|
width: 4px;
|
||||||
|
height: 14px;
|
||||||
|
background: #409eff;
|
||||||
|
margin-right: 8px;
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.people-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.column-full {
|
||||||
|
grid-column: span 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.client-item .el-form-item__label) {
|
||||||
|
color: #909399 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.owner-item .el-form-item__label) {
|
||||||
|
color: #409eff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modern-radio-group {
|
||||||
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dialog-footer {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.submit-btn {
|
||||||
|
padding-left: 24px;
|
||||||
|
padding-right: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item) {
|
||||||
|
margin-bottom: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-form-item__label) {
|
||||||
|
padding-bottom: 4px !important;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -24,7 +24,7 @@ const router = createRouter({
|
|||||||
{
|
{
|
||||||
path: 'customers',
|
path: 'customers',
|
||||||
name: 'customers',
|
name: 'customers',
|
||||||
component: () => import('../views/CustomersView.vue'),
|
component: () => import('../views/CustomersManagement.vue'),
|
||||||
meta: { requiresAuth: true, role: 'admin' }
|
meta: { requiresAuth: true, role: 'admin' }
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,289 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="customers-container">
|
|
||||||
<h2>客户及员工管理系统</h2>
|
|
||||||
|
|
||||||
<div class="action-bar">
|
|
||||||
<div class="search-group">
|
|
||||||
<el-input v-model="searchQuery" placeholder="搜索客户单位" style="width: 250px" clearable />
|
|
||||||
</div>
|
|
||||||
<div class="button-group">
|
|
||||||
<el-button type="primary" :icon="Plus" @click="handleAddCustomer">新增客户单位</el-button>
|
|
||||||
<el-button type="success" :icon="User" @click="handleAddEmployee">新增员工</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<el-table :data="filteredData" border style="width: 100%; margin-top: 20px" v-loading="loading">
|
|
||||||
<el-table-column prop="customer_id" label="客户ID" width="100" />
|
|
||||||
<el-table-column prop="company_name" label="客户单位名称" />
|
|
||||||
|
|
||||||
<el-table-column label="员工管理" width="250" align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<span style="margin-right: 15px">
|
|
||||||
共 <b style="color: #409EFF">{{ getEmployeeCount(scope.row.customer_id) }}</b> 人
|
|
||||||
</span>
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
type="primary"
|
|
||||||
link
|
|
||||||
:icon="View"
|
|
||||||
@click="showEmployeeDetails(scope.row)"
|
|
||||||
>查看详情</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
|
|
||||||
<el-table-column label="操作" width="150" fixed="right">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button size="small" type="danger" plain @click="handleDeleteCustomer(scope.row)">删除单位</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
|
|
||||||
<el-dialog v-model="customerDialogVisible" title="新增客户单位" width="400px">
|
|
||||||
<el-form :model="customerForm" label-width="100px">
|
|
||||||
<el-form-item label="单位名称" required>
|
|
||||||
<el-input v-model="customerForm.company_name" placeholder="例如:方舟星河" />
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="customerDialogVisible = false">取消</el-button>
|
|
||||||
<el-button type="primary" @click="submitCustomer">确定</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<el-dialog v-model="employeeDialogVisible" title="新增客户员工" width="450px">
|
|
||||||
<el-form :model="employeeForm" label-width="100px">
|
|
||||||
<el-form-item label="员工姓名" required>
|
|
||||||
<el-input v-model="employeeForm.employee_name" placeholder="请输入姓名" />
|
|
||||||
</el-form-item>
|
|
||||||
<el-form-item label="所属客户" required>
|
|
||||||
<el-select v-model="employeeForm.customer_id" placeholder="请选择归属单位" style="width: 100%">
|
|
||||||
<el-option
|
|
||||||
v-for="item in customerList"
|
|
||||||
:key="item.customer_id"
|
|
||||||
:label="item.company_name"
|
|
||||||
:value="item.customer_id"
|
|
||||||
/>
|
|
||||||
</el-select>
|
|
||||||
</el-form-item>
|
|
||||||
</el-form>
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="employeeDialogVisible = false">取消</el-button>
|
|
||||||
<el-button type="success" @click="submitEmployee">保存员工</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
|
|
||||||
<el-dialog v-model="employeeDetailVisible" :title="`【${activeCustomerName}】员工名单`" width="500px">
|
|
||||||
<el-table :data="currentCustomerEmployees" border stripe>
|
|
||||||
<el-table-column prop="employee_id" label="员工ID" width="100" />
|
|
||||||
<el-table-column prop="employee_name" label="姓名" />
|
|
||||||
<el-table-column label="操作" width="100" align="center">
|
|
||||||
<template #default="scope">
|
|
||||||
<el-button
|
|
||||||
size="small"
|
|
||||||
type="danger"
|
|
||||||
link
|
|
||||||
@click="confirmDeleteEmployee(scope.row.employee_id)"
|
|
||||||
>移除</el-button>
|
|
||||||
</template>
|
|
||||||
</el-table-column>
|
|
||||||
</el-table>
|
|
||||||
</el-dialog>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue'
|
|
||||||
import { Plus, User, View } from '@element-plus/icons-vue'
|
|
||||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
|
||||||
import { customerApi } from '@/api/ticket' // 确保路径指向包含 addCustomerName 的 API 文件
|
|
||||||
|
|
||||||
// --- 状态数据 ---
|
|
||||||
const loading = ref(false)
|
|
||||||
const customerList = ref([])
|
|
||||||
const employeeList = ref([])
|
|
||||||
const searchQuery = ref('')
|
|
||||||
|
|
||||||
const employeeDetailVisible = ref(false)
|
|
||||||
const activeCustomerName = ref('')
|
|
||||||
const activeCustomerId = ref(null)
|
|
||||||
|
|
||||||
// --- 表单数据 ---
|
|
||||||
const customerDialogVisible = ref(false)
|
|
||||||
const customerForm = ref({ company_name: '' })
|
|
||||||
|
|
||||||
const employeeDialogVisible = ref(false)
|
|
||||||
const employeeForm = ref({ employee_name: '', customer_id: null })
|
|
||||||
|
|
||||||
// --- 核心方法 ---
|
|
||||||
|
|
||||||
// 1. 获取全局数据
|
|
||||||
const fetchCustomers = async () => {
|
|
||||||
loading.value = true
|
|
||||||
try {
|
|
||||||
const res = await customerApi.getCustomers()
|
|
||||||
if (res.code === 200) {
|
|
||||||
customerList.value = res.customers || []
|
|
||||||
employeeList.value = res.employees || []
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.msg || '数据加载失败')
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('API Error:', error)
|
|
||||||
ElMessage.error('无法连接到服务器')
|
|
||||||
} finally {
|
|
||||||
loading.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. 统计处理
|
|
||||||
const getEmployeeCount = (id) => {
|
|
||||||
return employeeList.value.filter(emp => emp.customer_id === id).length
|
|
||||||
}
|
|
||||||
|
|
||||||
const currentCustomerEmployees = computed(() => {
|
|
||||||
return employeeList.value.filter(emp => emp.customer_id === activeCustomerId.value)
|
|
||||||
})
|
|
||||||
|
|
||||||
const filteredData = computed(() => {
|
|
||||||
return customerList.value.filter(item =>
|
|
||||||
item.company_name.toLowerCase().includes(searchQuery.value.toLowerCase())
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
// 3. 客户管理逻辑
|
|
||||||
const handleAddCustomer = () => {
|
|
||||||
customerForm.value.company_name = ''
|
|
||||||
customerDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitCustomer = async () => {
|
|
||||||
if (!customerForm.value.company_name) return ElMessage.error('名称不能为空')
|
|
||||||
try {
|
|
||||||
const res = await customerApi.addCustomerName({
|
|
||||||
company_name: customerForm.value.company_name
|
|
||||||
})
|
|
||||||
console.log('返回数据',res)
|
|
||||||
if (res.code === 200) {
|
|
||||||
ElMessage.success('客户单位创建成功')
|
|
||||||
customerDialogVisible.value = false
|
|
||||||
fetchCustomers()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('创建失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleDeleteCustomer = (row) => {
|
|
||||||
ElMessageBox.confirm(
|
|
||||||
`确定删除单位【${row.company_name}】吗?这将同时移除该单位下所有员工,操作不可恢复!`,
|
|
||||||
'极其危险的操作',
|
|
||||||
{
|
|
||||||
confirmButtonText: '确定彻底删除',
|
|
||||||
cancelButtonText: '取消',
|
|
||||||
type: 'error',
|
|
||||||
// 让确定按钮看起来更显眼(红色)
|
|
||||||
confirmButtonClass: 'el-button--danger',
|
|
||||||
}
|
|
||||||
).then(async () => {
|
|
||||||
// 1. 开启全局或表格的 loading 状态(如果有的话)
|
|
||||||
loading.value = true;
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 2. 调用接口,注意参数要包装成对象,匹配后端 $this->request->param('customer_id')
|
|
||||||
const res = await customerApi.deleteCustomer(row.customer_id);
|
|
||||||
|
|
||||||
// 3. 根据返回结果进行反馈
|
|
||||||
if (res.code === 200) {
|
|
||||||
ElMessage.success({
|
|
||||||
message: `单位【${row.company_name}】及其员工已成功清理`,
|
|
||||||
showClose: true
|
|
||||||
});
|
|
||||||
// 4. 刷新列表数据
|
|
||||||
await fetchCustomers();
|
|
||||||
} else {
|
|
||||||
ElMessage.error(res.msg || '删除失败,请联系管理员');
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('删除客户出错:', error);
|
|
||||||
ElMessage.error('网络连接超时或服务器异常');
|
|
||||||
} finally {
|
|
||||||
// 5. 无论成功失败都关闭 loading
|
|
||||||
loading.value = false;
|
|
||||||
}
|
|
||||||
}).catch(() => {
|
|
||||||
// 用户点击取消,无需处理
|
|
||||||
console.log('用户取消删除');
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// 4. 员工管理逻辑
|
|
||||||
const handleAddEmployee = () => {
|
|
||||||
if (customerList.value.length === 0) return ElMessage.warning('请先创建客户单位')
|
|
||||||
employeeForm.value = { employee_name: '', customer_id: null }
|
|
||||||
employeeDialogVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
const submitEmployee = async () => {
|
|
||||||
if (!employeeForm.value.employee_name || !employeeForm.value.customer_id) {
|
|
||||||
return ElMessage.error('请填写员工姓名并选择所属单位')
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
// console.log('这个是姓名',employeeForm.value.employee_name)
|
|
||||||
// 注意:确保你的 api 文件中有 addEmployee 方法
|
|
||||||
const res = await customerApi.addEmployee({
|
|
||||||
emp_name: employeeForm.value.employee_name,
|
|
||||||
customer_id: employeeForm.value.customer_id
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res.code === 200) {
|
|
||||||
ElMessage.success('员工保存成功')
|
|
||||||
employeeDialogVisible.value = false
|
|
||||||
fetchCustomers()
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('保存失败')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const showEmployeeDetails = (row) => {
|
|
||||||
activeCustomerId.value = row.customer_id
|
|
||||||
activeCustomerName.value = row.company_name
|
|
||||||
employeeDetailVisible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
//删除所属员工
|
|
||||||
const confirmDeleteEmployee = (empId) => {
|
|
||||||
ElMessageBox.confirm('确定要从该单位移除此员工吗?', '提示', { type: 'warning' }).then(async () => {
|
|
||||||
try {
|
|
||||||
// console.log('删除员工的ID',empId);
|
|
||||||
const res = await customerApi.deleteEmployee(empId) // 后端删除员工接口
|
|
||||||
if (res.code == 200 || res.code == true){
|
|
||||||
ElMessage.success('操作成功')
|
|
||||||
employeeDetailVisible.value = false;
|
|
||||||
fetchCustomers();
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
ElMessage.error('删除失败')
|
|
||||||
}
|
|
||||||
}).catch(() => {})
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
fetchCustomers()
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.customers-container { padding: 30px; background-color: #f5f7fa; min-height: 100vh; }
|
|
||||||
h2 { margin-bottom: 25px; color: #303133; font-weight: 600; }
|
|
||||||
.action-bar {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
background: #fff;
|
|
||||||
padding: 15px 20px;
|
|
||||||
border-radius: 8px;
|
|
||||||
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.05);
|
|
||||||
}
|
|
||||||
.button-group .el-button { margin-left: 10px; }
|
|
||||||
:deep(.el-table__header) { color: #606266; font-weight: bold; }
|
|
||||||
</style>
|
|
||||||
@@ -93,9 +93,9 @@ import { Plus, Memo, Clock, Loading, CircleCheck, Checked } from '@element-plus/
|
|||||||
import { ticketApi ,customerApi} from '@/api/ticket'
|
import { ticketApi ,customerApi} from '@/api/ticket'
|
||||||
|
|
||||||
// 组件引入
|
// 组件引入
|
||||||
import TicketFilter from '@/components/TicketFilter.vue'
|
import TicketFilter from '@/components/ticket/TicketFilter.vue'
|
||||||
import TicketList from '@/components/TicketList.vue'
|
import TicketList from '@/components/ticket/TicketList.vue'
|
||||||
import TicketForm from '@/components/TicketForm.vue'
|
import TicketForm from '@/components/ticket/TicketForm.vue'
|
||||||
|
|
||||||
// --- 响应式状态 ---
|
// --- 响应式状态 ---
|
||||||
const tickets = ref([])
|
const tickets = ref([])
|
||||||
|
|||||||
@@ -25,8 +25,4 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user