| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666 |
- <template>
- <el-dialog
- v-model="dialogVisible"
- title="其他情况"
- width="704px"
- :before-close="handleClose"
- align-center
- >
- <div class="dialog-content">
- <div class="left">
- <div class="description-text">
- 如存在不满足其它规则的情况下,则进入该情况,请选择该情况下要执行的动作
- </div>
- <div class="section-title">将执行以下动作</div>
- <el-form
- ref="formRef"
- :model="formData"
- label-width="auto"
- class="action-form"
- >
- <el-form-item
- v-for="(item, index) in formData.actions"
- :key="item.keyid"
- :prop="`actions.${index}`"
- :error="item.actionJson.duplicateError"
- >
- <el-select
- v-model="item.actionCode"
- placeholder="请选择动作"
- style="width: 276px; margin-right: 9px"
- @change="(val) => handleActionChange(val, index)"
- @blur="() => checkDuplicate(index)"
- >
- <el-option
- v-for="dictItem in getAvailableActions(index)"
- :key="dictItem.value"
- :value="dictItem.value"
- :label="dictItem.label"
- />
- </el-select>
- <img
- src="@/assets/images/delete.png"
- alt="删除"
- style="width: 18px; height: 18px; cursor: pointer"
- @click="deleteItem(index)"
- v-if="formData.actions.length > 1"
- />
- </el-form-item>
- </el-form>
- <el-button type="text" class="add-action-btn" @click="addItem">
- <el-icon><Plus /></el-icon>添加动作
- </el-button>
- </div>
- <div class="right">
- <div class="right-section">
- <div class="right-title">执行动作</div>
- <div class="action-label" v-if="lastActionLabel">
- {{ lastActionLabel }}
- </div>
- </div>
- <!-- 指定协议 -->
- <div
- v-if="
- formData.actions.length > 0 &&
- lastActionLabel === '报价失败指定协议'
- "
- style="margin-top: 20px"
- >
- <div
- style="
- font-weight: 600;
- font-size: 16px;
- margin-bottom: 4px;
- color: #333333;
- "
- >
- 选择报价协议
- </div>
- <div style="font-size: 14px; color: #666666; margin-bottom: 12px">
- 不满足所有规则的车辆报价,将使用该协议报价
- </div>
- <el-select
- v-model="
- formData.actions[formData.actions.length - 1].actionJson
- .agreementId
- "
- placeholder=""
- style="width: 304px"
- :rules="[
- {
- required: lastActionLabel === '报价失败指定协议',
- message: '请选择报价协议',
- trigger: 'blur',
- },
- ]"
- >
- <el-option
- v-for="item in agreementList"
- :key="item.id"
- :label="item.agreementName"
- :value="item.id"
- />
- </el-select>
- </div>
- <!-- 终止报价 -->
- <div
- v-if="formData.actions.length > 0 && lastActionLabel === '终止报价'"
- style="margin-top: 20px"
- >
- <div
- style="
- font-weight: 600;
- font-size: 16px;
- margin-bottom: 4px;
- color: #333333;
- "
- >
- 提示文案
- </div>
- <div style="font-size: 14px; color: #666666; margin-bottom: 12px">
- 设置终止该车辆报价的提示文案
- </div>
- <el-input
- v-model="
- formData.actions[formData.actions.length - 1].actionJson.message
- "
- style="width: 100%"
- :rows="4"
- type="textarea"
- placeholder="请输入提示文案"
- maxlength="500"
- show-word-limit
- />
- </div>
- <!-- 补录订单 -->
- <div
- v-if="formData.actions.length > 0 && lastActionLabel === '补录订单'"
- >
- <div style="font-size: 14px; color: #666666; margin-top: 10px">
- 终止报价,将报价信息写入补录订单内,生成一笔暂存的补录订单。
- </div>
- </div>
- <!-- 发送通知消息 -->
- <div
- v-if="
- formData.actions.length > 0 && lastActionLabel === '发送通知消息'
- "
- style="margin-top: 20px"
- >
- <div
- style="
- font-weight: 600;
- font-size: 16px;
- margin-bottom: 4px;
- color: #333333;
- "
- >
- 接收人
- </div>
- <div style="font-size: 14px; color: #666666; margin-bottom: 12px">
- 发送通知消息给接收人
- </div>
- <el-radio-group
- v-model="
- formData.actions[formData.actions.length - 1].actionJson.type
- "
- @change="handleTypeChange"
- style="
- display: flex;
- flex-direction: column;
- align-items: start;
- gap: 12px;
- "
- >
- <el-radio :value="1">业务员的管理人</el-radio>
- <el-radio :value="2">
- 指定人员
- <el-select
- v-model="
- formData.actions[formData.actions.length - 1].actionJson
- .user_id
- "
- placeholder="请选择人员"
- style="width: 204px; margin-left: 20px"
- clearable
- :disabled="
- formData.actions[formData.actions.length - 1].actionJson
- .type !== 2
- "
- >
- <el-option
- v-for="item in followerList"
- :key="item.id"
- :label="item.name"
- :value="item.id"
- />
- </el-select>
- </el-radio>
- </el-radio-group>
- <div style="font-size: 14px; margin: 20px 0px 10px 0px">标题</div>
- <div>
- <el-input
- v-model="
- formData.actions[formData.actions.length - 1].actionJson.title
- "
- style="width: 100%"
- placeholder="请输入标题"
- maxlength="50"
- show-word-limit
- />
- </div>
- <div style="font-size: 14px; margin: 20px 0px 10px 0px">内容</div>
- <el-input
- v-model="
- formData.actions[formData.actions.length - 1].actionJson.content
- "
- style="width: 100%"
- :rows="4"
- type="textarea"
- placeholder="请输入通知内容"
- maxlength="500"
- show-word-limit
- />
- </div>
- </div>
- </div>
- <template #footer>
- <div class="dialog-footer">
- <el-button class="border-[#0083FF] color-[#0083FF]" plain @click="(dialogVisible = false), emit('close')"
- >取消</el-button
- >
- <el-button type="primary" @click="handleSave">保存</el-button>
- </div>
- </template>
- </el-dialog>
- </template>
- <script lang="ts" setup>
- import { ref, onMounted, ElRef, watch, computed, defineProps } from "vue";
- import { ElMessageBox, ElForm, ElMessage } from "element-plus";
- import { Plus } from "@element-plus/icons-vue";
- import api from "@/api";
- import { getDictItems } from "@/api/dict";
- // 对话框显示状态
- const dialogVisible = ref(true);
- //协议列表,调度id
- const props = defineProps<{
- companyId: any;
- }>();
- // 表单引用
- const formRef = ref<ElRef<ElForm>>();
- const agreementList = ref([]); //协议列表
- // 表单数据:actions数组存储所有动作,每个动作包含完整字段
- const formData = ref({
- actions: [
- {
- companyId: props.companyId,
- keyid: generateId(),
- actionCode: "",
- actionJson: {
- agreementId: "", // 动作ID(关联字典)
- message: "", // 终止报价的提示文案
- type: null, // 通知接收人类型(1=业务员管理人,2=指定人员)
- content: "", // 通知内容
- user_id: null, // 指定人员ID
- title: "",
- duplicateError: "", // 重复选择错误提示
- },
- },
- ],
- });
- // 字典数据(动作列表,如“终止报价”“发送通知消息”等)
- const dictionaries = ref([]);
- // 从 dictionaries 数据中获取“指定协议”“终止报价”对应的 id
- const MUTUALLY_EXCLUSIVE_ACTIONS = {
- agreement: "414", // 假设“指定协议”的字典ID
- terminate: "415", // 假设“终止报价”的字典ID
- };
- // 最后一项动作的标签(用于右侧配置区显示)
- const lastActionLabel = ref("");
- // 后线人员列表(用于“指定人员”选择)
- const followerList = ref([]);
- /**
- * 生成唯一ID(避免列表key重复)
- * @returns 唯一ID字符串
- */
- function generateId(): string {
- return Date.now().toString(36) + Math.random().toString(36).substr(2, 5);
- }
- /**
- * 计算属性:根据当前已选动作,生成“可选动作列表”(排除其他行已选,保留当前行)
- */
- const getAvailableActions = computed(() => {
- return (currentIndex: number) => {
- // 收集其他行已选的动作值(dictItem.value)
- const selectedValues = formData.value.actions
- .filter((_, idx) => idx !== currentIndex)
- .map((item) => item.actionCode)
- .filter(Boolean);
- // 筛选字典:排除已被其他行选择的动作(用value字段判断)
- return dictionaries.value.filter((dictItem) => !selectedValues.includes(dictItem.value));
- };
- });
- /**
- * 更新最后一项动作的标签(右侧配置区基于此显示对应内容)
- */
- function updateLastAction() {
- if (formData.value.actions.length === 0) return;
- // 获取最后一个动作的选中值(即选择框绑定的dictItem.value)
- const lastAction = formData.value.actions[formData.value.actions.length - 1];
- // 关键修复:用字典项的value字段匹配actionCode
- const matchedAction = dictionaries.value.find(
- (dictItem: any) => dictItem.value === lastAction.actionCode
- );
- lastActionLabel.value = matchedAction ? matchedAction.label : "";
- // 重置无关字段(保持不变)
- if (lastActionLabel.value !== "发送通知消息") {
- lastAction.actionJson.type = null;
- lastAction.actionJson.user_id = null;
- lastAction.actionJson.content = "";
- lastAction.actionJson.title = "";
- }
- }
- /**
- * 校验单个动作是否重复(并标记错误)
- * @param index 当前动作行索引
- */
- const checkDuplicate = (index: number) => {
- const currentAction = formData.value.actions[index];
- if (!currentAction.actionCode) {
- currentAction.actionJson.duplicateError = "";
- return;
- }
- // 统计当前动作值(value)的出现次数
- const duplicateCount = formData.value.actions.filter(
- (item) => item.actionCode === currentAction.actionCode
- ).length;
- if (duplicateCount > 1) {
- // 用value字段查找动作标签
- const actionLabel = dictionaries.value.find(
- (dictItem) => dictItem.value === currentAction.actionCode
- )?.label;
- currentAction.actionJson.duplicateError = `“${actionLabel}”已选择,请更换其他动作`;
- } else {
- currentAction.actionJson.duplicateError = "";
- }
- };
- /**
- * 处理动作选择变化(立即校验重复)
- * @param val 当前选择的动作ID
- * @param index 当前动作行索引
- */
- const handleActionChange = (val: string, index: number) => {
- const { agreement, terminate } = MUTUALLY_EXCLUSIVE_ACTIONS;
- // 用选择框的value(val)判断是否为互斥动作
- const isMutualAction = [agreement, terminate].includes(val);
- if (isMutualAction) {
- const otherMutualAction = val === agreement ? terminate : agreement;
- // 检查其他行是否包含互斥动作的value
- const hasOtherMutual = formData.value.actions.some(
- (item, idx) => idx !== index && item.actionCode === otherMutualAction
- );
- if (hasOtherMutual) {
- ElMessage.error("指定报价协议 与 终止报价,只能选择其中1个");
- formData.value.actions[index].actionCode = "";
- formData.value.actions[index].actionJson.duplicateError = "";
- return;
- }
- }
- checkDuplicate(index);
- formData.value.actions.forEach((_, idx) => idx !== index && checkDuplicate(idx));
- updateLastAction();
- };
- /**
- * 批量校验所有动作是否重复(用于保存前最终校验)
- * @returns boolean 无重复返回true,有重复返回false
- */
- const checkAllDuplicate = () => {
- let hasDuplicate = false;
- // 1. 收集所有已选动作ID
- const selectedIds = formData.value.actions
- .map((item) => item.actionCode)
- .filter(Boolean);
- // 2. 检查是否有重复ID
- const uniqueIds = [...new Set(selectedIds)];
- if (selectedIds.length !== uniqueIds.length) {
- hasDuplicate = true;
- // 3. 标记所有重复行的错误提示
- formData.value.actions.forEach((item, index) => {
- if (
- item.actionCode &&
- selectedIds.filter((id) => id === item.actionCode).length > 1
- ) {
- checkDuplicate(index); // 复用单个校验逻辑,标记错误
- }
- });
- // 4. 弹出全局提示,引导用户修改
- ElMessage.warning("存在重复选择的动作,请先修改后再保存");
- }
- return !hasDuplicate;
- };
- /**
- * 添加新的动作行
- */
- const addItem = () => {
- formData.value.actions.push({
- keyid: generateId(),
- actionCode: "",
- companyId: props.companyId,
- actionJson: {
- agreementId: "",
- message: "",
- type: null,
- content: "",
- user_id: null,
- title: "",
- duplicateError: "",
- },
- });
- // 添加后更新最后一项动作的显示
- updateLastAction();
- };
- /**
- * 删除指定索引的动作行(至少保留1行)
- * @param index 要删除的行索引
- */
- const deleteItem = (index: number) => {
- if (formData.value.actions.length <= 1) {
- ElMessageBox.alert("至少需要保留一个动作", "提示", {
- confirmButtonText: "确定",
- });
- return;
- }
- // 1. 记录被删除行的动作ID(用于后续校验)
- const deletedActionId = formData.value.actions[index].actionCode;
- // 2. 删除指定行
- formData.value.actions.splice(index, 1);
- // 3. 重新校验剩余行:若删除的是重复ID,清除其他行的重复标记
- if (deletedActionId) {
- formData.value.actions.forEach((_, idx) => checkDuplicate(idx));
- }
- // 原有逻辑:更新右侧配置区
- updateLastAction();
- };
- /**
- * 处理通知接收人类型变化:选择1时清空指定人员
- * @param value 接收人类型(1=业务员管理人,2=指定人员)
- */
- const handleTypeChange = (value: number) => {
- const lastAction = formData.value.actions[formData.value.actions.length - 1];
- // 选择“业务员的管理人”(value=1)时,清空指定人员选择
- if (value === 1) {
- lastAction.actionJson.user_id = null;
- }
- };
- /**
- * 处理对话框关闭(未保存时提示)
- * @param done 关闭回调函数
- */
- const handleClose = (done: () => void) => {
- emit("close");
- };
- /**
- * 获取后线人员列表(接口请求)
- */
- const getUserList = async () => {
- try {
- const res = await api.dispatchApi.getUserList({ isMember: "1" });
- return res?.data || []; // 按实际接口结构调整数据路径
- } catch (error) {
- console.error("获取后线人员列表失败:", error);
- return [];
- }
- };
- // 获取报价协议列表
- const getByCompanyId = async () => {
- try {
- const res = await api.dispatchApi.getByCompanyIdt(props.companyId);
- agreementList.value = res.data;
- } catch (error) {
- console.error("获取报价协议列表失败:", error);
- }
- };
- /**
- * 处理保存操作(含完整验证逻辑)
- */
- const handleSave = async () => {
- // 校验报价协议是否选择
- const hasEmptyAgreement = formData.value.actions.some((item) => {
- const matchedAction = dictionaries.value.find(
- (dictItem) => dictItem.id === item.actionCode
- );
- return (
- matchedAction?.label === "报价失败指定协议" && !item.actionJson.agreementId
- );
- });
- if (hasEmptyAgreement) {
- ElMessage.error("请选择报价协议");
- return;
- }
- // 校验所有动作是否重复
- if (!checkAllDuplicate()) {
- return;
- }
- // 校验通过,执行保存逻辑
- try {
- const saveRes = await api.dispatchApi.addAgreementDispatchAction(
- formData.value.actions
- );
- if (saveRes.code === 200) {
- ElMessage.success("保存成功");
- emit("close");
- }
- } catch (error) {
- console.error("保存失败:", error);
- ElMessage.error("保存失败,请稍后重试");
- }
- };
- const emit = defineEmits(["close"]);
- /**
- * 组件挂载时加载初始化数据(字典+人员列表)
- */
- onMounted(async () => {
- const [dictData, userList] = await Promise.all([
- getDictItems({ dictCode: "dispatch_after_action" }), // 动作字典
- getUserList(), // 后线人员列表
- ]);
- dictionaries.value = dictData || [];
- followerList.value = userList;
- // formData.value.companyId = props.companyId;
- console.log( props.companyId);
- });
- /**
- * 监听动作列表变化,自动更新最后一项动作的显示
- */
- watch(
- () => formData.value.actions,
- () => updateLastAction(),
- { deep: true }
- );
- </script>
- <style lang="scss" scoped>
- .dialog-content {
- display: flex;
- width: 100%;
- gap: 24px;
- }
- .left {
- width: 50%;
- border-right: 1px solid #eeeeee;
- padding-right: 8px;
- }
- .right {
- width: 50%;
- padding-left: 8px;
- }
- .right-section {
- width: 100%;
- }
- .right-title {
- font-size: 16px;
- color: #333;
- font-weight: 600;
- margin-bottom: 10px;
- }
- .action-label {
- font-size: 14px;
- color: #666;
- width: 100%;
- border-radius: 4px;
- border: 1px solid #e5e5e5;
- height: 36px;
- line-height: 36px;
- padding: 0 12px;
- margin-top: 10px;
- background-color: #fff;
- }
- .description-text {
- font-size: 14px;
- color: #666666;
- line-height: 1.5;
- margin-bottom: 20px;
- }
- .section-title {
- font-size: 16px;
- margin-top: 8px;
- margin-bottom: 16px;
- color: #333;
- font-weight: 500;
- }
- .action-form {
- margin-bottom: 16px;
- }
- .add-action-btn {
- margin-top: 8px;
- color: #409eff;
- &:hover {
- color: #66b1ff;
- }
- }
- ::v-deep .el-radio {
- display: flex;
- align-items: center;
- }
- ::v-deep .el-form-item {
- margin-bottom: 12px;
- }
- .dialog-footer {
- display: flex;
- justify-content: flex-end;
- gap: 12px;
- }
- </style>
|