|
|
@@ -0,0 +1,181 @@
|
|
|
+<template>
|
|
|
+ <el-dialog :model-value="modelValue" title="检验员调配(组织选人)" width="840px" @update:model-value="(v: boolean) => emit('update:modelValue', v)" @open="onOpen">
|
|
|
+ <div class="picker">
|
|
|
+ <div class="tree-pane">
|
|
|
+ <OrgTree :tenant-id="tenantId" @node-click="onNodeClick" />
|
|
|
+ </div>
|
|
|
+ <div class="user-pane">
|
|
|
+ <el-form :inline="true" :model="query" class="mb8" @submit.prevent>
|
|
|
+ <el-form-item label="姓名"><el-input v-model="query.realName" clearable style="width: 120px" @keyup.enter="doSearch" /></el-form-item>
|
|
|
+ <el-form-item label="账号"><el-input v-model="query.account" clearable style="width: 120px" @keyup.enter="doSearch" /></el-form-item>
|
|
|
+ <el-form-item><el-button type="primary" @click="doSearch">查询</el-button></el-form-item>
|
|
|
+ </el-form>
|
|
|
+ <el-table :data="rows" v-loading="loading" border stripe size="small" height="340" highlight-current-row @current-change="onCurrent">
|
|
|
+ <el-table-column width="46" align="center">
|
|
|
+ <template #default="{ row }">
|
|
|
+ <el-radio v-model="pickedId" :label="row.id" @change="() => onPick(row)"><span /></el-radio>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="realName" label="姓名" min-width="100" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="account" label="账号" min-width="120" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="orgName" label="所属组织" min-width="140" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="posName" label="职位" min-width="100" show-overflow-tooltip />
|
|
|
+ <template #empty><el-empty description="该组织下无账号" /></template>
|
|
|
+ </el-table>
|
|
|
+ <div class="pager">
|
|
|
+ <el-pagination v-model:current-page="query.page" v-model:page-size="query.pageSize" :total="total" :page-sizes="[10, 20, 50]" layout="total, sizes, prev, pager, next" @current-change="loadUsers" @size-change="loadUsers" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="picked-hint">
|
|
|
+ <template v-if="picked">已选检验员:<b>{{ picked.realName }}({{ picked.account }})</b></template>
|
|
|
+ <span v-else class="muted">请在右侧账号列表勾选一名检验员</span>
|
|
|
+ </div>
|
|
|
+ <template #footer>
|
|
|
+ <el-button @click="emit('update:modelValue', false)">取消</el-button>
|
|
|
+ <el-button type="primary" :disabled="!picked" @click="onConfirm">确定</el-button>
|
|
|
+ </template>
|
|
|
+ </el-dialog>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup lang="ts" name="FqcInspectorPickerDialog">
|
|
|
+import { computed, reactive, ref } from 'vue';
|
|
|
+import { ElMessage } from 'element-plus';
|
|
|
+import { getAPI } from '/@/utils/axios-utils';
|
|
|
+import { SysUserApi } from '/@/api-services/api';
|
|
|
+import OrgTree from '/@/views/system/org/component/orgTree.vue';
|
|
|
+import { resolveAidopTenantId } from '/@/views/aidop/api/aidopTenant';
|
|
|
+
|
|
|
+interface PickedUser {
|
|
|
+ id: number;
|
|
|
+ realName: string;
|
|
|
+ account: string;
|
|
|
+}
|
|
|
+
|
|
|
+defineProps<{ modelValue: boolean }>();
|
|
|
+const emit = defineEmits<{
|
|
|
+ (e: 'update:modelValue', v: boolean): void;
|
|
|
+ (e: 'confirm', user: PickedUser): void;
|
|
|
+}>();
|
|
|
+
|
|
|
+// 锁当前登录租户:传给 OrgTree 隐藏其内置 TenantSelect,账号范围= 当前租户。
|
|
|
+// computed 确保在弹窗打开(此时 userInfo store 已就绪)时求值;store 万一仍空则从 JWT 兜底,
|
|
|
+// 双保险避免捕获到初始挂载时的 undefined 而露出「切换租户」控件。
|
|
|
+function currentTenantId(): number | undefined {
|
|
|
+ const v = resolveAidopTenantId();
|
|
|
+ if (v) return v;
|
|
|
+ try {
|
|
|
+ const tok = JSON.parse(localStorage.getItem('admin.net:access-token') || 'null');
|
|
|
+ if (tok) {
|
|
|
+ // base64url → base64(补 padding),兼容 token 轮换后 payload 含 - / _
|
|
|
+ let part = String(tok).split('.')[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
|
+ part += part.length % 4 ? '='.repeat(4 - (part.length % 4)) : '';
|
|
|
+ const payload = JSON.parse(atob(part));
|
|
|
+ const n = Number(payload?.TenantId);
|
|
|
+ return Number.isFinite(n) && n > 0 ? n : undefined;
|
|
|
+ }
|
|
|
+ } catch {
|
|
|
+ /* 解析失败则回落 undefined */
|
|
|
+ }
|
|
|
+ return undefined;
|
|
|
+}
|
|
|
+const tenantId = computed(() => currentTenantId());
|
|
|
+
|
|
|
+const query = reactive({ realName: '', account: '', orgId: undefined as number | undefined, page: 1, pageSize: 10 });
|
|
|
+const rows = ref<any[]>([]);
|
|
|
+const total = ref(0);
|
|
|
+const loading = ref(false);
|
|
|
+const pickedId = ref<number | null>(null);
|
|
|
+const picked = ref<PickedUser | null>(null);
|
|
|
+
|
|
|
+async function loadUsers() {
|
|
|
+ loading.value = true;
|
|
|
+ try {
|
|
|
+ const res = await getAPI(SysUserApi).apiSysUserPagePost({
|
|
|
+ page: query.page,
|
|
|
+ pageSize: query.pageSize,
|
|
|
+ realName: query.realName || undefined,
|
|
|
+ account: query.account || undefined,
|
|
|
+ orgId: query.orgId,
|
|
|
+ } as any);
|
|
|
+ rows.value = res.data.result?.items ?? [];
|
|
|
+ total.value = res.data.result?.total ?? 0;
|
|
|
+ } catch (e: any) {
|
|
|
+ rows.value = [];
|
|
|
+ total.value = 0;
|
|
|
+ ElMessage.error(e?.message || '加载账号失败');
|
|
|
+ } finally {
|
|
|
+ loading.value = false;
|
|
|
+ }
|
|
|
+}
|
|
|
+function doSearch() {
|
|
|
+ query.page = 1;
|
|
|
+ loadUsers();
|
|
|
+}
|
|
|
+function onNodeClick(node: any) {
|
|
|
+ const id = Number(node?.id);
|
|
|
+ query.orgId = Number.isFinite(id) && id > 0 ? id : undefined;
|
|
|
+ query.page = 1;
|
|
|
+ loadUsers();
|
|
|
+}
|
|
|
+function onPick(row: any) {
|
|
|
+ if (!row?.id) return;
|
|
|
+ pickedId.value = Number(row.id);
|
|
|
+ picked.value = { id: Number(row.id), realName: row.realName ?? '', account: row.account ?? '' };
|
|
|
+}
|
|
|
+function onCurrent(row: any) {
|
|
|
+ if (row) onPick(row);
|
|
|
+}
|
|
|
+function onOpen() {
|
|
|
+ query.realName = '';
|
|
|
+ query.account = '';
|
|
|
+ query.orgId = undefined;
|
|
|
+ query.page = 1;
|
|
|
+ pickedId.value = null;
|
|
|
+ picked.value = null;
|
|
|
+ loadUsers();
|
|
|
+}
|
|
|
+function onConfirm() {
|
|
|
+ if (!picked.value) {
|
|
|
+ ElMessage.warning('请选择一名检验员');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ emit('confirm', picked.value);
|
|
|
+}
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped lang="scss">
|
|
|
+.picker {
|
|
|
+ display: flex;
|
|
|
+ gap: 12px;
|
|
|
+ height: 420px;
|
|
|
+}
|
|
|
+.tree-pane {
|
|
|
+ width: 260px;
|
|
|
+ min-width: 260px;
|
|
|
+ height: 100%;
|
|
|
+ overflow: hidden;
|
|
|
+}
|
|
|
+.user-pane {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+.mb8 {
|
|
|
+ margin-bottom: 8px;
|
|
|
+}
|
|
|
+.pager {
|
|
|
+ margin-top: 8px;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+}
|
|
|
+.picked-hint {
|
|
|
+ margin-top: 10px;
|
|
|
+ font-size: 13px;
|
|
|
+ color: #303133;
|
|
|
+ .muted {
|
|
|
+ color: #909399;
|
|
|
+ }
|
|
|
+}
|
|
|
+</style>
|