刘光辉
6 小时以前 cf11e63b659afcc9792ab3e395f1b45d844431a8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
<script lang="ts" setup>
import type { ActionItem, BasicColumn } from '@jnpf/ui/vxeTable';
 
import type { FieldListItem, FieldPageQuery } from '#/api/x/eln/fields';
 
import { ref } from 'vue';
 
import { useMessage } from '@jnpf/hooks';
import { useDrawer } from '@jnpf/ui/drawer';
import { BasicForm, useForm } from '@jnpf/ui/form';
import { BasicModal, useModal } from '@jnpf/ui/modal';
import { BasicVxeTable, TableAction, useVxeTable } from '@jnpf/ui/vxeTable';
 
import { DatabaseOutlined } from '@ant-design/icons-vue';
 
import { disableField, enableField, getFieldPage, promoteFieldToGlobal } from '#/api/x/eln/fields';
import { onlineUtils } from '#/utils/jnpf';
 
import FieldForm from './FieldForm.vue';
import { fieldActions } from './fieldRules';
import VersionDrawer from './VersionDrawer.vue';
 
defineOptions({ name: 'ElnFieldCatalog' });
 
type FieldChange = 'disable' | 'enable' | 'promoteGlobal';
 
const { createMessage } = useMessage();
const [registerFieldForm, { openModal: openFieldForm }] = useModal();
const [registerReasonModal, { closeModal: closeReasonModal, openModal: openReasonModal }] = useModal();
const [registerVersionDrawer, { openDrawer: openVersionDrawer }] = useDrawer();
 
const reason = ref('');
const pendingChange = ref<{ field: FieldListItem; type: FieldChange }>();
 
const columns: BasicColumn[] = [
  { dataIndex: 'ziduanBianma', minWidth: 210, title: '字段编码', slots: { default: 'ziduanBianma' } },
  { dataIndex: 'ziduanMingcheng', minWidth: 150, title: '字段名称' },
  { dataIndex: 'zuoyongFanwei', title: '作用域', width: 100, slots: { default: 'zuoyongFanwei' } },
  { dataIndex: 'mubanBianhao', minWidth: 140, title: '归属模板', slots: { default: 'mubanBianhao' } },
  { dataIndex: 'dangqianBanbenHao', title: '当前版本', width: 100, slots: { default: 'dangqianBanbenHao' } },
  { dataIndex: 'zuixinFabuBanbenHao', title: '最新发布', width: 100, slots: { default: 'zuixinFabuBanbenHao' } },
  { dataIndex: 'bizEnabled', title: '启用状态', width: 100, slots: { default: 'bizEnabled' } },
  { align: 'right', dataIndex: 'mubanYinyongShu', title: '模板引用数', width: 110 },
  { dataIndex: 'gengxinShijian', format: 'date|YYYY-MM-DD HH:mm:ss', title: '更新时间', width: 165 },
];
 
const [registerSearchForm, { getFieldsValue, resetFields }] = useForm({
  baseColProps: { lg: 8, md: 12, sm: 12, xl: 6, xs: 24 },
  compact: true,
  labelWidth: 76,
  schemas: [
    {
      component: 'Input',
      componentProps: { placeholder: '字段编码或名称', submitOnPressEnter: true },
      field: 'keyword',
      label: '关键字',
    },
    {
      component: 'Select',
      componentProps: {
        allowClear: true,
        options: [
          { fullName: '全局字段', id: 'GLOBAL' },
          { fullName: '模板私有字段', id: 'TEMPLATE' },
        ],
        placeholder: '全部',
      },
      field: 'zuoyongFanwei',
      label: '作用域',
    },
    {
      component: 'Input',
      componentProps: { allowClear: true, placeholder: '请输入模板编号', submitOnPressEnter: true },
      field: 'mubanBianhao',
      label: '归属模板',
    },
    {
      component: 'Select',
      componentProps: {
        allowClear: true,
        options: [
          { fullName: '启用', id: 'enabled' },
          { fullName: '停用', id: 'disabled' },
        ],
        placeholder: '全部',
      },
      field: 'bizEnabled',
      label: '状态',
    },
  ],
  showActionButtonGroup: true,
  showAdvancedButton: false,
});
 
const [registerTable, { reload }] = useVxeTable({
  actionColumn: { dataIndex: 'action', fixed: 'right', title: '操作', width: 220 },
  api: fetchFieldPage,
  beforeFetch: normalizeQuery,
  columns,
  immediate: true,
  pagination: {
    pageSize: 20,
    pageSizeOptions: ['20', '50', '100'],
    showQuickJumper: true,
    showSizeChanger: true,
    showTotal: (total) => `共 ${total} 个字段`,
  },
  rowKey: 'id',
  showIndexColumn: false,
});
 
function normalizeQuery(raw: Record<string, unknown>): FieldPageQuery {
  const query: Record<string, unknown> = { ...raw };
  for (const key of ['keyword', 'mubanBianhao']) {
    if (typeof query[key] === 'string') query[key] = query[key].trim();
    if (!query[key]) delete query[key];
  }
  for (const key of ['bizEnabled', 'zuoyongFanwei']) {
    if (!query[key]) delete query[key];
  }
  query.currentPage = Math.max(1, Number(query.currentPage) || 1);
  query.pageSize = Math.max(1, Number(query.pageSize) || 20);
  return query as unknown as FieldPageQuery;
}
 
async function fetchFieldPage(query: FieldPageQuery) {
  const response = await getFieldPage(query);
  return { data: response.data };
}
 
function handleSearch() {
  reload({ page: 1, searchInfo: getFieldsValue() });
}
 
function openLimsSources() {
  onlineUtils.route('/eln/sources', { title: 'LIMS 数据项' });
}
 
async function handleReset() {
  await resetFields();
  reload({ page: 1, searchInfo: {} });
}
 
function getTableActions(field: FieldListItem): ActionItem[] {
  const actions = fieldActions({ enabled: field.bizEnabled === 'enabled', scope: field.zuoyongFanwei });
  return actions.map((action): ActionItem => {
    if (action === 'versions') {
      return { label: '详情', onClick: () => openVersionDrawer(true, { fieldId: field.id }) };
    }
    if (action === 'promoteGlobal') {
      return {
        label: '升级为全局',
        onClick: () => requestFieldChange(field, action),
      };
    }
    if (action === 'enable') {
      return { color: 'success', label: '启用', onClick: () => requestFieldChange(field, action) };
    }
    return { color: 'error', label: '停用', onClick: () => requestFieldChange(field, action) };
  });
}
 
function requestFieldChange(field: FieldListItem, type: FieldChange) {
  reason.value = '';
  pendingChange.value = { field, type };
  openReasonModal(true);
}
 
async function confirmFieldChange() {
  const change = pendingChange.value;
  const actualReason = reason.value.trim();
  if (!change || !actualReason) {
    createMessage.warning('请输入操作原因');
    return;
  }
  if (change.type === 'promoteGlobal') await promoteFieldToGlobal(change.field.id, actualReason);
  else if (change.type === 'enable') await enableField(change.field.id, actualReason);
  else await disableField(change.field.id, actualReason);
  createMessage.success(change.type === 'promoteGlobal' ? '已升级为全局字段' : change.type === 'enable' ? '字段已启用' : '字段已停用');
  closeReasonModal();
  reload();
}
 
function reasonTitle() {
  if (pendingChange.value?.type === 'promoteGlobal') return '升级为全局字段';
  return pendingChange.value?.type === 'enable' ? '启用字段' : '停用字段';
}
</script>
 
<template>
  <div class="jnpf-content-wrapper">
    <div class="jnpf-content-wrapper-center">
      <div class="jnpf-content-wrapper-search-box">
        <BasicForm @register="registerSearchForm" @reset="handleReset" @submit="handleSearch" />
      </div>
      <div class="jnpf-content-wrapper-content">
        <BasicVxeTable @register="registerTable">
          <template #tableTitle>
            <a-button type="primary" pre-icon="icon-ym icon-ym-btn-add" @click="openFieldForm(true, {})"> 新增全局字段 </a-button>
            <a-button data-test="open-lims-sources" @click="openLimsSources">
              <template #icon><DatabaseOutlined /></template>
              LIMS 数据项
            </a-button>
          </template>
          <template #ziduanBianma="{ record }">
            <span :title="record.ziduanBianma">{{ record.ziduanBianma }}</span>
          </template>
          <template #zuoyongFanwei="{ record }">
            <a-tag :color="record.zuoyongFanwei === 'GLOBAL' ? 'blue' : 'default'">
              {{ record.zuoyongFanwei === 'GLOBAL' ? '全局' : '模板私有' }}
            </a-tag>
          </template>
          <template #mubanBianhao="{ record }">
            {{ record.zuoyongFanwei === 'TEMPLATE' ? record.mubanBianhao || '-' : '-' }}
          </template>
          <template #dangqianBanbenHao="{ record }">
            {{ record.dangqianBanbenHao ? `V${record.dangqianBanbenHao}` : '-' }}
          </template>
          <template #zuixinFabuBanbenHao="{ record }">
            {{ record.zuixinFabuBanbenHao ? `V${record.zuixinFabuBanbenHao}` : '-' }}
          </template>
          <template #bizEnabled="{ record }">
            <a-tag :color="record.bizEnabled === 'enabled' ? 'success' : 'default'">
              {{ record.bizEnabled === 'enabled' ? '启用' : '停用' }}
            </a-tag>
          </template>
          <template #action="{ record }">
            <TableAction :actions="getTableActions(record)" />
          </template>
        </BasicVxeTable>
      </div>
    </div>
 
    <FieldForm @register="registerFieldForm" @reload="reload" />
    <VersionDrawer @register="registerVersionDrawer" @reload="reload" />
    <BasicModal @register="registerReasonModal" :title="reasonTitle()" destroy-on-close @ok="confirmFieldChange">
      <a-form-item label="操作原因" required>
        <a-textarea v-model:value="reason" :maxlength="500" :rows="4" placeholder="请输入操作原因" show-count />
      </a-form-item>
      <a-alert
        v-if="pendingChange?.type === 'promoteGlobal'"
        message="字段编码和历史版本保持不变,模板归属将清空,字段可供所有模板引用。"
        show-icon
        type="warning" />
    </BasicModal>
  </div>
</template>
 
<style lang="scss" scoped>
:deep(.jnpf-vxe-table) {
  min-width: 0;
}
</style>