Merge remote-tracking branch 'yudao/master'

pull/360/head
jason 2026-06-11 22:59:32 +08:00
commit edec738466
1761 changed files with 114668 additions and 14760 deletions

View File

@ -0,0 +1,3 @@
{
"singleQuote": true
}

View File

@ -0,0 +1,7 @@
---
'@vben/styles': patch
'@vben-core/form-ui': patch
'@vben/web-naive': patch
---
feat(@core/form-ui): 新增 useVbenForm 数组编辑器 VbenFormFieldArray

View File

@ -61,7 +61,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v4
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@ -89,6 +89,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v4
with:
category: '/language:${{matrix.language}}'

View File

@ -19,7 +19,7 @@ jobs:
steps:
# 关闭未活动的 Issues
- name: Close Inactive Issues
uses: actions/stale@v9
uses: actions/stale@v10
with:
days-before-stale: -1 # Issues and PR will never be flagged stale automatically.
stale-issue-label: needs-reproduction # Label that flags an issue as stale.

View File

@ -14,7 +14,7 @@ jobs:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- uses: dessant/lock-threads@v5
- uses: dessant/lock-threads@v6
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
issue-inactive-days: '14'

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Validate PR title
uses: amannn/action-semantic-pull-request@v5
uses: amannn/action-semantic-pull-request@v6
with:
wip: true
subjectPattern: ^(?![A-Z]).+$

View File

@ -9,7 +9,7 @@ jobs:
if: github.repository == 'vbenjs/vue-vben-admin'
runs-on: ubuntu-latest
steps:
- uses: actions/stale@v9
- uses: actions/stale@v10
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 60 days with no activity. Remove stale label or comment or this will be closed in 7 days'

View File

@ -1 +1 @@
22.22.0
24.16.0

View File

@ -41,24 +41,24 @@
| 框架 | 说明 | 版本 |
| --- | --- | --- |
| [Vue](https://staging-cn.vuejs.org/) | vue框架 | 3.5.30 |
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 8.0.0 |
| [Vue](https://staging-cn.vuejs.org/) | vue框架 | 3.5.35 |
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 8.0.10 |
| [Ant Design Vue](https://www.antdv.com/) | Ant Design Vue | 4.2.6 |
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.13.5 |
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.14.1 |
| [Naive UI](https://www.naiveui.com/) | Naive UI | 2.44.1 |
| [TDesign](https://tdesign.tencent.com/) | TDesign | 1.18.5 |
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 超集 | 5.9.3 |
| [TDesign](https://tdesign.tencent.com/) | TDesign | 1.20.0 |
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 超集 | 6.0.3 |
| [pinia](https://pinia.vuejs.org/) | Vue 存储库替代 vuex5 | 3.0.4 |
| [vueuse](https://vueuse.org/) | 常用工具集 | 14.2.1 |
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 11.3.0 |
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 5.0.3 |
| [Tailwind CSS](https://tailwindcss.com/) | 原子 CSS | 4.2.1 |
| [Iconify](https://iconify.design/) | 图标组件 | 5.0.0 |
| [Iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.449 |
| [vueuse](https://vueuse.org/) | 常用工具集 | 14.3.0 |
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 11.4.4 |
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 5.1.0 |
| [Tailwind CSS](https://tailwindcss.com/) | 原子 CSS | 4.3.0 |
| [Iconify](https://iconify.design/) | 图标组件 | 5.0.1 |
| [Iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.481 |
| [TinyMCE](https://www.tiny.cloud/) | 富文本编辑器 | 7.3.0 |
| [Echarts](https://echarts.apache.org/) | 图表库 | 6.0.0 |
| [axios](https://axios-http.com/) | http客户端 | 1.13.6 |
| [dayjs](https://day.js.org/) | 日期处理库 | 1.11.20 |
| [Echarts](https://echarts.apache.org/) | 图表库 | 6.1.0 |
| [axios](https://axios-http.com/) | http客户端 | 1.16.1 |
| [dayjs](https://day.js.org/) | 日期处理库 | 1.11.21 |
| [vee-validate](https://vee-validate.logaretm.com/) | 表单验证 | 4.15.1 |
| [zod](https://zod.dev/) | 数据验证 | 3.25.76 |
@ -279,7 +279,6 @@
使用文档:<https://doc.iocoder.cn/im-preview/>
![功能图](/.gitee/image/common/im-feature.png)
| 聊天界面 | 聊天管理 |

View File

@ -29,6 +29,10 @@ export namespace CrmReceivablePlanApi {
returnTime: Date;
};
}
export interface PlanPageParam extends PageParam {
customerId?: number;
contractId?: number;
}
}
/** 查询回款计划列表 */

View File

@ -11,7 +11,7 @@ export namespace RuleSceneApi {
status?: number;
triggers?: Trigger[];
actions?: Action[];
lastTriggeredTime?: Date;
lastTriggerTime?: Date;
createTime?: Date;
}

View File

@ -7,7 +7,7 @@ import { requestClient } from '#/api/request';
export namespace MallTradeStatisticsApi {
/** 交易状况 Request */
export interface TradeTrendReqVO {
times: [Date, Date];
times?: string[];
}
/** 交易统计 Response */

View File

@ -51,5 +51,7 @@ export function deleteAutoCodeRule(id: number) {
/** 导出编码规则 */
export function exportAutoCodeRule(params: PageParam) {
return requestClient.download('/mes/md/auto-code-rule/export-excel', { params });
return requestClient.download('/mes/md/auto-code-rule/export-excel', {
params,
});
}

View File

@ -34,7 +34,9 @@ export namespace MesMdItemApi {
/** 查询物料产品分页 */
export function getItemPage(params: PageParam) {
return requestClient.get<PageResult<MesMdItemApi.Item>>('/mes/md/item/page', { params });
return requestClient.get<PageResult<MesMdItemApi.Item>>('/mes/md/item/page', {
params,
});
}
/** 查询物料产品详情 */

View File

@ -32,9 +32,7 @@ export function getRouteSimpleList() {
/** 查询工艺路线详情 */
export function getRoute(id: number) {
return requestClient.get<MesProRouteApi.Route>(
`/mes/pro/route/get?id=${id}`,
);
return requestClient.get<MesProRouteApi.Route>(`/mes/pro/route/get?id=${id}`);
}
/** 新增工艺路线 */

View File

@ -25,12 +25,16 @@ export function getDefectPage(params: PageParam) {
/** 查询缺陷类型精简列表 */
export function getDefectSimpleList() {
return requestClient.get<MesQcDefectApi.Defect[]>('/mes/qc/defect/simple-list');
return requestClient.get<MesQcDefectApi.Defect[]>(
'/mes/qc/defect/simple-list',
);
}
/** 查询缺陷类型详情 */
export function getDefect(id: number) {
return requestClient.get<MesQcDefectApi.Defect>(`/mes/qc/defect/get?id=${id}`);
return requestClient.get<MesQcDefectApi.Defect>(
`/mes/qc/defect/get?id=${id}`,
);
}
/** 新增缺陷类型 */

View File

@ -25,7 +25,7 @@ export function getDefectRecord(id: number) {
/** 查询质检缺陷记录分页 */
export function getDefectRecordPage(
params: PageParam & { lineId?: number; qcId?: number; qcType?: number; },
params: PageParam & { lineId?: number; qcId?: number; qcType?: number },
) {
return requestClient.get<PageResult<MesQcDefectRecordApi.DefectRecord>>(
'/mes/qc/defect-record/page',

View File

@ -50,10 +50,9 @@ export namespace MesQcIpqcApi {
/** 查询过程检验单分页 */
export function getIpqcPage(params: PageParam) {
return requestClient.get<PageResult<MesQcIpqcApi.Ipqc>>(
'/mes/qc/ipqc/page',
{ params },
);
return requestClient.get<PageResult<MesQcIpqcApi.Ipqc>>('/mes/qc/ipqc/page', {
params,
});
}
/** 查询过程检验单详情 */

View File

@ -24,7 +24,9 @@ export namespace MesQcTemplateIndicatorApi {
}
/** 查询检测指标项分页 */
export function getTemplateIndicatorPage(params: PageParam & { templateId?: number }) {
export function getTemplateIndicatorPage(
params: PageParam & { templateId?: number },
) {
return requestClient.get<
PageResult<MesQcTemplateIndicatorApi.TemplateIndicator>
>('/mes/qc/template/indicator/page', { params });

View File

@ -22,7 +22,9 @@ export namespace MesQcTemplateItemApi {
}
/** 查询产品关联分页 */
export function getTemplateItemPage(params: PageParam & { templateId?: number }) {
export function getTemplateItemPage(
params: PageParam & { templateId?: number },
) {
return requestClient.get<PageResult<MesQcTemplateItemApi.TemplateItem>>(
'/mes/qc/template/item/page',
{ params },

View File

@ -37,16 +37,12 @@ export function getArrivalNotice(id: number) {
}
/** 新增到货通知单 */
export function createArrivalNotice(
data: MesWmArrivalNoticeApi.ArrivalNotice,
) {
export function createArrivalNotice(data: MesWmArrivalNoticeApi.ArrivalNotice) {
return requestClient.post<number>('/mes/wm/arrival-notice/create', data);
}
/** 修改到货通知单 */
export function updateArrivalNotice(
data: MesWmArrivalNoticeApi.ArrivalNotice,
) {
export function updateArrivalNotice(data: MesWmArrivalNoticeApi.ArrivalNotice) {
return requestClient.put('/mes/wm/arrival-notice/update', data);
}

View File

@ -39,10 +39,7 @@ export function getArrivalNoticeLine(id: number) {
export function createArrivalNoticeLine(
data: MesWmArrivalNoticeLineApi.ArrivalNoticeLine,
) {
return requestClient.post<number>(
'/mes/wm/arrival-notice-line/create',
data,
);
return requestClient.post<number>('/mes/wm/arrival-notice-line/create', data);
}
/** 修改到货通知单行 */

View File

@ -73,9 +73,12 @@ export function getBatchPage(params: MesWmBatchApi.PageParams) {
/** 批次向前追溯 */
export function getForwardBatchList(code: string) {
return requestClient.get<MesWmBatchApi.Batch[]>('/mes/wm/batch/forward-list', {
params: { code },
});
return requestClient.get<MesWmBatchApi.Batch[]>(
'/mes/wm/batch/forward-list',
{
params: { code },
},
);
}
/** 批次向后追溯 */

View File

@ -66,5 +66,7 @@ export function cancelMiscReceipt(id: number) {
/** 导出杂项入库单 */
export function exportMiscReceipt(params: any) {
return requestClient.download('/mes/wm/misc-receipt/export-excel', { params });
return requestClient.download('/mes/wm/misc-receipt/export-excel', {
params,
});
}

View File

@ -28,9 +28,10 @@ export namespace MesWmOutsourceIssueDetailApi {
/** 查询外协发料单明细列表 */
export function getOutsourceIssueDetailListByLineId(lineId: number) {
return requestClient.get<
MesWmOutsourceIssueDetailApi.OutsourceIssueDetail[]
>('/mes/wm/outsource-issue-detail/list-by-line', { params: { lineId } });
return requestClient.get<MesWmOutsourceIssueDetailApi.OutsourceIssueDetail[]>(
'/mes/wm/outsource-issue-detail/list-by-line',
{ params: { lineId } },
);
}
/** 查询外协发料单明细详情 */

View File

@ -21,10 +21,9 @@ export namespace MesWmOutsourceReceiptApi {
/** 查询外协入库单分页 */
export function getOutsourceReceiptPage(params: PageParam) {
return requestClient.get<PageResult<MesWmOutsourceReceiptApi.OutsourceReceipt>>(
'/mes/wm/outsource-receipt/page',
{ params },
);
return requestClient.get<
PageResult<MesWmOutsourceReceiptApi.OutsourceReceipt>
>('/mes/wm/outsource-receipt/page', { params });
}
/** 查询外协入库单详情 */

View File

@ -40,7 +40,10 @@ export function getProductIssueDetail(id: number) {
export function createProductIssueDetail(
data: MesWmProductIssueDetailApi.ProductIssueDetail,
) {
return requestClient.post<number>('/mes/wm/product-issue-detail/create', data);
return requestClient.post<number>(
'/mes/wm/product-issue-detail/create',
data,
);
}
/** 修改领料出库明细 */

View File

@ -20,10 +20,9 @@ export namespace MesWmProductIssueLineApi {
/** 查询领料出库单行分页 */
export function getProductIssueLinePage(params: PageParam) {
return requestClient.get<PageResult<MesWmProductIssueLineApi.ProductIssueLine>>(
'/mes/wm/product-issue-line/page',
{ params },
);
return requestClient.get<
PageResult<MesWmProductIssueLineApi.ProductIssueLine>
>('/mes/wm/product-issue-line/page', { params });
}
/** 查询领料出库单行详情 */

View File

@ -42,7 +42,10 @@ export function getProductSalesDetail(id: number) {
export function createProductSalesDetail(
data: MesWmProductSalesDetailApi.ProductSalesDetail,
) {
return requestClient.post<number>('/mes/wm/product-sales-detail/create', data);
return requestClient.post<number>(
'/mes/wm/product-sales-detail/create',
data,
);
}
/** 修改销售出库明细 */

View File

@ -40,7 +40,10 @@ export function getReturnVendorDetail(id: number) {
export function createReturnVendorDetail(
data: MesWmReturnVendorDetailApi.ReturnVendorDetail,
) {
return requestClient.post<number>('/mes/wm/return-vendor-detail/create', data);
return requestClient.post<number>(
'/mes/wm/return-vendor-detail/create',
data,
);
}
/** 修改供应商退货明细 */

View File

@ -21,10 +21,9 @@ export namespace MesWmReturnVendorLineApi {
/** 查询供应商退货单行分页 */
export function getReturnVendorLinePage(params: PageParam) {
return requestClient.get<PageResult<MesWmReturnVendorLineApi.ReturnVendorLine>>(
'/mes/wm/return-vendor-line/page',
{ params },
);
return requestClient.get<
PageResult<MesWmReturnVendorLineApi.ReturnVendorLine>
>('/mes/wm/return-vendor-line/page', { params });
}
/** 查询供应商退货单行详情 */

View File

@ -60,5 +60,7 @@ export function updateStockTakingResult(
/** 删除盘点结果 */
export function deleteStockTakingResult(id: number) {
return requestClient.delete(`/mes/wm/stocktaking-task-result/delete?id=${id}`);
return requestClient.delete(
`/mes/wm/stocktaking-task-result/delete?id=${id}`,
);
}

View File

@ -18,8 +18,7 @@ const routes: RouteRecordRaw[] = [
title: '库区设置',
activePath: '/mes/wm/warehouse',
},
component: () =>
import('#/views/mes/wm/warehouse/location/index.vue'),
component: () => import('#/views/mes/wm/warehouse/location/index.vue'),
},
{
path: 'wm/warehouse/area',

View File

@ -77,7 +77,9 @@ async function handleStatusChange(
row: AiKnowledgeDocumentApi.KnowledgeDocument,
): Promise<boolean | undefined> {
try {
await confirm(`你要将${row.name}的状态切换为【${getDictLabel(DICT_TYPE.COMMON_STATUS, newStatus)}】吗?`);
await confirm(
`你要将${row.name}的状态切换为【${getDictLabel(DICT_TYPE.COMMON_STATUS, newStatus)}】吗?`,
);
} catch {
return false;
}

View File

@ -65,7 +65,9 @@ async function handleStatusChange(
row: AiKnowledgeSegmentApi.KnowledgeSegment,
): Promise<boolean | undefined> {
try {
await confirm(`你要将片段 ${row.id} 的状态切换为【${getDictLabel(DICT_TYPE.COMMON_STATUS, newStatus)}】吗?`);
await confirm(
`你要将片段 ${row.id} 的状态切换为【${getDictLabel(DICT_TYPE.COMMON_STATUS, newStatus)}】吗?`,
);
} catch {
return false;
}

View File

@ -192,7 +192,7 @@ const resetTaskForm = () => {
// extensionElements
// if (businessObject.candidateStrategy != undefined) {
// if (businessObject.candidateStrategy !== undefined) {
// userTaskForm.value.candidateStrategy = parseInt(
// businessObject.candidateStrategy,
// ) as any;

View File

@ -234,7 +234,9 @@ function createFileLinkHtml(file: unknown) {
linkEl.setAttribute('href', String(url));
linkEl.setAttribute('target', '_blank');
linkEl.setAttribute('rel', 'noopener noreferrer');
const fallbackName = String(url).slice(Math.max(0, String(url).lastIndexOf('/') + 1)) || String(url);
const fallbackName =
String(url).slice(Math.max(0, String(url).lastIndexOf('/') + 1)) ||
String(url);
const recordName = record ? getRecordValue(record, 'name') : undefined;
linkEl.textContent = recordName ? String(recordName) : fallbackName;
return linkEl.outerHTML;
@ -247,16 +249,14 @@ function renderFileListHtml(value: unknown) {
.join('<br/>');
}
function mapValuesWithOptions(
value: unknown,
options: FormFieldOption[] = [],
) {
function mapValuesWithOptions(value: unknown, options: FormFieldOption[] = []) {
const values = toValueArray(value);
const labels = values
.map((item) => {
const matched = options.find(
(option) =>
option?.value === item || String(option?.value ?? '') === String(item),
option?.value === item ||
String(option?.value ?? '') === String(item),
);
return escapeHtml(matched?.label ?? String(item));
})
@ -300,9 +300,15 @@ function mapValueWithLabelMap(
* @returns 打印展示时使用的区域部门用户名称映射
*/
async function loadPrintLookupMaps(formFieldsObj: FormFieldRule[]) {
const hasAreaSelect = formFieldsObj.some((item) => item.type === 'AreaSelect');
const hasUserSelect = formFieldsObj.some((item) => item.type === 'UserSelect');
const hasDeptSelect = formFieldsObj.some((item) => item.type === 'DeptSelect');
const hasAreaSelect = formFieldsObj.some(
(item) => item.type === 'AreaSelect',
);
const hasUserSelect = formFieldsObj.some(
(item) => item.type === 'UserSelect',
);
const hasDeptSelect = formFieldsObj.some(
(item) => item.type === 'DeptSelect',
);
const [areaList, userList, deptList] = await Promise.all([
hasAreaSelect ? getAreaTree() : Promise.resolve([]),
@ -335,7 +341,7 @@ function formatPrintField(
rule: FormFieldRule,
value: unknown,
lookupMaps: PrintLookupMaps,
){
) {
const type = String(rule.type ?? '');
switch (type) {
@ -387,8 +393,9 @@ function formatPrintField(
} as const;
const rawValueType = String(getRuleProp(rule, 'valueType') ?? '');
const valueType =
(valueTypeMap as Record<string, 'boolean' | 'number' | 'string'>)[rawValueType] ??
'string';
(valueTypeMap as Record<string, 'boolean' | 'number' | 'string'>)[
rawValueType
] ?? 'string';
const options = getDictOptions(dictType, valueType);
return mapValuesWithOptions(value, options);
}
@ -402,10 +409,9 @@ function formatPrintField(
}
case 'IframeComponent': {
const propsObj = rule.props;
const propsUrl =
isPrintableRecord(propsObj)
? String(getRecordValue(propsObj, 'url') ?? '')
: '';
const propsUrl = isPrintableRecord(propsObj)
? String(getRecordValue(propsObj, 'url') ?? '')
: '';
const iframeUrl = isEmptyValue(value) ? propsUrl : String(value ?? '');
return iframeUrl ? createFileLinkHtml(iframeUrl) : '';
}
@ -446,7 +452,9 @@ function initPrintDataMap() {
printDataMap.value.startUserDept =
printData.value.processInstance.startUser?.deptName || '';
printDataMap.value.processName = printData.value.processInstance.name;
printDataMap.value.processNum = String(printData.value.processInstance.id ?? '');
printDataMap.value.processNum = String(
printData.value.processInstance.id ?? '',
);
printDataMap.value.startTime = formatDate(
printData.value.processInstance.startTime,
);

View File

@ -75,7 +75,9 @@ async function handleDeleteContactBusinessList() {
return;
}
try {
await confirm(`确定要将${checkedRows.value.map((item) => item.name).join(',')}解除关联吗?`);
await confirm(
`确定要将${checkedRows.value.map((item) => item.name).join(',')}解除关联吗?`,
);
} catch {
return false;
}

View File

@ -72,7 +72,9 @@ async function handleDeleteContactBusinessList() {
return;
}
try {
await confirm(`确定要将${checkedRows.value.map((item) => item.name).join(',')}解除关联吗?`);
await confirm(
`确定要将${checkedRows.value.map((item) => item.name).join(',')}解除关联吗?`,
);
} catch {
return false;
}

View File

@ -150,7 +150,9 @@ async function handlePutPool(): Promise<boolean | undefined> {
async function handleUpdateDealStatus(): Promise<boolean | undefined> {
const dealStatus = !customer.value.dealStatus;
try {
await confirm(`确定更新成交状态为【${dealStatus ? '已成交' : '未成交'}】吗?`);
await confirm(
`确定更新成交状态为【${dealStatus ? '已成交' : '未成交'}】吗?`,
);
} catch {
return false;
}

View File

@ -100,7 +100,9 @@ async function handleDelete() {
return;
}
try {
await confirm(`你要将${checkedRows.value.map((item) => item.nickname).join(',')}移出团队吗?`);
await confirm(
`你要将${checkedRows.value.map((item) => item.nickname).join(',')}移出团队吗?`,
);
} catch {
return false;
}

View File

@ -57,12 +57,19 @@ export function useFormSchema(): VbenFormSchema[] {
label: '客户名称',
component: 'ApiSelect',
rules: 'required',
componentProps: {
componentProps: (_values, form) => ({
api: getCustomerSimpleList,
labelField: 'name',
valueField: 'id',
placeholder: '请选择客户',
},
onChange: () => {
form.setFieldValue('contractId', undefined);
form.setFieldValue('planId', undefined);
form.setFieldValue('price', undefined);
form.setFieldValue('returnTime', undefined);
form.setFieldValue('returnType', undefined);
},
}),
dependencies: {
triggerFields: ['id'],
disabled: (values) => values.id,
@ -76,21 +83,33 @@ export function useFormSchema(): VbenFormSchema[] {
dependencies: {
triggerFields: ['customerId'],
disabled: (values) => !values.customerId || values.id,
async componentProps(values) {
if (values.customerId) {
if (!values.id) {
// 特殊:只有在【新增】时,才清空合同编号
values.contractId = undefined;
}
const contracts = await getContractSimpleList(values.customerId);
async componentProps(values, form) {
if (!values.customerId) {
return {
options: contracts.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: '请选择合同',
} as any;
options: [],
placeholder: '请选择客户',
};
}
const contracts = await getContractSimpleList(values.customerId);
return {
options: contracts.map((item) => ({
label: item.name,
value: item.id,
})),
placeholder: '请选择合同',
onChange: (value: number) => {
form.setFieldValue('planId', undefined);
form.setFieldValue('returnTime', undefined);
form.setFieldValue('returnType', undefined);
const contract = contracts.find((item) => item.id === value);
form.setFieldValue(
'price',
contract
? contract.totalPrice - contract.totalReceivablePrice
: undefined,
);
},
} as any;
},
},
},
@ -101,28 +120,38 @@ export function useFormSchema(): VbenFormSchema[] {
rules: 'required',
dependencies: {
triggerFields: ['contractId'],
disabled: (values) => !values.contractId,
async componentProps(values) {
if (values.contractId) {
values.planId = undefined;
const plans = await getReceivablePlanSimpleList(
values.customerId,
values.contractId,
);
disabled: (values) => !values.contractId || values.id,
async componentProps(values, form) {
if (!values.contractId) {
return {
options: plans.map((item) => ({
label: item.period,
value: item.id,
})),
placeholder: '请选择回款期数',
onChange: async (value: any) => {
const plan = await getReceivablePlan(value);
values.returnTime = plan?.returnTime;
values.price = plan?.price;
values.returnType = plan?.returnType;
},
} as any;
options: [],
placeholder: '请选择合同',
};
}
const plans = await getReceivablePlanSimpleList(
values.customerId,
values.contractId,
);
return {
options: plans.map((item) => ({
disabled: !!item.receivableId,
label: `${item.period}`,
value: item.id,
})),
placeholder: '请选择回款期数',
onChange: async (value: any) => {
if (!value) {
form.setFieldValue('returnTime', undefined);
form.setFieldValue('price', undefined);
form.setFieldValue('returnType', undefined);
return;
}
const plan = await getReceivablePlan(value);
form.setFieldValue('returnTime', plan?.returnTime);
form.setFieldValue('price', plan?.price);
form.setFieldValue('returnType', plan?.returnType);
},
} as any;
},
},
},

View File

@ -7,6 +7,7 @@ import { useVbenForm, useVbenModal } from '@vben/common-ui';
import { message } from 'ant-design-vue';
import { getContractSimpleList } from '#/api/crm/contract';
import {
createReceivable,
getReceivable,
@ -16,8 +17,20 @@ import { $t } from '#/locales';
import { useFormSchema } from '../data';
type ReceivablePrefillData = Partial<
Pick<
CrmReceivableApi.Receivable,
'contractId' | 'customerId' | 'price' | 'returnType'
>
> & { id?: number };
type ReceivableFormModalData = ReceivablePrefillData & {
plan?: ReceivablePrefillData;
receivable?: Pick<CrmReceivableApi.Receivable, 'id'>;
};
const emit = defineEmits(['success']);
const formData = ref<CrmReceivableApi.Receivable>();
const formData = ref<Partial<CrmReceivableApi.Receivable>>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['回款'])
@ -36,6 +49,32 @@ const [Form, formApi] = useVbenForm({
showDefaultActions: false,
});
/** 构建新增回款的预填表单 */
async function buildCreateFormData(
plan: ReceivablePrefillData,
): Promise<Partial<CrmReceivableApi.Receivable>> {
const values: Partial<CrmReceivableApi.Receivable> = {
contractId: plan?.contractId,
customerId: plan?.customerId,
};
//
if (plan?.id) {
values.planId = plan.id;
values.price = plan.price;
values.returnType = plan.returnType;
return values;
}
// /
if (values.customerId && values.contractId) {
const contracts = await getContractSimpleList(values.customerId);
const contract = contracts.find((item) => item.id === values.contractId);
if (contract) {
values.price = contract.totalPrice - contract.totalReceivablePrice;
}
}
return values;
}
const [Modal, modalApi] = useVbenModal({
async onConfirm() {
const { valid } = await formApi.validate();
@ -63,31 +102,26 @@ const [Modal, modalApi] = useVbenModal({
return;
}
//
const data = modalApi.getData();
formData.value = undefined;
await formApi.resetForm();
const data = modalApi.getData() as null | ReceivableFormModalData;
if (!data) {
return;
}
const { receivable, plan } = data;
const { receivable } = data;
const plan =
data.plan ?? (data.customerId || data.contractId ? data : undefined);
modalApi.lock();
try {
if (receivable) {
if (receivable?.id) {
formData.value = await getReceivable(receivable.id!);
} else if (plan) {
formData.value = plan.id
? {
planId: plan.id,
price: plan.price,
returnType: plan.returnType,
customerId: plan.customerId,
contractId: plan.contractId,
}
: ({
customerId: plan.customerId,
contractId: plan.contractId,
} as any);
formData.value = await buildCreateFormData(plan);
}
if (formData.value) {
// values
await formApi.setValues(formData.value as any);
}
// values
await formApi.setValues(formData.value as any);
} finally {
modalApi.unlock();
}

View File

@ -238,7 +238,7 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
<template #description> 今日晴20 - 32 </template>
</WorkbenchHeader>
<div class="mt-5 flex flex-col lg:flex-row">
<div class="flex flex-col lg:flex-row">
<div class="mr-4 w-full lg:w-3/5">
<WorkbenchProject :items="projectItems" title="项目" @click="navTo" />
<WorkbenchTrends :items="trendItems" class="mt-5" title="最新动态" />
@ -246,7 +246,7 @@ function navTo(nav: WorkbenchProjectItem | WorkbenchQuickNavItem) {
<div class="w-full lg:w-2/5">
<WorkbenchQuickNav
:items="quickNavItems"
class="mt-5 lg:mt-0"
class="lg:mt-0"
title="快捷导航"
@click="navTo"
/>

View File

@ -176,6 +176,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal
:title="getTitle"
class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'"
>
<Form class="mx-3">

View File

@ -26,7 +26,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
gridOptions: {
columns: usePurchaseInGridColumns(),
height: 'auto',
height: 520,
keepSource: true,
proxyConfig: {
ajax: {
@ -95,14 +95,12 @@ defineExpose({ open: openModal });
<template>
<Modal
class="!w-[50vw]"
v-model:open="open"
title="选择采购入库单"
@ok="handleOk"
width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
>
<Grid
class="max-h-[600px]"
table-title="采购入库单列表(仅展示可付款的单据)"
/>
<Grid table-title="()" />
</Modal>
</template>

View File

@ -26,7 +26,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
gridOptions: {
columns: useSaleReturnGridColumns(),
height: 'auto',
height: 520,
keepSource: true,
proxyConfig: {
ajax: {
@ -99,14 +99,12 @@ defineExpose({ open: openModal });
<template>
<Modal
class="!w-[50vw]"
v-model:open="open"
title="选择采购退货单"
@ok="handleOk"
width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
>
<Grid
class="max-h-[600px]"
table-title="采购退货单列表(仅展示需退款的单据)"
/>
<Grid table-title="退(退)" />
</Modal>
</template>

View File

@ -190,6 +190,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal
:title="getTitle"
class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'"
>
<Form class="mx-3">

View File

@ -26,7 +26,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
gridOptions: {
columns: useSaleOutGridColumns(),
height: 'auto',
height: 520,
keepSource: true,
proxyConfig: {
ajax: {
@ -91,14 +91,12 @@ defineExpose({ open: openModal });
<template>
<Modal
class="!w-[50vw]"
v-model:open="open"
title="选择销售出库单"
@ok="handleOk"
width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
>
<Grid
class="max-h-[600px]"
table-title="销售出库单列表(仅展示可收款的单据)"
/>
<Grid table-title="()" />
</Modal>
</template>

View File

@ -26,7 +26,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
gridOptions: {
columns: useSaleReturnGridColumns(),
height: 'auto',
height: 520,
keepSource: true,
proxyConfig: {
ajax: {
@ -95,14 +95,12 @@ defineExpose({ open: openModal });
<template>
<Modal
class="!w-[50vw]"
v-model:open="open"
title="选择销售退货单"
@ok="handleOk"
width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
>
<Grid
class="max-h-[600px]"
table-title="销售退货单列表(仅展示可退款的单据)"
/>
<Grid table-title="退(退)" />
</Modal>
</template>

View File

@ -206,6 +206,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal
:title="getTitle"
class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'"
>
<Form class="mx-3">

View File

@ -38,7 +38,7 @@ const [Grid] = useVbenVxeGrid({
},
gridOptions: {
columns: useOrderGridColumns(),
height: 'auto',
height: 520,
keepSource: true,
proxyConfig: {
ajax: {
@ -108,12 +108,13 @@ function handleOk() {
</template>
</Input>
<Modal
class="!w-[50vw]"
v-model:open="open"
title="选择关联订单"
@ok="handleOk"
width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
>
<Grid class="max-h-[600px]" table-title="(退)" />
<Grid table-title="(退)" />
</Modal>
</div>
</template>

View File

@ -206,6 +206,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal
:title="getTitle"
class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'"
>
<Form class="mx-3">

View File

@ -38,7 +38,7 @@ const [Grid] = useVbenVxeGrid({
},
gridOptions: {
columns: useOrderGridColumns(),
height: 'auto',
height: 520,
keepSource: true,
proxyConfig: {
ajax: {
@ -108,12 +108,13 @@ function handleOk() {
</template>
</Input>
<Modal
class="!w-[50vw]"
v-model:open="open"
title="选择关联订单"
@ok="handleOk"
width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
>
<Grid class="max-h-[600px]" table-title="(退)" />
<Grid table-title="(退)" />
</Modal>
</div>
</template>

View File

@ -201,6 +201,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal
:title="getTitle"
class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'"
>
<Form class="mx-3">

View File

@ -38,7 +38,7 @@ const [Grid] = useVbenVxeGrid({
},
gridOptions: {
columns: useOrderGridColumns(),
height: 'auto',
height: 520,
keepSource: true,
proxyConfig: {
ajax: {
@ -108,12 +108,13 @@ function handleOk() {
</template>
</Input>
<Modal
class="!w-[50vw]"
v-model:open="open"
title="选择关联订单"
@ok="handleOk"
width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
>
<Grid class="max-h-[600px]" table-title="()" />
<Grid table-title="()" />
</Modal>
</div>
</template>

View File

@ -206,6 +206,7 @@ const [Modal, modalApi] = useVbenModal({
<Modal
:title="getTitle"
class="w-3/4"
:close-on-click-modal="false"
:show-confirm-button="formType !== 'detail'"
>
<Form class="mx-3">

View File

@ -38,7 +38,7 @@ const [Grid] = useVbenVxeGrid({
},
gridOptions: {
columns: useOrderGridColumns(),
height: 'auto',
height: 520,
keepSource: true,
proxyConfig: {
ajax: {
@ -108,12 +108,13 @@ function handleOk() {
</template>
</Input>
<Modal
class="!w-[50vw]"
v-model:open="open"
title="选择关联订单"
@ok="handleOk"
width="80%"
@cancel.stop="open = false"
@ok.stop="handleOk"
>
<Grid class="max-h-[600px]" table-title="(退)" />
<Grid table-title="(退)" />
</Modal>
</div>
</template>

View File

@ -42,7 +42,9 @@ function handleDetail(row: InfraApiErrorLogApi.ApiErrorLog) {
/** 处理已处理 / 已忽略的操作 */
async function handleProcess(id: number, processStatus: number) {
await confirm(`确认标记为${InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略'}?`);
await confirm(
`确认标记为${InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略'}?`,
);
const hideLoading = message.loading({
content: '正在处理中...',
duration: 0,

View File

@ -19,7 +19,9 @@ import { NotifyTemplateSelect } from '#/views/system/notify/template/components'
import { SmsTemplateSelect } from '#/views/system/sms/template/components';
function hasReceiveType(values: Partial<Record<string, any>>, type: number) {
return Array.isArray(values.receiveTypes) && values.receiveTypes.includes(type);
return (
Array.isArray(values.receiveTypes) && values.receiveTypes.includes(type)
);
}
/** 新增/修改告警配置的表单 */
@ -155,7 +157,8 @@ export function useFormSchema(): VbenFormSchema[] {
component: markRaw(NotifyTemplateSelect),
dependencies: {
triggerFields: ['receiveTypes'],
show: (values) => hasReceiveType(values, IotAlertReceiveTypeEnum.NOTIFY),
show: (values) =>
hasReceiveType(values, IotAlertReceiveTypeEnum.NOTIFY),
trigger: async (values, formApi) => {
if (
!hasReceiveType(values, IotAlertReceiveTypeEnum.NOTIFY) &&

View File

@ -79,16 +79,13 @@ export function useAdvancedFormSchema(): VbenFormSchema[] {
},
rules: z
.string()
.refine(
(value) => {
const length = value.replaceAll(
/[\u4E00-\u9FA5\u3040-\u30FF]/g,
'aa',
).length;
return length >= 4 && length <= 64;
},
'备注名称长度限制为 4~64 个字符,中文及日文算 2 个字符',
)
.refine((value) => {
const length = value.replaceAll(
/[\u4E00-\u9FA5\u3040-\u30FF]/g,
'aa',
).length;
return length >= 4 && length <= 64;
}, '备注名称长度限制为 4~64 个字符,中文及日文算 2 个字符')
.refine(
(value) => /^[\u4E00-\u9FA5\u3040-\u30FF\w]+$/.test(value),
'备注名称只能包含中文、英文字母、日文、数字和下划线_',

View File

@ -38,7 +38,7 @@ const mapDialogRef = ref<InstanceType<typeof MapDialog>>();
/** 是否有位置信息(合法经纬度 0 不应视为空) */
const hasLocation = computed(() => {
return props.device.longitude != null && props.device.latitude != null;
return props.device.longitude !== null && props.device.latitude !== null;
});
/** 打开地图弹窗 */

View File

@ -36,7 +36,9 @@ function handleEdit(row: IotDeviceGroupApi.DeviceGroup) {
/** 删除设备分组 */
async function handleDelete(row: IotDeviceGroupApi.DeviceGroup) {
if (row.deviceCount && row.deviceCount > 0) {
message.warning(`分组「${row.name}」下存在 ${row.deviceCount} 台设备,无法删除`);
message.warning(
`分组「${row.name}」下存在 ${row.deviceCount} 台设备,无法删除`,
);
return;
}
const hideLoading = message.loading({

View File

@ -24,11 +24,12 @@ const deviceList = ref<IotDeviceApi.Device[]>([]); // 设备分布列表
const hasData = computed(() => deviceList.value.length > 0); //
const stateOptions = computed(() =>
getDictOptions(
DICT_TYPE.IOT_DEVICE_STATE,
'number',
) as NumberDictDataType[],
const stateOptions = computed(
() =>
getDictOptions(
DICT_TYPE.IOT_DEVICE_STATE,
'number',
) as NumberDictDataType[],
); //
const stateColorMap: Record<number, string> = {

View File

@ -13,11 +13,7 @@ import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { deleteOtaFirmware, getOtaFirmwarePage } from '#/api/iot/ota/firmware';
import { $t } from '#/locales';
import {
getProductName,
useGridColumns,
useGridFormSchema,
} from './data';
import { getProductName, useGridColumns, useGridFormSchema } from './data';
import OtaFirmwareForm from './modules/form.vue';
const { push } = useRouter();

View File

@ -53,7 +53,9 @@ const queryParams = ref({
/** 获取分类名称 */
function getCategoryName(item: any) {
const category = props.categoryList.find((c: any) => c.id === item.categoryId);
const category = props.categoryList.find(
(c: any) => c.id === item.categoryId,
);
return item.categoryName || category?.name || '未分类';
}
@ -144,11 +146,7 @@ onMounted(() => {
alt=""
class="size-6 object-contain"
/>
<IconifyIcon
v-else
:icon="item.icon"
class="text-xl"
/>
<IconifyIcon v-else :icon="item.icon" class="text-xl" />
</div>
<div class="ml-3 min-w-0 flex-1">
<div

View File

@ -69,10 +69,10 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
slots: { default: 'actionsCol' },
},
{
field: 'lastTriggeredTime',
field: 'lastTriggerTime',
title: '最近触发',
width: 180,
slots: { default: 'lastTriggeredTime' },
slots: { default: 'lastTriggerTime' },
},
{
field: 'createTime',

View File

@ -265,9 +265,7 @@ function handlePropertyChange(propertyInfo: any) {
triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST
"
:value="condition.value"
@update:value="
(value) => updateConditionField('value', value)
"
@update:value="(value) => updateConditionField('value', value)"
placeholder="留空则事件发生即匹配"
/>
<!-- 普通值输入 -->

View File

@ -47,7 +47,7 @@ const allOperators = [
label: IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS.name,
symbol: '≠',
description: '值不相等时触发',
example: 'power != false',
example: 'power !== false',
supportedTypes: [
IoTDataSpecsDataTypeEnum.INT,
IoTDataSpecsDataTypeEnum.FLOAT,

View File

@ -354,9 +354,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
<Tag color="success" class="m-0">{{ getActionSummary(row) }}</Tag>
</template>
<!-- 最近触发列 -->
<template #lastTriggeredTime="{ row }">
<span v-if="row.lastTriggeredTime">
{{ formatDateTime(row.lastTriggeredTime) }}
<template #lastTriggerTime="{ row }">
<span v-if="row.lastTriggerTime">
{{ formatDateTime(row.lastTriggerTime) }}
</span>
<span v-else class="text-muted-foreground">未触发</span>
</template>

View File

@ -216,10 +216,7 @@ function removeDataSpecs(val: any) {
label="标识符"
name="identifier"
>
<Input
v-model:value="formData.identifier"
placeholder="请输入标识符"
/>
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
</Form.Item>
<!-- 属性配置 -->
<ThingModelProperty

View File

@ -3,6 +3,7 @@ import { onActivated, onMounted, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { CountTo } from '@vben/common-ui';
import { fenToYuan } from '@vben/utils';
import { Card } from 'ant-design-vue';
@ -78,7 +79,7 @@ async function loadProductData() {
/** 查询钱包充值数据 */
async function loadWalletRechargeData() {
const paySummary = await getWalletRechargePrice();
data.rechargePrice.value = paySummary.rechargePrice;
data.rechargePrice.value = Number(fenToYuan(paySummary.rechargePrice || 0));
}
/** 跳转到对应页面 */

View File

@ -130,11 +130,6 @@ const [Modal, modalApi] = useVbenModal({
await modalApi.close();
emit('success');
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
return;
}
},
});
</script>

View File

@ -1,6 +1,5 @@
<script lang="ts" setup>
import type { MallBannerApi } from '#/api/mall/promotion/banner';
import type { SystemUserApi } from '#/api/system/user';
import { computed, ref } from 'vue';
@ -19,7 +18,7 @@ import { $t } from '#/locales';
import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<SystemUserApi.User>();
const formData = ref<MallBannerApi.Banner>();
const getTitle = computed(() => {
return formData.value?.id
? $t('ui.actionTitle.edit', ['Banner'])

View File

@ -234,12 +234,10 @@ const [Modal, modalApi] = useVbenModal({
>();
if (props.multiple && Array.isArray(data) && data.length > 0) {
setTimeout(() => {
const tableData = gridApi.grid.getTableData().fullData;
const tableData = gridApi.grid.getTableData()
.fullData as MallCombinationActivityApi.CombinationActivity[];
data.forEach((activity) => {
const row = tableData.find(
(item: MallCombinationActivityApi.CombinationActivity) =>
item.id === activity.id,
);
const row = tableData.find((item) => item.id === activity.id);
if (row) {
gridApi.grid.setCheckboxRow(row, true);
}
@ -247,11 +245,9 @@ const [Modal, modalApi] = useVbenModal({
}, 300);
} else if (!props.multiple && data && !Array.isArray(data)) {
setTimeout(() => {
const tableData = gridApi.grid.getTableData().fullData;
const row = tableData.find(
(item: MallCombinationActivityApi.CombinationActivity) =>
item.id === data.id,
);
const tableData = gridApi.grid.getTableData()
.fullData as MallCombinationActivityApi.CombinationActivity[];
const row = tableData.find((item) => item.id === data.id);
if (row) {
gridApi.grid.setRadioRow(row);
}

View File

@ -1,4 +1,6 @@
<script setup lang="ts">
import type { Ref } from 'vue';
import type { ComponentStyle } from '../util';
import { useVModel } from '@vueuse/core';
@ -28,6 +30,7 @@ defineOptions({ name: 'ComponentContainer' });
const props = defineProps<{ modelValue: ComponentStyle }>();
const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
const formStyleValues = formData as unknown as Ref<Record<string, number>>;
const treeData: any[] = [
{
@ -182,9 +185,7 @@ function handleSliderChange(prop: string) {
<Row>
<Col :span="19">
<Slider
v-model:value="
formData[dataRef.prop as keyof ComponentStyle]
"
v-model:value="formStyleValues[dataRef.prop]"
:max="100"
:min="0"
@change="handleSliderChange(dataRef.prop)"
@ -196,9 +197,7 @@ function handleSliderChange(prop: string) {
class="w-[50px]"
:max="100"
:min="0"
v-model:value="
formData[dataRef.prop as keyof ComponentStyle]
"
v-model:value="formStyleValues[dataRef.prop]"
/>
</Col>
</Row>

View File

@ -1,4 +1,4 @@
import type { StyleValue } from 'vue';
import type { CSSProperties } from 'vue';
import type { HotZoneItemProperty } from '../../config';
@ -16,7 +16,7 @@ export enum CONTROL_TYPE_ENUM {
export interface ControlDot {
position: string;
types: CONTROL_TYPE_ENUM[];
style: StyleValue;
style: CSSProperties;
}
/** 热区的 8 个控制点 */

View File

@ -1,3 +1,4 @@
import type { Rect } from '../../../../magic-cube-editor/util';
import type { ComponentStyle, DiyComponent } from '../../../util';
/** 广告魔方属性 */
@ -10,13 +11,9 @@ export interface MagicCubeProperty {
}
/** 广告魔方项目属性 */
export interface MagicCubeItemProperty {
export interface MagicCubeItemProperty extends Rect {
imgUrl: string; // 图标链接
url: string; // 链接
width: number; // 宽
height: number; // 高
top: number; // 上
left: number; // 左
}
/** 定义组件 */

View File

@ -1,3 +1,4 @@
import type { Rect } from '../../../../magic-cube-editor/util';
import type { DiyComponent } from '../../../util';
/** 顶部导航栏属性 */
@ -16,12 +17,8 @@ export interface NavigationBarProperty {
}
/** 顶部导航栏 - 单元格 属性 */
export interface NavigationBarCellProperty {
export interface NavigationBarCellProperty extends Rect {
type: 'image' | 'search' | 'text'; // 类型:文字 | 图片 | 搜索框
width: number; // 宽度
height: number; // 高度
top: number; // 顶部位置
left: number; // 左侧位置
text: string; // 文字内容
textColor: string; // 文字颜色
imgUrl: string; // 图片地址

View File

@ -22,7 +22,9 @@ const emit = defineEmits(['update:modelValue']);
const formData = useVModel(props, 'modelValue', emit);
const rules = {
content: [{ required: true, message: '请输入公告', trigger: 'blur' }],
content: [
{ required: true, message: '请输入公告', trigger: 'blur' as const },
],
}; //
</script>

View File

@ -44,7 +44,6 @@ const emits = defineEmits(['reset', 'save', 'update:modelValue']); // 工具栏
// margin: 4,
// }); //
const componentLibrary = ref(); //
const pageConfigComponent = ref<DiyComponent<any>>(
cloneDeep(PAGE_CONFIG_COMPONENT),
); //
@ -334,11 +333,7 @@ onMounted(() => {
<Row class="mt-4 h-[calc(80vh)]">
<!-- 左侧组件库ComponentLibrary -->
<Col :span="6">
<ComponentLibrary
v-if="libs && libs.length > 0"
ref="componentLibrary"
:list="libs"
/>
<ComponentLibrary v-if="libs && libs.length > 0" :list="libs" />
</Col>
<!-- 中心设计区域ComponentContainer -->
<Col :span="12">

View File

@ -61,7 +61,9 @@ async function handleStatusChange(
row: MallCouponTemplateApi.CouponTemplate,
): Promise<boolean | undefined> {
try {
await confirm(`你要将${row.name}的状态切换为【${newStatus === CommonStatusEnum.ENABLE ? '启用' : '停用'}】吗?`);
await confirm(
`你要将${row.name}的状态切换为【${newStatus === CommonStatusEnum.ENABLE ? '启用' : '停用'}】吗?`,
);
} catch {
return false;
}

View File

@ -4,9 +4,9 @@ import type { Emoji } from './tools/emoji';
import type { MallKefuConversationApi } from '#/api/mall/promotion/kefu/conversation';
import type { MallKefuMessageApi } from '#/api/mall/promotion/kefu/message';
import { computed, KeFuMessageContentTypeEnum, reactive, ref, toRefs, unref, watch } from 'vue';
import { computed, reactive, ref, toRefs, unref, watch } from 'vue';
import { UserTypeEnum } from '@vben/constants';
import { KeFuMessageContentTypeEnum, UserTypeEnum } from '@vben/constants';
import { IconifyIcon } from '@vben/icons';
import { formatDate, isEmpty, jsonParse } from '@vben/utils';

View File

@ -196,12 +196,10 @@ const [Modal, modalApi] = useVbenModal({
>();
if (props.multiple && Array.isArray(data) && data.length > 0) {
setTimeout(() => {
const tableData = gridApi.grid.getTableData().fullData;
const tableData = gridApi.grid.getTableData()
.fullData as MallPointActivityApi.PointActivity[];
data.forEach((activity) => {
const row = tableData.find(
(item: MallPointActivityApi.PointActivity) =>
item.id === activity.id,
);
const row = tableData.find((item) => item.id === activity.id);
if (row) {
gridApi.grid.setCheckboxRow(row, true);
}
@ -209,10 +207,9 @@ const [Modal, modalApi] = useVbenModal({
}, 300);
} else if (!props.multiple && data && !Array.isArray(data)) {
setTimeout(() => {
const tableData = gridApi.grid.getTableData().fullData;
const row = tableData.find(
(item: MallPointActivityApi.PointActivity) => item.id === data.id,
);
const tableData = gridApi.grid.getTableData()
.fullData as MallPointActivityApi.PointActivity[];
const row = tableData.find((item) => item.id === data.id);
if (row) {
gridApi.grid.setRadioRow(row);
}

View File

@ -104,7 +104,10 @@ const hasFestivalDay = computed(() => {
<div class="flex h-full flex-col overflow-hidden p-1">
<!-- 顶部日期数字 + 上班/休息标签 -->
<div class="flex shrink-0 items-center justify-between">
<span class="text-base font-medium" :class="{ 'text-red-500': isWeekend }">
<span
class="text-base font-medium"
:class="{ 'text-red-500': isWeekend }"
>
{{ dayNumber }}
</span>
<Tag v-if="isHoliday" color="green" class="!m-0"> </Tag>

View File

@ -26,7 +26,9 @@ const legendItems: Array<{ color: string; label: string }> = [
{{ item.label }}
</span>
<span class="flex items-center gap-1">
<span class="inline-block h-2.5 w-2.5 shrink-0 rounded-sm bg-[#f56c6c] opacity-60"></span>
<span
class="inline-block h-2.5 w-2.5 shrink-0 rounded-sm bg-[#f56c6c] opacity-60"
></span>
<span class="text-red-500">红色日期</span>
= 周末
</span>

View File

@ -54,7 +54,9 @@ onMounted(async () => {
<template>
<div class="flex">
<!-- 左侧班组列表选择 -->
<div class="border-border mr-3 w-[150px] shrink-0 overflow-hidden rounded border">
<div
class="border-border mr-3 w-[150px] shrink-0 overflow-hidden rounded border"
>
<div
v-for="team in teamList"
:key="team.id"

View File

@ -55,7 +55,9 @@ onMounted(() => {
<template>
<div class="flex">
<!-- 左侧班组类型选择 -->
<div class="border-border mr-3 w-[150px] shrink-0 overflow-hidden rounded border">
<div
class="border-border mr-3 w-[150px] shrink-0 overflow-hidden rounded border"
>
<div
v-for="item in typeOptions"
:key="item.value as number"

View File

@ -104,7 +104,12 @@ function getLunarInfo(day: string) {
termName,
};
} catch {
return { lunarFestival: '', lunarText: '', solarFestival: '', termName: '' };
return {
lunarFestival: '',
lunarText: '',
solarFestival: '',
termName: '',
};
}
}

View File

@ -35,9 +35,15 @@ const [Modal, modalApi] = useVbenModal({
}
modalApi.lock();
//
const data = (await formApi.getValues()) as MesCalHolidayApi.Holiday & { dayDisplay?: string };
const data = (await formApi.getValues()) as MesCalHolidayApi.Holiday & {
dayDisplay?: string;
};
try {
await saveHoliday({ day: data.day, type: data.type, remark: data.remark });
await saveHoliday({
day: data.day,
type: data.type,
remark: data.remark,
});
//
await modalApi.close();
emit('success');
@ -54,7 +60,7 @@ const [Modal, modalApi] = useVbenModal({
if (!data?.day) {
return;
}
const timestamp = dayjs(data.day + ' 00:00:00').valueOf();
const timestamp = dayjs(`${data.day} 00:00:00`).valueOf();
await formApi.setValues({
day: timestamp,
dayDisplay: data.day,
@ -63,7 +69,9 @@ const [Modal, modalApi] = useVbenModal({
});
modalApi.lock();
try {
const holiday = await getHolidayByDay(dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss'));
const holiday = await getHolidayByDay(
dayjs(timestamp).format('YYYY-MM-DD HH:mm:ss'),
);
if (holiday) {
await formApi.setValues({
type: holiday.type ?? HolidayType.WORKDAY,

View File

@ -4,7 +4,13 @@ import type { MesCalPlanApi } from '#/api/mes/cal/plan';
import { h } from 'vue';
import { DICT_TYPE, MesAutoCodeRuleCode, MesCalPlanStatusEnum, MesCalShiftMethodEnum, MesCalShiftTypeEnum } from '@vben/constants';
import {
DICT_TYPE,
MesAutoCodeRuleCode,
MesCalPlanStatusEnum,
MesCalShiftMethodEnum,
MesCalShiftTypeEnum,
} from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { Button } from 'ant-design-vue';
@ -55,7 +61,9 @@ export function useFormSchema(
{
type: 'default',
onClick: async () => {
const code = await generateAutoCode(MesAutoCodeRuleCode.CAL_PLAN_CODE);
const code = await generateAutoCode(
MesAutoCodeRuleCode.CAL_PLAN_CODE,
);
await formApi?.setFieldValue('code', code);
},
},
@ -126,7 +134,8 @@ export function useFormSchema(
},
dependencies: {
triggerFields: ['shiftType'],
show: (values) => !!values.shiftType && values.shiftType !== MesCalShiftTypeEnum.SINGLE,
show: (values) =>
!!values.shiftType && values.shiftType !== MesCalShiftTypeEnum.SINGLE,
},
},
{
@ -237,8 +246,18 @@ export function useGridColumns(): VxeTableGridOptions<MesCalPlanApi.Plan>['colum
props: { type: DICT_TYPE.MES_CAL_CALENDAR_TYPE },
},
},
{ field: 'startDate', title: '开始日期', width: 150, formatter: 'formatDate' },
{ field: 'endDate', title: '结束日期', width: 150, formatter: 'formatDate' },
{
field: 'startDate',
title: '开始日期',
width: 150,
formatter: 'formatDate',
},
{
field: 'endDate',
title: '结束日期',
width: 150,
formatter: 'formatDate',
},
{
field: 'shiftType',
title: '轮班方式',
@ -266,7 +285,12 @@ export function useGridColumns(): VxeTableGridOptions<MesCalPlanApi.Plan>['colum
props: { type: DICT_TYPE.MES_CAL_PLAN_STATUS },
},
},
{ field: 'createTime', title: '创建时间', width: 180, formatter: 'formatDateTime' },
{
field: 'createTime',
title: '创建时间',
width: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
width: 160,

View File

@ -72,7 +72,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }, formValues) =>
await getPlanPage({ pageNo: page.currentPage, pageSize: page.pageSize, ...formValues }),
await getPlanPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
}),
},
},
rowConfig: {

View File

@ -11,7 +11,12 @@ import { MesCalPlanStatusEnum } from '@vben/constants';
import { Button, message, Popconfirm, Tabs } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { confirmPlan, createPlan, getPlan, updatePlan } from '#/api/mes/cal/plan';
import {
confirmPlan,
createPlan,
getPlan,
updatePlan,
} from '#/api/mes/cal/plan';
import { $t } from '#/locales';
import { useFormSchema } from '../data';
@ -24,7 +29,9 @@ const subTabsName = ref('shift'); // 当前资源页签
const formData = ref<MesCalPlanApi.Plan>();
const isDetail = computed(() => formType.value === 'detail'); //
const canConfirm = computed(
() => formType.value === 'update' && formData.value?.status === MesCalPlanStatusEnum.PREPARE,
() =>
formType.value === 'update' &&
formData.value?.status === MesCalPlanStatusEnum.PREPARE,
); //
const getTitle = computed(() => {
if (formType.value === 'detail') {
@ -85,7 +92,11 @@ const [Modal, modalApi] = useVbenModal({
try {
if (formType.value === 'create') {
const id = await createPlan(data);
formData.value = { ...data, id: id as number, status: MesCalPlanStatusEnum.PREPARE };
formData.value = {
...data,
id: id as number,
status: MesCalPlanStatusEnum.PREPARE,
};
await formApi.setFieldValue('id', id);
formType.value = 'update';
} else {

View File

@ -18,14 +18,19 @@ import {
} from '#/api/mes/cal/plan/shift';
import { $t } from '#/locales';
const props = withDefaults(defineProps<{ formType?: FormType; planId: number }>(), {
formType: 'update',
});
const props = withDefaults(
defineProps<{ formType?: FormType; planId: number }>(),
{
formType: 'update',
},
);
const isEditable = computed(() => props.formType !== 'detail'); //
const formOpen = ref(false); //
const formLoading = ref(false); //
const shiftFormType = ref<'create' | 'update'>('create'); //
const formTitle = computed(() => (shiftFormType.value === 'create' ? '添加班次' : '修改班次'));
const formTitle = computed(() =>
shiftFormType.value === 'create' ? '添加班次' : '修改班次',
);
const list = ref<MesCalPlanShiftApi.PlanShift[]>([]); //
const [Form, formApi] = useVbenForm({
@ -157,7 +162,10 @@ async function getList() {
}
/** 打开班次表单 */
async function openForm(type: 'create' | 'update', row?: MesCalPlanShiftApi.PlanShift) {
async function openForm(
type: 'create' | 'update',
row?: MesCalPlanShiftApi.PlanShift,
) {
formOpen.value = true;
shiftFormType.value = type;
await formApi.resetForm();
@ -173,7 +181,9 @@ async function submitForm() {
formLoading.value = true;
try {
const data = (await formApi.getValues()) as MesCalPlanShiftApi.PlanShift;
await (shiftFormType.value === 'create' ? createPlanShift(data) : updatePlanShift(data));
await (shiftFormType.value === 'create'
? createPlanShift(data)
: updatePlanShift(data));
formOpen.value = false;
message.success($t('ui.actionMessage.operationSuccess'));
await getList();
@ -204,14 +214,24 @@ watch(
<div>
<div v-if="isEditable" class="mb-3 flex items-center justify-start">
<TableAction
:actions="[{ label: '添加班次', type: 'primary', onClick: openForm.bind(null, 'create') }]"
:actions="[
{
label: '添加班次',
type: 'primary',
onClick: openForm.bind(null, 'create'),
},
]"
/>
</div>
<Grid class="w-full">
<template #actions="{ row }">
<TableAction
:actions="[
{ label: '编辑', type: 'link', onClick: openForm.bind(null, 'update', row) },
{
label: '编辑',
type: 'link',
onClick: openForm.bind(null, 'update', row),
},
{
label: '删除',
type: 'link',

View File

@ -11,14 +11,21 @@ import { computed, ref, watch } from 'vue';
import { Card, message } from 'ant-design-vue';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { createPlanTeam, deletePlanTeam, getPlanTeamListByPlan } from '#/api/mes/cal/plan/team';
import {
createPlanTeam,
deletePlanTeam,
getPlanTeamListByPlan,
} from '#/api/mes/cal/plan/team';
import { getTeamMemberListByTeam } from '#/api/mes/cal/team/member';
import { $t } from '#/locales';
import { CalTeamSelectDialog } from '#/views/mes/cal/team/components';
const props = withDefaults(defineProps<{ formType?: FormType; planId: number }>(), {
formType: 'update',
});
const props = withDefaults(
defineProps<{ formType?: FormType; planId: number }>(),
{
formType: 'update',
},
);
const isEditable = computed(() => props.formType !== 'detail'); //
const list = ref<MesCalPlanTeamApi.PlanTeam[]>([]); //
const memberList = ref<MesCalTeamMemberApi.TeamMember[]>([]); //
@ -61,7 +68,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
} as VxeTableGridOptions<MesCalPlanTeamApi.PlanTeam>,
gridEvents: {
cellClick: ({ row }: { row: MesCalPlanTeamApi.PlanTeam }) => handleTeamSelect(row),
cellClick: ({ row }: { row: MesCalPlanTeamApi.PlanTeam }) =>
handleTeamSelect(row),
},
});
@ -123,13 +131,17 @@ async function handleTeamSelect(row?: MesCalPlanTeamApi.PlanTeam) {
/** 打开班组选择弹窗 */
function openTeamSelect() {
teamDialogRef.value?.open(list.value.map((item) => item.teamId!).filter(Boolean));
teamDialogRef.value?.open(
list.value.map((item) => item.teamId!).filter(Boolean),
);
}
/** 处理班组选择 */
async function handleTeamsSelected(rows: MesCalTeamApi.Team[]) {
const existingTeamIds = new Set(list.value.map((item) => item.teamId));
const newTeams = rows.filter((team) => team.id && !existingTeamIds.has(team.id));
const newTeams = rows.filter(
(team) => team.id && !existingTeamIds.has(team.id),
);
if (newTeams.length === 0) {
message.warning('所选班组已全部添加过');
return;
@ -139,7 +151,7 @@ async function handleTeamsSelected(rows: MesCalTeamApi.Team[]) {
for (const team of newTeams) {
await createPlanTeam({ planId: props.planId, teamId: team.id });
}
message.success('成功添加 ' + newTeams.length + ' 个班组');
message.success(`成功添加 ${newTeams.length} 个班组`);
await getList();
} finally {
gridApi.setLoading(false);
@ -170,7 +182,11 @@ watch(
<template>
<div>
<div v-if="isEditable" class="mb-3 flex items-center justify-start">
<TableAction :actions="[{ label: '添加班组', type: 'primary', onClick: openTeamSelect }]" />
<TableAction
:actions="[
{ label: '添加班组', type: 'primary', onClick: openTeamSelect },
]"
/>
</div>
<div class="grid grid-cols-5 gap-4">
<div class="col-span-3">
@ -195,15 +211,23 @@ watch(
<div class="col-span-2">
<Card class="h-full" size="small">
<template #title>
{{ selectedTeamName ? `${selectedTeamName}」班组成员` : '班组成员' }}
{{
selectedTeamName ? `${selectedTeamName}」班组成员` : '班组成员'
}}
</template>
<div v-if="!selectedTeamId">
<div class="py-8 text-center text-gray-400">请点击左侧班组查看成员</div>
<div class="py-8 text-center text-gray-400">
请点击左侧班组查看成员
</div>
</div>
<MemberGrid v-else class="w-full" />
</Card>
</div>
</div>
<CalTeamSelectDialog ref="teamDialogRef" :multiple="true" @selected="handleTeamsSelected" />
<CalTeamSelectDialog
ref="teamDialogRef"
:multiple="true"
@selected="handleTeamsSelected"
/>
</div>
</template>

View File

@ -33,7 +33,8 @@ function handleCellDblclick({ row }: { row: MesCalTeamApi.Team }) {
const records = gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
const checked = records.some((item) => item.id === row.id);
gridApi.grid.setCheckboxRow(row, !checked);
selectedRows.value = gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
selectedRows.value =
gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
return;
}
selectedRows.value = [row];
@ -51,7 +52,8 @@ function applyPreSelection() {
gridApi.grid.setCheckboxRow(row, true);
}
}
selectedRows.value = gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
selectedRows.value =
gridApi.grid.getCheckboxRecords() as MesCalTeamApi.Team[];
}
const [Grid, gridApi] = useVbenVxeGrid({
@ -70,7 +72,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }, formValues) =>
await getTeamPage({ pageNo: page.currentPage, pageSize: page.pageSize, ...formValues }),
await getTeamPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
...formValues,
}),
},
},
rowConfig: {
@ -97,7 +103,10 @@ async function resetQueryState() {
}
/** 打开班组选择弹窗 */
async function openModal(selectedIds?: number[], options?: { multiple?: boolean }) {
async function openModal(
selectedIds?: number[],
options?: { multiple?: boolean },
) {
open.value = true;
multiple.value = options?.multiple ?? true;
preSelectedIds.value = selectedIds || [];
@ -120,7 +129,10 @@ function handleConfirm() {
message.warning(multiple.value ? '请至少选择一条数据' : '请选择一条数据');
return;
}
emit('selected', multiple.value ? selectedRows.value : [selectedRows.value[0]!]);
emit(
'selected',
multiple.value ? selectedRows.value : [selectedRows.value[0]!],
);
open.value = false;
}

View File

@ -52,7 +52,9 @@ function openDialog() {
if (props.disabled) {
return;
}
dialogRef.value?.open(props.modelValue ? [props.modelValue] : [], { multiple: false });
dialogRef.value?.open(props.modelValue ? [props.modelValue] : [], {
multiple: false,
});
}
/** 处理弹窗选择 */
@ -79,6 +81,10 @@ onMounted(loadTeamList);
@change="handleChange"
/>
<Button :disabled="disabled" @click="openDialog"></Button>
<CalTeamSelectDialog ref="dialogRef" :multiple="false" @selected="handleSelected" />
<CalTeamSelectDialog
ref="dialogRef"
:multiple="false"
@selected="handleSelected"
/>
</div>
</template>

View File

@ -47,7 +47,9 @@ export function useFormSchema(
{
type: 'default',
onClick: async () => {
const code = await generateAutoCode(MesAutoCodeRuleCode.CAL_TEAM_CODE);
const code = await generateAutoCode(
MesAutoCodeRuleCode.CAL_TEAM_CODE,
);
await formApi?.setFieldValue('code', code);
},
},
@ -145,7 +147,12 @@ export function useGridColumns(): VxeTableGridOptions<MesCalTeamApi.Team>['colum
},
},
{ field: 'remark', title: '备注', minWidth: 180 },
{ field: 'createTime', title: '创建时间', width: 180, formatter: 'formatDateTime' },
{
field: 'createTime',
title: '创建时间',
width: 180,
formatter: 'formatDateTime',
},
{
title: '操作',
width: 180,

View File

@ -16,7 +16,6 @@ import { $t } from '#/locales';
import { useFormSchema } from '../data';
import MemberList from './member-list.vue';
const emit = defineEmits(['success']);
const formType = ref<FormType>('create'); //
const subTabsName = ref('member'); //

View File

@ -18,10 +18,15 @@ import {
import { getSimpleUserList } from '#/api/system/user';
import { $t } from '#/locales';
const props = withDefaults(defineProps<{ formType?: FormType; teamId: number }>(), {
formType: 'update',
});
const isEditable = computed(() => ['create', 'update'].includes(props.formType)); //
const props = withDefaults(
defineProps<{ formType?: FormType; teamId: number }>(),
{
formType: 'update',
},
);
const isEditable = computed(() =>
['create', 'update'].includes(props.formType),
); //
const formOpen = ref(false); //
const formLoading = ref(false); //
const list = ref<MesCalTeamMemberApi.TeamMember[]>([]); //
@ -163,7 +168,9 @@ watch(
<template>
<div>
<div v-if="isEditable" class="mb-3 flex items-center justify-start">
<TableAction :actions="[{ label: '添加成员', type: 'primary', onClick: openForm }]" />
<TableAction
:actions="[{ label: '添加成员', type: 'primary', onClick: openForm }]"
/>
</div>
<Grid class="w-full">
<template #actions="{ row }">

Some files were not shown because too many files have changed in this diff Show More