fix(project): 修复 OA 邮箱校验、报销明细编辑与用品选择初始化

关联:https://t.zsxq.com/y1ggF
This commit is contained in:
YunaiV
2026-09-26 21:40:46 +08:00
parent 59edd3f7f3
commit 4954cdb6eb
10 changed files with 95 additions and 54 deletions
@@ -3,6 +3,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { isServerHost } from '@vben/utils';
import { z } from '#/adapter/form';
@@ -94,7 +95,10 @@ function useConnectionFormSchema(
componentProps: {
placeholder: '请输入服务器域名',
},
rules: 'required',
rules: z
.string()
.min(1, '请输入服务器域名')
.refine(isServerHost, '请输入有效的服务器域名或 IP 地址'),
},
{
fieldName: `${protocol}.port`,
@@ -7,7 +7,7 @@ import { computed, nextTick, ref, watch } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { formatDateTime } from '@vben/utils';
import { cloneDeep, formatDateTime, isEqual } from '@vben/utils';
import { Button, DatePicker, Input, InputNumber, Select } from 'ant-design-vue';
import dayjs from 'dayjs';
@@ -72,34 +72,39 @@ async function reloadData() {
await gridApi.grid?.reloadData(tableData.value);
}
/** 监听外部传入的明细数据,同引用回写时跳过重载 */
/** 表单值可能只读,使用独立副本编辑;同值回写时不重载表格 */
watch(
() => props.modelValue,
async (items) => {
const next = items ?? [];
const unchanged =
next.length === tableData.value.length &&
next.every((item, index) => item === tableData.value[index]);
if (unchanged) {
if (isEqual(next, tableData.value)) {
return;
}
tableData.value = [...next];
tableData.value = cloneDeep(next);
await reloadData();
},
{ immediate: true },
);
watch(
tableData,
(items) => {
if (!isEqual(items, props.modelValue)) {
emit('update:modelValue', cloneDeep(items));
}
},
{ deep: true },
);
/** 新增费用明细 */
async function handleAdd() {
tableData.value.push({ invoiceCount: 0, price: 0 });
emit('update:modelValue', [...tableData.value]);
await reloadData();
}
/** 删除费用明细 */
async function handleDelete(index: number) {
tableData.value.splice(index, 1);
emit('update:modelValue', [...tableData.value]);
await reloadData();
}
@@ -3,8 +3,6 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { OaSupplyItemApi } from '#/api/oa/supply/item';
import { nextTick } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
@@ -111,12 +109,8 @@ function handleSelect(item: OaSupplyItemApi.SupplyItem) {
const [Modal, modalApi] = useVbenModal({
showConfirmButton: false,
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
return;
}
// 打开时重置搜索并重新查询
await nextTick();
async onOpened() {
// 表格挂载后重置搜索并重新查询
await gridApi.formApi.reset();
gridApi.query();
},
@@ -3,6 +3,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { isServerHost } from '@vben/utils';
import { z } from '#/adapter/form';
@@ -94,7 +95,10 @@ function useConnectionFormSchema(
componentProps: {
placeholder: '请输入服务器域名',
},
rules: 'required',
rules: z
.string()
.min(1, '请输入服务器域名')
.refine(isServerHost, '请输入有效的服务器域名或 IP 地址'),
},
{
fieldName: `${protocol}.port`,
@@ -7,7 +7,7 @@ import { computed, nextTick, ref, watch } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { formatDateTime } from '@vben/utils';
import { cloneDeep, formatDateTime, isEqual } from '@vben/utils';
import { Button, DatePicker, Input, InputNumber, Select } from 'antdv-next';
import dayjs from 'dayjs';
@@ -72,34 +72,39 @@ async function reloadData() {
await gridApi.grid?.reloadData(tableData.value);
}
/** 监听外部传入的明细数据,同引用回写时跳过重载 */
/** 表单值可能只读,使用独立副本编辑;同值回写时不重载表格 */
watch(
() => props.modelValue,
async (items) => {
const next = items ?? [];
const unchanged =
next.length === tableData.value.length &&
next.every((item, index) => item === tableData.value[index]);
if (unchanged) {
if (isEqual(next, tableData.value)) {
return;
}
tableData.value = [...next];
tableData.value = cloneDeep(next);
await reloadData();
},
{ immediate: true },
);
watch(
tableData,
(items) => {
if (!isEqual(items, props.modelValue)) {
emit('update:modelValue', cloneDeep(items));
}
},
{ deep: true },
);
/** 新增费用明细 */
async function handleAdd() {
tableData.value.push({ invoiceCount: 0, price: 0 });
emit('update:modelValue', [...tableData.value]);
await reloadData();
}
/** 删除费用明细 */
async function handleDelete(index: number) {
tableData.value.splice(index, 1);
emit('update:modelValue', [...tableData.value]);
await reloadData();
}
@@ -3,8 +3,6 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { OaSupplyItemApi } from '#/api/oa/supply/item';
import { nextTick } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
@@ -111,12 +109,8 @@ function handleSelect(item: OaSupplyItemApi.SupplyItem) {
const [Modal, modalApi] = useVbenModal({
showConfirmButton: false,
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
return;
}
// 打开时重置搜索并重新查询
await nextTick();
async onOpened() {
// 表格挂载后重置搜索并重新查询
await gridApi.formApi.reset();
gridApi.query();
},
@@ -3,6 +3,7 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { isServerHost } from '@vben/utils';
import { z } from '#/adapter/form';
@@ -94,7 +95,10 @@ function useConnectionFormSchema(
componentProps: {
placeholder: '请输入服务器域名',
},
rules: 'required',
rules: z
.string()
.min(1, '请输入服务器域名')
.refine(isServerHost, '请输入有效的服务器域名或 IP 地址'),
},
{
fieldName: `${protocol}.port`,
@@ -5,7 +5,7 @@ import { computed, nextTick, ref, watch } from 'vue';
import { DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { formatDateTime } from '@vben/utils';
import { cloneDeep, formatDateTime, isEqual } from '@vben/utils';
import {
ElButton,
@@ -76,34 +76,39 @@ async function reloadData() {
await gridApi.grid?.reloadData(tableData.value);
}
/** 监听外部传入的明细数据,同引用回写时跳过重载 */
/** 表单值可能只读,使用独立副本编辑;同值回写时不重载表格 */
watch(
() => props.modelValue,
async (items) => {
const next = items ?? [];
const unchanged =
next.length === tableData.value.length &&
next.every((item, index) => item === tableData.value[index]);
if (unchanged) {
if (isEqual(next, tableData.value)) {
return;
}
tableData.value = [...next];
tableData.value = cloneDeep(next);
await reloadData();
},
{ immediate: true },
);
watch(
tableData,
(items) => {
if (!isEqual(items, props.modelValue)) {
emit('update:modelValue', cloneDeep(items));
}
},
{ deep: true },
);
/** 新增费用明细 */
async function handleAdd() {
tableData.value.push({ invoiceCount: 0, price: 0 });
emit('update:modelValue', [...tableData.value]);
await reloadData();
}
/** 删除费用明细 */
async function handleDelete(index: number) {
tableData.value.splice(index, 1);
emit('update:modelValue', [...tableData.value]);
await reloadData();
}
</script>
@@ -3,8 +3,6 @@ import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { OaSupplyItemApi } from '#/api/oa/supply/item';
import { nextTick } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { DICT_TYPE } from '@vben/constants';
@@ -111,12 +109,8 @@ function handleSelect(item: OaSupplyItemApi.SupplyItem) {
const [Modal, modalApi] = useVbenModal({
showConfirmButton: false,
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
return;
}
// 打开时重置搜索并重新查询
await nextTick();
async onOpened() {
// 表格挂载后重置搜索并重新查询
await gridApi.formApi.reset();
gridApi.query();
},
+32
View File
@@ -14,3 +14,35 @@ function isMobile(value?: null | string): boolean {
}
export { isMobile };
/** 校验服务器主机名或 IP,不包含协议、路径和端口 */
export function isServerHost(value?: null | string): boolean {
if (!value || value.length > 253) return false;
const ipv4 =
/^(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]?\d)){3}$/;
if (value.includes(':')) {
let address = value;
if (address.includes('.')) {
const index = address.lastIndexOf(':');
if (!ipv4.test(address.slice(index + 1))) return false;
address = `${address.slice(0, index + 1)}0:0`;
}
const parts = address.split('::');
if (
parts.length > 2 ||
!parts.every(
(part) => part === '' || /^[\da-f]{1,4}(?::[\da-f]{1,4})*$/i.test(part),
)
)
return false;
const count = parts.reduce(
(total, part) => total + (part ? part.split(':').length : 0),
0,
);
return parts.length === 2 ? count < 8 : count === 8;
}
if (/^\d+(?:\.\d+){3}$/.test(value)) return ipv4.test(value);
return /^[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?(?:\.[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?)*\.?$/i.test(
value,
);
}