修复已知问题
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": {
|
||||
"baseUrl": ".",
|
||||
"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="filter-main">
|
||||
<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="0">未分配</el-radio-button>
|
||||
<el-radio-button :label="1">进行中</el-radio-button>
|
||||
@@ -23,12 +27,12 @@
|
||||
class="w-150"
|
||||
@change="handleSearch"
|
||||
>
|
||||
<el-option
|
||||
v-for="op in operators"
|
||||
:key="op.id"
|
||||
:label="`${op.name} (${op.company})`"
|
||||
:value="op.name"
|
||||
/>
|
||||
<el-option
|
||||
v-for="op in operators"
|
||||
:key="op.id"
|
||||
:label="`${op.name} (${op.company})`"
|
||||
:value="op.name"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
|
||||
@@ -69,7 +73,12 @@
|
||||
</div>
|
||||
|
||||
<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 @click="$emit('reset')" plain>重置条件</el-button>
|
||||
@@ -78,8 +87,12 @@
|
||||
<el-button icon="Sort">排序</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item @click="setSort('desc')">创建时间 (新→旧)</el-dropdown-item>
|
||||
<el-dropdown-item @click="setSort('asc')">创建时间 (旧→新)</el-dropdown-item>
|
||||
<el-dropdown-item @click="setSort('desc')"
|
||||
>创建时间 (新→旧)</el-dropdown-item
|
||||
>
|
||||
<el-dropdown-item @click="setSort('asc')"
|
||||
>创建时间 (旧→新)</el-dropdown-item
|
||||
>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
@@ -90,48 +103,48 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, watch } from 'vue'
|
||||
import { Search } from '@element-plus/icons-vue'
|
||||
import { reactive, watch } from "vue";
|
||||
import { Search } from "@element-plus/icons-vue";
|
||||
|
||||
const props = defineProps({
|
||||
filters: {
|
||||
type: Object,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
// 传入的是所有客户员工的名字数组 [ "张三", "李四", ... ]
|
||||
operators: {
|
||||
type: Array,
|
||||
default: () => []
|
||||
default: () => [],
|
||||
},
|
||||
// 传入的是我方员工对象数组 [ {id: 1, name: "管理员"}, ... ]
|
||||
staffs: {
|
||||
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,用于同步重置状态
|
||||
watch(
|
||||
() => props.filters,
|
||||
(newFilters) => {
|
||||
Object.assign(localFilters, newFilters)
|
||||
Object.assign(localFilters, newFilters);
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
);
|
||||
|
||||
const handleSearch = () => {
|
||||
emit('filter-change', { ...localFilters })
|
||||
}
|
||||
emit("filter-change", { ...localFilters });
|
||||
};
|
||||
|
||||
const setSort = (order) => {
|
||||
localFilters.sort_order = order
|
||||
handleSearch()
|
||||
}
|
||||
localFilters.sort_order = order;
|
||||
handleSearch();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -165,17 +178,38 @@ const setSort = (order) => {
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
}
|
||||
.w-150 { width: 160px; }
|
||||
.search-input { 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; }
|
||||
.w-150 {
|
||||
width: 160px;
|
||||
}
|
||||
.search-input {
|
||||
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) {
|
||||
.search-input { width: 200px; }
|
||||
.search-input {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
.search-container { flex-direction: column; }
|
||||
.search-input { width: 100%; }
|
||||
.search-container {
|
||||
flex-direction: column;
|
||||
}
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</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',
|
||||
name: 'customers',
|
||||
component: () => import('../views/CustomersView.vue'),
|
||||
component: () => import('../views/CustomersManagement.vue'),
|
||||
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 TicketFilter from '@/components/TicketFilter.vue'
|
||||
import TicketList from '@/components/TicketList.vue'
|
||||
import TicketForm from '@/components/TicketForm.vue'
|
||||
import TicketFilter from '@/components/ticket/TicketFilter.vue'
|
||||
import TicketList from '@/components/ticket/TicketList.vue'
|
||||
import TicketForm from '@/components/ticket/TicketForm.vue'
|
||||
|
||||
// --- 响应式状态 ---
|
||||
const tickets = ref([])
|
||||
|
||||
@@ -25,8 +25,4 @@ export default defineConfig({
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user