<script lang="ts" setup>
|
import type { FormSchema } from '@jnpf/ui/form';
|
import type { BasicColumn } from '@jnpf/ui/vxeTable';
|
|
import type { PaperType } from './model';
|
|
import type { WatermarkConfig, WatermarkPosition, WatermarkScene, WatermarkType } from '#/api/x/dms/watermark';
|
|
import { computed, nextTick, ref } from 'vue';
|
|
import { useMessage } from '@jnpf/hooks';
|
import { BasicForm, useForm } from '@jnpf/ui/form';
|
import { BasicModal, useModalInner } from '@jnpf/ui/modal';
|
import { BasicVxeTable, TableAction, useVxeTable } from '@jnpf/ui/vxeTable';
|
|
import { EyeOutlined } from '@ant-design/icons-vue';
|
|
import { createWatermark, getWatermarkDetail, getWatermarkFonts, updateWatermark } from '#/api/x/dms/watermark';
|
import { getDictionaryDataSelector } from '#/api/systemData/dictionary';
|
|
import { CUSTOM_BARCODE_TEMPLATE, defaultPosition, defaultWatermark, PAPER_SIZES, validatePositionConfiguration } from './model';
|
import PreviewModal from './PreviewModal.vue';
|
|
type FormMode = 'create' | 'edit' | 'view';
|
type BarcodeSource = 'CUSTOM' | 'TEMPLATE';
|
|
const emit = defineEmits<{ saved: [] }>();
|
const mode = ref<FormMode>('create');
|
const positions = ref<WatermarkPosition[]>([]);
|
const previewVisible = ref(false);
|
const previewConfig = ref<WatermarkConfig>();
|
const businessTypesLoaded = ref(false);
|
const positionKeys = new WeakMap<WatermarkPosition, string>();
|
const duplicateNameMessage = '已存在同名水印,请更换名称';
|
let nextPositionKey = 0;
|
let templateDraft = '';
|
|
const { createMessage } = useMessage();
|
const readonly = computed(() => mode.value === 'view');
|
const modalTitle = computed(() => ({ create: '新增水印配置', edit: '编辑水印配置', view: '查看水印配置' })[mode.value]);
|
|
const sceneOptions: Array<{ fullName: string; id: WatermarkScene }> = [
|
{ fullName: '下载', id: 'DOWNLOAD' },
|
{ fullName: '预览', id: 'PREVIEW' },
|
{ fullName: '打印', id: 'PRINT' },
|
];
|
const variableOptions = [
|
{ label: '当前用户', value: '{/CUN}' },
|
{ label: '当前部门', value: '{/CDN}' },
|
{ label: '当前时间', value: '{/CDT}' },
|
{ label: '文件名称', value: '{/FILENAME}' },
|
{ label: '文件编号', value: '{/FILENO}' },
|
{ label: '版本号', value: '{/VERSION}' },
|
{ label: '文件状态', value: '{/FILESTATE}' },
|
{ label: '当前页', value: '{/CurrPage}' },
|
{ label: '总页数', value: '{/TotalPage}' },
|
];
|
const paperTypes = Object.keys(PAPER_SIZES) as PaperType[];
|
|
const schemas: FormSchema[] = [
|
groupTitle('baseGroup', '基础信息'),
|
{
|
field: 'name',
|
label: '水印名称',
|
component: 'Input',
|
componentProps: { maxlength: 100, placeholder: '请输入水印名称', showCount: true },
|
colProps: { span: 12 },
|
rules: [{ required: true, message: '请输入水印名称', trigger: 'blur' }],
|
},
|
{
|
field: 'enabled',
|
label: '是否启用',
|
component: 'Switch',
|
componentProps: { checkedValue: true, unCheckedValue: false },
|
colProps: { span: 12 },
|
defaultValue: true,
|
},
|
{
|
field: 'watermarkType',
|
label: '水印类型',
|
component: 'Select',
|
componentProps: {
|
onChange: changeType,
|
options: [
|
{ fullName: '文本', id: 'TEXT' },
|
{ fullName: '图片', id: 'IMAGE' },
|
{ fullName: '条码', id: 'BARCODE' },
|
],
|
},
|
colProps: { span: 12 },
|
rules: [{ required: true, message: '请选择水印类型', trigger: 'change' }],
|
},
|
{
|
field: 'loadScenes',
|
label: '水印加载',
|
component: 'Checkbox',
|
componentProps: { options: sceneOptions },
|
colProps: { span: 12 },
|
rules: [{ required: true, type: 'array', min: 1, message: '至少选择一个加载场景', trigger: 'change' }],
|
},
|
{
|
field: 'watermarkBusinessTypes',
|
label: '水印业务配置',
|
component: 'Select',
|
componentProps: { multiple: true, options: [], placeholder: '请选择水印业务类型' },
|
colProps: { span: 24 },
|
rules: [{ required: true, type: 'array', min: 1, message: '至少选择一个水印业务类型', trigger: 'change' }],
|
},
|
{
|
field: 'versionScope',
|
label: '适用版本',
|
component: 'Select',
|
componentProps: {
|
options: [
|
{ fullName: '所有版本', id: 'ALL' },
|
{ fullName: '现行版本', id: 'CURRENT' },
|
],
|
},
|
colProps: { span: 12 },
|
rules: [{ required: true, message: '请选择适用版本', trigger: 'change' }],
|
},
|
{
|
field: 'enterpriseName',
|
label: '所属企业',
|
component: 'Input',
|
componentProps: { maxlength: 100, placeholder: '请输入所属企业', showCount: true },
|
colProps: { span: 12 },
|
rules: [{ required: true, message: '请输入所属企业', trigger: 'blur' }],
|
},
|
groupTitle('contentGroup', '内容设置'),
|
{
|
field: 'barcodeSource',
|
label: '条码内容来源',
|
component: 'Radio',
|
colProps: { span: 24 },
|
ifShow: ({ values }) => values.watermarkType === 'BARCODE',
|
slot: 'barcodeSource',
|
},
|
{
|
field: 'contentTemplate',
|
label: '水印内容',
|
component: 'Textarea',
|
colProps: { span: 24 },
|
ifShow: ({ values }) => ['BARCODE', 'TEXT'].includes(values.watermarkType) && values.barcodeSource !== 'CUSTOM',
|
rules: [{ required: true, message: '请输入内容', trigger: 'blur' }],
|
slot: 'contentTemplate',
|
},
|
{
|
field: 'imageUrl',
|
label: '水印图片',
|
component: 'UploadImgSingle',
|
colProps: { span: 24 },
|
ifShow: ({ values }) => values.watermarkType === 'IMAGE',
|
rules: [{ required: true, message: '请上传水印图片', trigger: 'change' }],
|
},
|
{
|
field: 'barcodeFormat',
|
label: '条码格式',
|
component: 'Radio',
|
componentProps: {
|
optionType: 'button',
|
options: [
|
{ fullName: 'Code 128', id: 'CODE_128' },
|
{ fullName: '二维码', id: 'QR_CODE' },
|
],
|
},
|
colProps: { span: 12 },
|
ifShow: ({ values }) => values.watermarkType === 'BARCODE',
|
rules: [{ required: true, message: '请选择条码格式', trigger: 'change' }],
|
},
|
{
|
field: 'showBarcodeText',
|
label: '显示设置',
|
component: 'JnpfCheckboxSingle',
|
componentProps: { checkedValue: true, label: '是否显示条码文本', unCheckedValue: false },
|
colProps: { span: 12 },
|
dynamicDisabled: ({ values }) => readonly.value || values.barcodeSource === 'CUSTOM',
|
ifShow: ({ values }) => values.watermarkType === 'BARCODE',
|
},
|
groupTitle('effectGroup', '效果配置'),
|
{
|
field: 'fontFamily',
|
label: '字体',
|
component: 'Select',
|
componentProps: {
|
options: [],
|
placeholder: '请选择字体',
|
showSearch: true,
|
},
|
rules: [{ required: true, message: '请选择字体', trigger: 'change' }],
|
ifShow: ({ values }) => values.watermarkType === 'TEXT',
|
},
|
{ field: 'fontColor', label: '字体颜色', component: 'ColorPicker', ifShow: ({ values }) => values.watermarkType === 'TEXT' },
|
{
|
field: 'fontSize',
|
label: '字体大小',
|
component: 'InputNumber',
|
componentProps: { min: 6, max: 144 },
|
ifShow: ({ values }) => values.watermarkType === 'TEXT',
|
},
|
{
|
field: 'rotationAngle',
|
label: '旋转角度',
|
component: 'InputNumber',
|
componentProps: { min: -180, max: 180 },
|
rules: [{ required: true, type: 'number', message: '请输入旋转角度', trigger: 'change' }],
|
},
|
{
|
field: 'opacity',
|
label: '透明度',
|
component: 'Slider',
|
componentProps: { min: 0, max: 100 },
|
suffix: '%',
|
rules: [{ required: true, type: 'number', message: '请输入透明度', trigger: 'change' }],
|
},
|
{
|
field: 'layoutMode',
|
label: '布局方式',
|
component: 'Radio',
|
componentProps: {
|
optionType: 'button',
|
options: [
|
{ fullName: '按位置', id: 'POSITION' },
|
{ fullName: '整页重复', id: 'TILED' },
|
],
|
},
|
},
|
{
|
field: 'markWidthMm',
|
label: '水印宽度',
|
component: 'InputNumber',
|
componentProps: { min: 1, max: 1000 },
|
colProps: { span: 12 },
|
className: 'watermark-dimension',
|
suffix: 'mm',
|
},
|
{
|
field: 'markHeightMm',
|
label: '水印高度',
|
component: 'InputNumber',
|
componentProps: { min: 1, max: 1000 },
|
colProps: { span: 12 },
|
className: 'watermark-dimension',
|
suffix: 'mm',
|
},
|
{
|
field: 'horizontalAlign',
|
label: '水平对齐',
|
component: 'Radio',
|
componentProps: {
|
optionType: 'button',
|
options: [
|
{ fullName: '左对齐', id: 'LEFT' },
|
{ fullName: '居中', id: 'CENTER' },
|
{ fullName: '右对齐', id: 'RIGHT' },
|
],
|
},
|
colProps: { span: 12 },
|
ifShow: ({ values }) => values.watermarkType === 'TEXT' && values.layoutMode === 'POSITION',
|
defaultValue: 'LEFT',
|
rules: [{ required: true, message: '请选择水平对齐方式', trigger: 'change' }],
|
},
|
{
|
field: 'verticalAlign',
|
label: '垂直对齐',
|
component: 'Radio',
|
componentProps: {
|
optionType: 'button',
|
options: [
|
{ fullName: '顶部', id: 'TOP' },
|
{ fullName: '居中', id: 'MIDDLE' },
|
{ fullName: '底部', id: 'BOTTOM' },
|
],
|
},
|
colProps: { span: 12 },
|
ifShow: ({ values }) => values.watermarkType === 'TEXT' && values.layoutMode === 'POSITION',
|
defaultValue: 'TOP',
|
rules: [{ required: true, message: '请选择垂直对齐方式', trigger: 'change' }],
|
},
|
{
|
field: 'repeatSpacingXMm',
|
label: '横向间隔',
|
component: 'InputNumber',
|
componentProps: { min: 0, max: 1000 },
|
ifShow: ({ values }) => values.layoutMode === 'TILED',
|
suffix: 'mm',
|
},
|
{
|
field: 'repeatSpacingYMm',
|
label: '纵向间隔',
|
component: 'InputNumber',
|
componentProps: { min: 0, max: 1000 },
|
ifShow: ({ values }) => values.layoutMode === 'TILED',
|
suffix: 'mm',
|
},
|
groupTitle('positionGroup', '水印位置'),
|
{ field: 'positions', label: '', component: 'Input', colProps: { span: 24 }, colSlot: 'positions' },
|
];
|
|
const positionColumns: BasicColumn[] = [
|
{ title: '纸张类型', dataIndex: 'paperType', width: 120, slots: { default: 'paperType' } },
|
{ title: '宽(mm)', dataIndex: 'paperWidthMm', width: 120, slots: { default: 'paperWidthMm' } },
|
{ title: '高(mm)', dataIndex: 'paperHeightMm', width: 120, slots: { default: 'paperHeightMm' } },
|
{
|
title: '水印位置(横向,mm)',
|
children: [
|
{ title: 'X', dataIndex: 'landscapeXMm', width: 120, slots: { default: 'landscapeXMm' } },
|
{ title: 'Y', dataIndex: 'landscapeYMm', width: 120, slots: { default: 'landscapeYMm' } },
|
],
|
},
|
{
|
title: '水印位置(纵向,mm)',
|
children: [
|
{ title: 'X', dataIndex: 'portraitXMm', width: 120, slots: { default: 'portraitXMm' } },
|
{ title: 'Y', dataIndex: 'portraitYMm', width: 120, slots: { default: 'portraitYMm' } },
|
],
|
},
|
];
|
|
const [registerForm, { clearValidate, getFieldsValue, resetFields, setFieldsValue, updateSchema, validate }] = useForm({
|
baseColProps: { span: 8 },
|
labelWidth: 110,
|
schemas,
|
showActionButtonGroup: false,
|
});
|
const [registerPositionTable] = useVxeTable({
|
actionColumn: { dataIndex: 'action', fixed: 'right', ifShow: () => !readonly.value, title: '操作', width: 80 },
|
columns: positionColumns,
|
dataSource: positions,
|
emptyText: '暂无水印位置',
|
height: 260,
|
immediate: false,
|
pagination: false,
|
rowKey: positionRowKey,
|
showIndexColumn: false,
|
showTableSetting: false,
|
});
|
const [registerModal, { changeOkLoading, closeModal, setModalProps }] = useModalInner(init);
|
|
function groupTitle(field: string, content: string): FormSchema {
|
return { field, label: '', component: 'GroupTitle', componentProps: { content }, colProps: { span: 24 } };
|
}
|
|
async function loadFonts(isCreate: boolean) {
|
await updateSchema({ field: 'fontFamily', componentProps: { options: [], loading: true } });
|
try {
|
const response = await getWatermarkFonts();
|
const options = response.data.map((font) => ({ fullName: font.displayName, id: font.family }));
|
await updateSchema({
|
field: 'fontFamily',
|
componentProps: { options, placeholder: options.length ? '请选择字体' : '字体目录没有可用字体,请添加 TTF、OTF 或 TTC 字体' },
|
});
|
if (isCreate) {
|
const preferred = options.find((font) => font.id === 'SimHei') || options[0];
|
await setFieldsValue({ fontFamily: preferred?.id });
|
}
|
if (!options.length) createMessage.warning('字体目录没有可用字体,请添加 TTF、OTF 或 TTC 字体文件');
|
} catch {
|
await updateSchema({ field: 'fontFamily', componentProps: { options: [], placeholder: '字体列表加载失败' } });
|
createMessage.error('字体列表加载失败,请检查 ONLYOFFICE_CUSTOM_FONT_DIR 配置');
|
} finally {
|
await updateSchema({ field: 'fontFamily', componentProps: { loading: false } });
|
}
|
}
|
|
async function loadBusinessTypes() {
|
businessTypesLoaded.value = false;
|
await updateSchema({ field: 'watermarkBusinessTypes', componentProps: { loading: true } });
|
try {
|
const response = await getDictionaryDataSelector('879384732964547653');
|
const rows = response?.data?.list;
|
if (!Array.isArray(rows)) throw new Error('字典响应无效');
|
const options = rows.filter((item) => item?.enCode).map((item) => ({ fullName: item.fullName, id: item.enCode }));
|
businessTypesLoaded.value = options.length > 0;
|
await updateSchema({ field: 'watermarkBusinessTypes', componentProps: { options } });
|
if (!options.length) createMessage.warning('水印业务类型字典没有可用选项');
|
} catch {
|
await updateSchema({ field: 'watermarkBusinessTypes', componentProps: { options: [] } });
|
createMessage.error('水印业务类型字典加载失败');
|
} finally {
|
await updateSchema({ field: 'watermarkBusinessTypes', componentProps: { loading: false } });
|
}
|
}
|
|
async function init(data: { id?: string; mode: FormMode }) {
|
mode.value = data.mode;
|
previewVisible.value = false;
|
templateDraft = '';
|
const initialValue = {
|
...defaultWatermark(),
|
barcodeFormat: undefined,
|
barcodeSource: 'TEMPLATE' as BarcodeSource,
|
contentTemplate: '',
|
id: undefined,
|
imageUrl: undefined,
|
revision: undefined,
|
};
|
positions.value = initialValue.positions;
|
await resetFields();
|
await setFieldsValue(initialValue);
|
await updateContentLabel('TEXT');
|
setModalProps({ confirmLoading: false, showOkBtn: data.mode !== 'view' });
|
await loadFonts(data.mode === 'create');
|
await loadBusinessTypes();
|
if (data.id) {
|
changeOkLoading(true);
|
try {
|
const response = await getWatermarkDetail(data.id);
|
const watermarkType = (response.data.watermarkType || 'TEXT').toUpperCase() as WatermarkType;
|
const layoutMode = (response.data.layoutMode || 'POSITION').toUpperCase();
|
const customSource = watermarkType === 'BARCODE' && response.data.contentTemplate === CUSTOM_BARCODE_TEMPLATE;
|
templateDraft = customSource ? '' : response.data.contentTemplate || '';
|
positions.value = response.data.positions || [];
|
await setFieldsValue({
|
...response.data,
|
watermarkType,
|
layoutMode,
|
horizontalAlign: response.data.horizontalAlign || 'LEFT',
|
verticalAlign: response.data.verticalAlign || 'TOP',
|
barcodeSource: customSource ? 'CUSTOM' : 'TEMPLATE',
|
showBarcodeText: customSource || response.data.showBarcodeText,
|
});
|
await updateContentLabel(watermarkType);
|
} catch {
|
createMessage.error('水印配置加载失败');
|
closeModal();
|
} finally {
|
changeOkLoading(false);
|
}
|
}
|
nextTick(() => clearValidate());
|
}
|
|
function addPosition() {
|
const paperType = paperTypes.find((type) => !positions.value.some((position) => position.paperType === type));
|
const { markWidthMm, markHeightMm } = getFieldsValue();
|
if (paperType) positions.value.push(defaultPosition(paperType, markWidthMm, markHeightMm));
|
else createMessage.warning('A4、A3 均已配置');
|
}
|
|
function paperTypeOptions(record: WatermarkPosition) {
|
return paperTypes.map((type) => ({
|
id: type,
|
fullName: type,
|
disabled: positions.value.some((position) => position !== record && position.paperType === type),
|
}));
|
}
|
|
function changePaperType(record: WatermarkPosition, paperType: PaperType) {
|
if (paperType === record.paperType) return;
|
if (positions.value.some((position) => position !== record && position.paperType === paperType)) {
|
createMessage.warning(`${paperType} 已配置,请勿重复添加`);
|
return;
|
}
|
const { markWidthMm, markHeightMm } = getFieldsValue();
|
const previousDefault = record.paperType in PAPER_SIZES ? defaultPosition(record.paperType as PaperType, markWidthMm, markHeightMm) : undefined;
|
const wasCentered =
|
previousDefault && (['portraitXMm', 'portraitYMm', 'landscapeXMm', 'landscapeYMm'] as const).every((field) => record[field] === previousDefault[field]);
|
const { widthMm, heightMm } = PAPER_SIZES[paperType];
|
Object.assign(record, wasCentered ? defaultPosition(paperType, markWidthMm, markHeightMm) : { paperType, paperWidthMm: widthMm, paperHeightMm: heightMm });
|
}
|
|
function removePosition(record: WatermarkPosition) {
|
const index = positions.value.indexOf(record);
|
if (index !== -1) positions.value.splice(index, 1);
|
}
|
|
function positionRowKey(record: Recordable) {
|
const position = record as WatermarkPosition;
|
let key = positionKeys.get(position);
|
if (!key) {
|
key = `position-${nextPositionKey++}`;
|
positionKeys.set(position, key);
|
}
|
return key;
|
}
|
|
function insertVariable(model: Record<string, any>, value: string) {
|
model.contentTemplate = (model.contentTemplate || '') + value;
|
}
|
|
async function changeType(value: WatermarkType) {
|
const values = getFieldsValue();
|
if (value !== 'BARCODE' && values.barcodeSource === 'CUSTOM') {
|
await setFieldsValue({ barcodeSource: 'TEMPLATE', contentTemplate: '' });
|
}
|
if (value === 'BARCODE' && !values.barcodeFormat) await setFieldsValue({ barcodeFormat: 'CODE_128' });
|
if (value !== 'BARCODE') await setFieldsValue({ showBarcodeText: false });
|
await updateContentLabel(value);
|
}
|
|
async function changeBarcodeSource(source: BarcodeSource) {
|
const values = getFieldsValue();
|
if (source === 'CUSTOM') {
|
templateDraft = values.contentTemplate || '';
|
await setFieldsValue({ barcodeFormat: 'QR_CODE', contentTemplate: CUSTOM_BARCODE_TEMPLATE, showBarcodeText: true });
|
} else {
|
await setFieldsValue({ contentTemplate: templateDraft });
|
}
|
}
|
|
function updateContentLabel(type: WatermarkType) {
|
return updateSchema({ field: 'contentTemplate', label: type === 'BARCODE' ? '条码内容' : '水印内容' });
|
}
|
|
function handlePreview() {
|
if (!positions.value.length) {
|
createMessage.warning('请至少配置一个纸张位置');
|
return;
|
}
|
const snapshot = { ...defaultWatermark(), ...getFieldsValue(), positions: positions.value };
|
// Vue form values and table rows can be reactive proxies.
|
// eslint-disable-next-line unicorn/prefer-structured-clone
|
previewConfig.value = JSON.parse(JSON.stringify(snapshot)) as WatermarkConfig;
|
previewVisible.value = true;
|
}
|
|
async function handleSubmit() {
|
if (!businessTypesLoaded.value) {
|
createMessage.error('水印业务类型字典不可用,无法保存');
|
return;
|
}
|
const values = await validate();
|
if (!values) return;
|
if (!positions.value.length) {
|
createMessage.warning('请至少配置一个纸张位置');
|
return;
|
}
|
const { barcodeSource: _barcodeSource, positions: _positions, ...config } = values;
|
const payload = {
|
...config,
|
contentTemplate: config.contentTemplate?.trim(),
|
enterpriseName: config.enterpriseName.trim(),
|
name: config.name.trim(),
|
positions: positions.value,
|
} as WatermarkConfig;
|
const positionError = validatePositionConfiguration(payload);
|
if (positionError) {
|
createMessage.warning(positionError);
|
return;
|
}
|
changeOkLoading(true);
|
try {
|
await (payload.id ? updateWatermark(payload.id, payload) : createWatermark(payload));
|
createMessage.success(payload.id ? '水印配置已更新' : '水印配置已创建');
|
closeModal();
|
emit('saved');
|
} catch (error) {
|
const responseMessage = (error as { response?: { data?: { msg?: string } } })?.response?.data?.msg;
|
const message = responseMessage || (error instanceof Error ? error.message : '');
|
createMessage.error(message === duplicateNameMessage ? duplicateNameMessage : '保存失败,请检查配置或刷新后重试');
|
} finally {
|
changeOkLoading(false);
|
}
|
}
|
</script>
|
|
<template>
|
<BasicModal v-bind="$attrs" :title="modalTitle" :width="1120" :min-height="620" destroy-on-close @register="registerModal" @ok="handleSubmit">
|
<template #insertFooter>
|
<a-button v-if="mode !== 'view'" @click="handlePreview"><EyeOutlined />预览</a-button>
|
</template>
|
<BasicForm class="watermark-form" :disabled="readonly" @register="registerForm">
|
<template #barcodeSource="{ model, field }">
|
<JnpfRadio
|
v-model:value="model[field]"
|
:disabled="readonly"
|
option-type="button"
|
:options="[
|
{ fullName: '模板内容', id: 'TEMPLATE' },
|
{ fullName: '自定义条码文本', id: 'CUSTOM' },
|
]"
|
@change="changeBarcodeSource" />
|
<div v-if="model[field] === 'CUSTOM'" class="text-muted-foreground mt-2">
|
由业务调用逐项提供条码内容和显示文字;每项可指定页码或应用于每页,同页可添加多项。
|
</div>
|
</template>
|
<template #contentTemplate="{ model, field }">
|
<JnpfTextarea v-model:value="model[field]" :disabled="readonly" :maxlength="2000" :rows="3" show-count />
|
<div class="mt-2 flex flex-wrap items-center gap-2">
|
<span>插入变量</span>
|
<a-button v-for="item in variableOptions" :key="item.value" :disabled="readonly" size="small" @click="insertVariable(model, item.value)">
|
{{ item.label }}
|
</a-button>
|
</div>
|
</template>
|
<template #positions>
|
<BasicVxeTable :data-source="positions" @register="registerPositionTable">
|
<template #tableTitle>
|
<a-button v-if="!readonly" pre-icon="icon-ym icon-ym-btn-add" @click="addPosition">新增位置</a-button>
|
</template>
|
<template #paperType="{ record }">
|
<JnpfSelect
|
:value="record.paperType"
|
:disabled="readonly"
|
:options="paperTypeOptions(record)"
|
@change="(value) => changePaperType(record, value)" />
|
</template>
|
<template #paperWidthMm="{ record }"><JnpfInputNumber v-model:value="record.paperWidthMm" disabled :min="1" /></template>
|
<template #paperHeightMm="{ record }"><JnpfInputNumber v-model:value="record.paperHeightMm" disabled :min="1" /></template>
|
<template #landscapeXMm="{ record }">
|
<JnpfInputNumber v-model:value="record.landscapeXMm" :disabled="readonly" :min="0" :max="record.paperHeightMm" />
|
</template>
|
<template #landscapeYMm="{ record }">
|
<JnpfInputNumber v-model:value="record.landscapeYMm" :disabled="readonly" :min="0" :max="record.paperWidthMm" />
|
</template>
|
<template #portraitXMm="{ record }">
|
<JnpfInputNumber v-model:value="record.portraitXMm" :disabled="readonly" :min="0" :max="record.paperWidthMm" />
|
</template>
|
<template #portraitYMm="{ record }">
|
<JnpfInputNumber v-model:value="record.portraitYMm" :disabled="readonly" :min="0" :max="record.paperHeightMm" />
|
</template>
|
<template #action="{ record }">
|
<TableAction :actions="[{ label: '删除', color: 'error', onClick: () => removePosition(record) }]" />
|
</template>
|
</BasicVxeTable>
|
</template>
|
</BasicForm>
|
</BasicModal>
|
<PreviewModal v-model:open="previewVisible" :config="previewConfig" />
|
</template>
|
|
<style scoped>
|
/* BasicForm offsets controls with a suffix by 4px; keep these inputs level with their labels. */
|
.watermark-form :deep(.watermark-dimension .ant-form-item-control) {
|
margin-top: 0;
|
}
|
</style>
|