|
|
@@ -75,14 +75,62 @@
|
|
|
</el-radio-group>
|
|
|
</el-form-item>
|
|
|
</el-col>
|
|
|
- <el-col :span="12"><el-form-item label="员工编码" prop="employee"><el-input v-model="form.employee" /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="库位"><el-input v-model="form.location" /></el-form-item></el-col>
|
|
|
- <el-col v-if="scopeMode === 'single'" :span="12"><el-form-item label="单物料编码"><el-input v-model="form.itemNum" /></el-form-item></el-col>
|
|
|
- <el-col v-if="scopeMode === 'range'" :span="12"><el-form-item label="物料编码起"><el-input v-model="form.itemNum1" /></el-form-item></el-col>
|
|
|
- <el-col v-if="scopeMode === 'range'" :span="12"><el-form-item label="物料编码止"><el-input v-model="form.itemNum2" /></el-form-item></el-col>
|
|
|
- <el-col :span="12"><el-form-item label="生产线"><el-input v-model="form.prodLine" /></el-form-item></el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="员工编码" prop="employee">
|
|
|
+ <el-select v-model="form.employee" filterable remote clearable :remote-method="remoteSearchEmployee"
|
|
|
+ :loading="employeeLoading" placeholder="输入编码/姓名搜索" style="width: 100%">
|
|
|
+ <el-option v-for="o in employeeOptions" :key="o.value" :label="o.label" :value="o.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="库位">
|
|
|
+ <el-select v-model="form.location" filterable clearable placeholder="请选择库位" style="width: 100%">
|
|
|
+ <el-option v-for="o in locationOptions" :key="o.value" :label="o.label" :value="o.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col v-if="scopeMode === 'single'" :span="12">
|
|
|
+ <el-form-item label="单物料编码">
|
|
|
+ <el-select v-model="form.itemNum" filterable remote clearable :remote-method="remoteSearchItemSingle"
|
|
|
+ :loading="materialLoading" placeholder="输入编码/名称搜索" style="width: 100%">
|
|
|
+ <el-option v-for="o in itemSingleOptions" :key="o.value" :label="o.label" :value="o.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col v-if="scopeMode === 'range'" :span="12">
|
|
|
+ <el-form-item label="物料编码起">
|
|
|
+ <el-select v-model="form.itemNum1" filterable remote clearable :remote-method="remoteSearchItemStart"
|
|
|
+ :loading="materialLoading" placeholder="输入编码/名称搜索" style="width: 100%">
|
|
|
+ <el-option v-for="o in itemStartOptions" :key="o.value" :label="o.label" :value="o.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col v-if="scopeMode === 'range'" :span="12">
|
|
|
+ <el-form-item label="物料编码止">
|
|
|
+ <el-select v-model="form.itemNum2" filterable remote clearable :remote-method="remoteSearchItemEnd"
|
|
|
+ :loading="materialLoading" placeholder="输入编码/名称搜索" style="width: 100%">
|
|
|
+ <el-option v-for="o in itemEndOptions" :key="o.value" :label="o.label" :value="o.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="12">
|
|
|
+ <el-form-item label="生产线">
|
|
|
+ <el-select v-model="form.prodLine" filterable remote clearable :remote-method="remoteSearchProdLine"
|
|
|
+ :loading="prodLineLoading" placeholder="输入编码/名称搜索" style="width: 100%">
|
|
|
+ <el-option v-for="o in prodLineOptions" :key="o.value" :label="o.label" :value="o.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
<el-col :span="12"><el-form-item label="员工类型"><el-input v-model="form.empType" /></el-form-item></el-col>
|
|
|
- <el-col :span="24"><el-form-item label="职责(原始值)"><el-input v-model="form.duty" placeholder="如 iss-po,rct-wo" /></el-form-item></el-col>
|
|
|
+ <el-col :span="24">
|
|
|
+ <el-form-item label="职责">
|
|
|
+ <el-select v-model="dutyList" multiple filterable clearable collapse-tags collapse-tags-tooltip
|
|
|
+ placeholder="请选择职责(可多选)" style="width: 100%">
|
|
|
+ <el-option v-for="o in dutyOptions" :key="o.value" :label="o.label" :value="o.value" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ </el-col>
|
|
|
<el-col v-if="false" :span="12"><el-form-item label="域编码"><el-input v-model="form.domainCode" /></el-form-item></el-col>
|
|
|
<el-col :span="12"><el-form-item label="自定义字段2"><el-input v-model="form.ufld2" /></el-form-item></el-col>
|
|
|
<el-col :span="24">
|
|
|
@@ -99,13 +147,27 @@
|
|
|
</template>
|
|
|
|
|
|
<script setup lang="ts" name="aidopS0WhEmpWorkDuty">
|
|
|
-import { computed, onMounted, reactive, ref, watch } from 'vue';
|
|
|
+import { computed, onMounted, reactive, ref, watch, type Ref } from 'vue';
|
|
|
import { useRoute } from 'vue-router';
|
|
|
import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus';
|
|
|
import AidopDemoShell from '../../components/AidopDemoShell.vue';
|
|
|
-import { s0EmpWorkDutiesApi, type S0EmpWorkDutyRow, type S0EmpWorkDutyUpsert } from '../api/s0WarehouseApi';
|
|
|
+import {
|
|
|
+ s0EmpWorkDutiesApi,
|
|
|
+ s0EmployeesApi,
|
|
|
+ s0LocationsApi,
|
|
|
+ type S0DutyOption,
|
|
|
+ type S0EmpWorkDutyRow,
|
|
|
+ type S0EmpWorkDutyUpsert,
|
|
|
+} from '../api/s0WarehouseApi';
|
|
|
+import { s0MfgProductionLinesApi, searchMaterialsForMfg } from '../api/s0ManufacturingApi';
|
|
|
import { loadOrgList, type OrgOption } from '../api/s0SalesApi';
|
|
|
|
|
|
+// 下拉/远程搜索选项通用形态:value=业务编码(实际保存值),label=展示文本
|
|
|
+interface Opt {
|
|
|
+ value: string;
|
|
|
+ label: string;
|
|
|
+}
|
|
|
+
|
|
|
const route = useRoute();
|
|
|
const pageTitle = computed(() => (route.meta?.title as string) || '物料职责维护');
|
|
|
const query = reactive({ keyword: '', domainCode: '', employee: '', location: '', itemNum: '', page: 1, pageSize: 20 });
|
|
|
@@ -121,6 +183,93 @@ const scopeMode = ref<'single' | 'range'>('range');
|
|
|
const emptyF = (): S0EmpWorkDutyUpsert => ({ companyRefId: undefined, factoryRefId: undefined, domainCode: '', employee: '', itemNum: '', itemNum1: '', itemNum2: '', location: '', prodLine: '', duty: '', empType: '', ufld2: '' });
|
|
|
const form = reactive<S0EmpWorkDutyUpsert>(emptyF());
|
|
|
|
|
|
+// 下拉/远程搜索选项集合:雇员/生产线/物料为远程搜索,库位/职责为进入页面一次性加载
|
|
|
+const employeeOptions = ref<Opt[]>([]);
|
|
|
+const locationOptions = ref<Opt[]>([]);
|
|
|
+const prodLineOptions = ref<Opt[]>([]);
|
|
|
+const itemSingleOptions = ref<Opt[]>([]);
|
|
|
+const itemStartOptions = ref<Opt[]>([]);
|
|
|
+const itemEndOptions = ref<Opt[]>([]);
|
|
|
+const dutyOptions = ref<S0DutyOption[]>([]);
|
|
|
+// 职责多选(数组)↔ 存储口径(逗号分隔字符串 form.duty)在 openEdit/submitForm 处 split/join
|
|
|
+const dutyList = ref<string[]>([]);
|
|
|
+const employeeLoading = ref(false);
|
|
|
+const prodLineLoading = ref(false);
|
|
|
+const materialLoading = ref(false);
|
|
|
+
|
|
|
+function toOpt(value?: string | null, label?: string | null): Opt {
|
|
|
+ const v = (value ?? '').trim();
|
|
|
+ const l = (label ?? '').trim();
|
|
|
+ return { value: v, label: l || v };
|
|
|
+}
|
|
|
+// 编辑历史数据时,当前编码可能不在最新 options 中:注入一条以保证正确回显、不丢值
|
|
|
+function seedOption(target: Ref<Opt[]>, value?: string | null, label?: string | null) {
|
|
|
+ const v = (value ?? '').trim();
|
|
|
+ if (!v) return;
|
|
|
+ if (!target.value.some((o) => o.value === v)) {
|
|
|
+ target.value = [toOpt(v, label), ...target.value];
|
|
|
+ }
|
|
|
+}
|
|
|
+function splitDuty(raw?: string | null): string[] {
|
|
|
+ return (raw ?? '').split(/[,;\s]+/).map((s) => s.trim()).filter(Boolean);
|
|
|
+}
|
|
|
+
|
|
|
+async function remoteSearchEmployee(keyword: string) {
|
|
|
+ if (!keyword) return;
|
|
|
+ employeeLoading.value = true;
|
|
|
+ try {
|
|
|
+ const data = await s0EmployeesApi.list({ page: 1, pageSize: 50, keyword });
|
|
|
+ employeeOptions.value = data.list.map((e) => toOpt(e.employee, `${e.employee} ${e.name ?? ''}`));
|
|
|
+ } catch {
|
|
|
+ /* 搜索失败保留已有选项,不清空 */
|
|
|
+ } finally {
|
|
|
+ employeeLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+async function remoteSearchProdLine(keyword: string) {
|
|
|
+ if (!keyword) return;
|
|
|
+ prodLineLoading.value = true;
|
|
|
+ try {
|
|
|
+ const data = await s0MfgProductionLinesApi.list({ page: 1, pageSize: 50, keyword });
|
|
|
+ prodLineOptions.value = data.list.map((l) => toOpt(l.line, `${l.line} ${l.describe ?? ''}`));
|
|
|
+ } catch {
|
|
|
+ /* 搜索失败保留已有选项,不清空 */
|
|
|
+ } finally {
|
|
|
+ prodLineLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+async function remoteSearchMaterial(keyword: string, target: Ref<Opt[]>) {
|
|
|
+ if (!keyword) return;
|
|
|
+ materialLoading.value = true;
|
|
|
+ try {
|
|
|
+ const data = await searchMaterialsForMfg({ keyword });
|
|
|
+ target.value = data.list.map((m) => toOpt(m.itemNum, `${m.itemNum} ${m.descr ?? ''}`));
|
|
|
+ } catch {
|
|
|
+ /* 搜索失败保留已有选项,不清空 */
|
|
|
+ } finally {
|
|
|
+ materialLoading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+const remoteSearchItemSingle = (kw: string) => remoteSearchMaterial(kw, itemSingleOptions);
|
|
|
+const remoteSearchItemStart = (kw: string) => remoteSearchMaterial(kw, itemStartOptions);
|
|
|
+const remoteSearchItemEnd = (kw: string) => remoteSearchMaterial(kw, itemEndOptions);
|
|
|
+
|
|
|
+async function loadLocationOptions() {
|
|
|
+ try {
|
|
|
+ const data = await s0LocationsApi.options({ limit: 500 });
|
|
|
+ locationOptions.value = data.map((o) => toOpt(o.value, o.label));
|
|
|
+ } catch {
|
|
|
+ locationOptions.value = [];
|
|
|
+ }
|
|
|
+}
|
|
|
+async function loadDutyOptions() {
|
|
|
+ try {
|
|
|
+ dutyOptions.value = await s0EmpWorkDutiesApi.dutyOptions();
|
|
|
+ } catch {
|
|
|
+ dutyOptions.value = [];
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
// 单甲方私有云口径:公司/工厂不在前端启用,统一注入全局默认组织(不依赖登录账号 tenant_id)
|
|
|
const DEFAULT_COMPANY_REF_ID = '1329900200001';
|
|
|
const DEFAULT_FACTORY_REF_ID = '1329900200002';
|
|
|
@@ -159,12 +308,33 @@ async function loadList() {
|
|
|
} catch { rows.value = []; total.value = 0; } finally { loading.value = false; }
|
|
|
}
|
|
|
function resetQuery() { Object.assign(query, { keyword: '', domainCode: '', employee: '', location: '', itemNum: '', page: 1 }); void loadList(); }
|
|
|
-function resetForm() { editingId.value = null; scopeMode.value = 'range'; Object.assign(form, emptyF()); formRef.value?.clearValidate(); }
|
|
|
+function resetForm() {
|
|
|
+ editingId.value = null;
|
|
|
+ scopeMode.value = 'range';
|
|
|
+ Object.assign(form, emptyF());
|
|
|
+ dutyList.value = [];
|
|
|
+ // 清空远程搜索选项(库位/职责为全局加载,保留)
|
|
|
+ employeeOptions.value = [];
|
|
|
+ prodLineOptions.value = [];
|
|
|
+ itemSingleOptions.value = [];
|
|
|
+ itemStartOptions.value = [];
|
|
|
+ itemEndOptions.value = [];
|
|
|
+ formRef.value?.clearValidate();
|
|
|
+}
|
|
|
function openCreate() { resetForm(); dialogTitle.value = '新增物料职责'; dialogVisible.value = true; }
|
|
|
function openEdit(row: S0EmpWorkDutyRow) {
|
|
|
resetForm(); editingId.value = row.id; dialogTitle.value = `编辑物料职责`;
|
|
|
scopeMode.value = row.itemNum ? 'single' : 'range';
|
|
|
Object.assign(form, { companyRefId: row.companyRefId, factoryRefId: row.factoryRefId, domainCode: row.domainCode ?? '', employee: row.employee, itemNum: row.itemNum ?? '', itemNum1: row.itemNum1 ?? '', itemNum2: row.itemNum2 ?? '', location: row.location ?? '', prodLine: row.prodLine ?? '', duty: row.duty ?? '', empType: row.empType ?? '', ufld2: row.ufld2 ?? '' });
|
|
|
+ // 职责字符串 → 数组(存储口径不变,仅前端展示为多选)
|
|
|
+ dutyList.value = splitDuty(row.duty);
|
|
|
+ // 回显:注入当前编码的选项,保证历史值不在最新 options 时仍显示、不丢值
|
|
|
+ seedOption(employeeOptions, row.employee, `${row.employee} ${row.employeeName ?? ''}`);
|
|
|
+ seedOption(locationOptions, row.location, row.locationName);
|
|
|
+ seedOption(prodLineOptions, row.prodLine, row.lineName);
|
|
|
+ seedOption(itemSingleOptions, row.itemNum, row.itemNum);
|
|
|
+ seedOption(itemStartOptions, row.itemNum1, row.itemNum1);
|
|
|
+ seedOption(itemEndOptions, row.itemNum2, row.itemNum2);
|
|
|
dialogVisible.value = true;
|
|
|
}
|
|
|
async function submitForm() {
|
|
|
@@ -172,7 +342,8 @@ async function submitForm() {
|
|
|
applyDefaultOrg();
|
|
|
saving.value = true;
|
|
|
try {
|
|
|
- const payload: S0EmpWorkDutyUpsert = { ...form };
|
|
|
+ // 职责数组 → 逗号分隔字符串(存储口径不变;Up-Shelf 等大小写原样保留)
|
|
|
+ const payload: S0EmpWorkDutyUpsert = { ...form, duty: dutyList.value.join(',') };
|
|
|
if (scopeMode.value === 'single') {
|
|
|
if (!payload.itemNum?.trim()) { ElMessage.warning('请填写单物料编码'); return; }
|
|
|
payload.itemNum = payload.itemNum.trim();
|
|
|
@@ -199,7 +370,7 @@ async function loadOrgOptions() {
|
|
|
factoryOptions.value = factories.filter((item) => item.id && item.id !== '0');
|
|
|
}
|
|
|
|
|
|
-onMounted(() => { void loadList(); void loadOrgOptions(); });
|
|
|
+onMounted(() => { void loadList(); void loadOrgOptions(); void loadLocationOptions(); void loadDutyOptions(); });
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|