!372 fix(vben): 对齐 antd/next/ele 业务代码差异

Merge pull request !372 from 芋道源码/migration
This commit is contained in:
芋道源码
2026-06-25 01:54:17 +00:00
committed by Gitee
77 changed files with 408 additions and 1074 deletions

View File

@@ -35,11 +35,6 @@ export namespace CrmReceivableApi {
no: string;
totalPrice: number;
}
export interface ReceivablePageParam extends PageParam {
contractId?: number;
customerId?: number;
}
}
/** 查询回款列表 */
@@ -51,9 +46,7 @@ export function getReceivablePage(params: PageParam) {
}
/** 查询回款列表,基于指定客户 */
export function getReceivablePageByCustomer(
params: CrmReceivableApi.ReceivablePageParam,
) {
export function getReceivablePageByCustomer(params: PageParam) {
return requestClient.get<PageResult<CrmReceivableApi.Receivable>>(
'/crm/receivable/page-by-customer',
{ params },

View File

@@ -76,9 +76,9 @@ export function getSpuPage(params: PageParam) {
});
}
/** 获得商品 SPU 列表 tabsCount */
export function getTabsCount() {
return requestClient.get<Record<string, number>>('/product/spu/get-count');
/** 获得商品 SPU 列表 tabsCount(支持按 name/categoryId/createTime 筛选) */
export function getTabsCount(params?: Record<string, any>) {
return requestClient.get<Record<string, number>>('/product/spu/get-count', { params });
}
/** 创建商品 SPU */

View File

@@ -110,7 +110,10 @@ export function getSimpleUser(id: number | string) {
/** 按昵称模糊搜索用户 */
export function getSimpleUserListByNickname(nickname: string) {
return requestClient.get<SystemUserApi.UserSimple[]>('/system/user/list-by-nickname', {
params: { nickname },
});
return requestClient.get<SystemUserApi.UserSimple[]>(
'/system/user/list-by-nickname',
{
params: { nickname },
},
);
}

View File

@@ -109,21 +109,6 @@ const coreRoutes: RouteRecordRaw[] = [
},
],
},
/**
* 用于 bpm 移动端流程表单 web-view 的嵌入
*/
{
component: () => import('#/views/bpm/form/mobile/index.vue'),
meta: {
hideInBreadcrumb: true,
hideInMenu: true,
hideInTab: true,
ignoreAccess: true,
title: '移动端流程表单展示',
},
name: 'BpmMobileFormPreview',
path: '/bpm/mobile/form-preview',
},
];
export { coreRoutes, fallbackNotFoundRoute };

View File

@@ -40,7 +40,7 @@ const props = defineProps({
default: '',
},
});
const prefix = inject<string>('prefix', 'flowable'); // 增加默认值flowable
const prefix = inject<string>('prefix', 'flowable');
const elementListenersList = ref<any[]>([]); // 监听器列表
const listenerForm = ref<any>({}); // 监听器详情表单
const fieldsListOfListener = ref<any[]>([]);

View File

@@ -1,405 +0,0 @@
<script lang="ts" setup>
/**
* 移动端流程表单展示页面 - Ant Design Vue 版本
* 使用 @form-create/ant-design-vue 渲染表单
* 用于 UniApp 通过 iframe/webview 嵌入
*
* URL 参数说明:
* - type: 环境类型(必填)'miniapp' 小程序(微信/支付宝/百度等) | 'h5' H5
* - processInstanceId: 流程实例ID查看已有流程时使用
* - taskId: 任务ID可选
* - activityId: 活动节点ID可选
* - token: 访问令牌(用于 API 认证)
*/
import { computed, nextTick, onMounted, ref, toRaw } from 'vue';
import { useRoute } from 'vue-router';
import { BpmFieldPermissionType, BpmModelFormType } from '@vben/constants';
import { updatePreferences } from '@vben/preferences';
import { useAccessStore } from '@vben/stores';
import { Button, Empty, Spin } from 'ant-design-vue';
import { getApprovalDetail } from '#/api/bpm/processInstance';
import { setConfAndFields2 } from '#/components/form-create';
type EnvType = 'h5' | 'miniapp'; // 环境类型
// UniApp WebView 类型声明
interface UniWebView {
postMessage: (options: { data: any }, targetOrigin?: string) => void;
getEnv: (callback: (res: any) => void) => void;
navigateTo: (options: {
fail?: () => void;
success?: () => void;
url: string;
}) => void;
navigateBack: (options?: { delta?: number }) => void;
switchTab: (options: { url: string }) => void;
reLaunch: (options: { url: string }) => void;
redirectTo: (options: { url: string }) => void;
}
declare global {
interface Window {
uni?: UniWebView;
}
}
defineOptions({ name: 'BpmMobileFormPreview' });
const route = useRoute();
const accessStore = useAccessStore();
const envType = ref<EnvType>('h5'); // 环境类型
const loading = ref(true); // 页面加载状态
const error = ref<null | string>(null);
const processInstance = ref<any>(null);
const processDefinition = ref<any>(null);
const detailForm = ref<{
option: any;
rule: any[];
value: Record<string, any>;
}>({
option: {},
rule: [],
value: {},
}); // 流程实例的表单详情
const fApi = ref<any>(null); // form-create API 引用
const fieldPermissions = ref<Record<string, string>>({}); // 字段权限
// 是否有表单内容
const hasFormContent = computed(() => {
return detailForm.value.rule && detailForm.value.rule.length > 0;
});
/**
* 初始化 Token
* 从 URL 参数获取 token 并设置到 store
*/
function initToken() {
const token = route.query.token as string;
if (token) {
accessStore.setAccessToken(token);
}
}
/**
* 验证并初始化环境类型
*/
function initEnvType(): boolean {
const type = route.query.type as string;
if (!type) {
error.value = '缺少必填参数: type';
return false;
}
if (type !== 'h5' && type !== 'miniapp') {
error.value = 'type 参数值无效,必须是 h5 或 miniapp';
return false;
}
envType.value = type as EnvType;
return true;
}
/**
* 获取审批详情
*/
async function getDetail() {
loading.value = true;
error.value = null;
try {
const processInstanceId = route.query.processInstanceId as string;
const taskId = route.query.taskId as string;
const activityId = route.query.activityId as string;
if (!processInstanceId) {
throw new Error('缺少流程实例ID参数');
}
const data = await getApprovalDetail({
processInstanceId,
taskId,
activityId,
});
if (!data) {
throw new Error('查询不到审批详情信息');
}
if (!data.processDefinition || !data.processInstance) {
throw new Error('查询不到流程信息');
}
processInstance.value = data.processInstance;
processDefinition.value = data.processDefinition;
// 设置普通表单信息
if (data.processDefinition.formType === BpmModelFormType.NORMAL) {
if (detailForm.value.rule?.length > 0) {
// 避免刷新 form-create 显示不了
detailForm.value.value = processInstance.value.formVariables;
} else {
setConfAndFields2(
detailForm,
processDefinition.value.formConf,
processDefinition.value.formFields,
processInstance.value.formVariables,
);
}
await nextTick();
fApi.value?.btn.show(false);
fApi.value?.resetBtn.show(false);
fApi.value?.disabled(true);
// 设置表单字段权限
if (data.formFieldsPermission) {
Object.keys(data.formFieldsPermission).forEach((item) => {
setFieldPermission(item, data.formFieldsPermission[item]);
});
}
}
} finally {
loading.value = false;
}
}
/**
* 向父页面发送消息
* 根据环境类型选择不同的通信方式
*/
function postMessageToParent(message: { data: any; type: string }) {
const messageData = {
source: 'bpm-mobile-form',
type: message.type,
data: message.data,
};
// 小程序环境:使用 uni.postMessage
if (envType.value === 'miniapp') {
if (window.uni?.postMessage) {
// 传递的消息信息,必须写在 data 对象中
window.uni.postMessage({ data: message.data }, window.location.origin);
} else {
console.error('小程序环境下 uni 对象未定义');
}
return;
}
// H5 环境:使用 window.postMessage
if (envType.value === 'h5' && window.parent !== window) {
window.parent.postMessage(messageData, '*');
}
}
/**
* 安全地克隆对象,移除不可序列化的属性
*/
function safeClone(obj: any): any {
try {
// 先使用 toRaw 移除 Vue 的响应式代理
const raw = toRaw(obj);
// 使用 JSON 序列化来移除函数、DOM 元素等不可序列化的内容
// eslint-disable-next-line unicorn/prefer-structured-clone
return JSON.parse(JSON.stringify(raw));
} catch (error) {
console.error('克隆对象失败:', error);
return {};
}
}
/** 设置表单权限 */
function setFieldPermission(field: string, permission: string) {
fieldPermissions.value[field] = permission;
if (permission === BpmFieldPermissionType.READ) {
fApi.value?.disabled(true, field);
}
if (permission === BpmFieldPermissionType.WRITE) {
fApi.value?.disabled(false, field);
}
if (permission === BpmFieldPermissionType.NONE) {
fApi.value?.hidden(true, field);
}
}
/**
* 确定按钮点击事件
* 获取表单数据并发送给父页面
*/
function handleConfirm() {
// 获取最新的表单值(转换为普通对象,避免 Proxy 序列化问题)
const rawValue = detailForm.value.value;
const currentValue = safeClone(rawValue);
// 发送表单数据给父页面
postMessageToParent({
type: 'FORM_SUBMIT',
data: {
formValue: currentValue,
fieldPermissions: safeClone(fieldPermissions.value),
processInstanceId: route.query.processInstanceId,
taskId: route.query.taskId,
},
});
window.uni?.navigateBack();
}
onMounted(() => {
// 验证环境类型
if (!initEnvType()) {
loading.value = false;
return;
}
// 1. 先加载微信 JSSDK微信小程序需要
const wxScript = document.createElement('script');
wxScript.type = 'text/javascript';
wxScript.src = 'https://res.wx.qq.com/open/js/jweixin-1.4.0.js';
wxScript.addEventListener('load', () => {
// 2. 微信 SDK 加载完成后,加载 UniApp WebView SDK
const uniScript = document.createElement('script');
uniScript.type = 'text/javascript';
uniScript.src = 'https://unpkg.com/@dcloudio/uni-webview-js@0.0.3/index.js';
uniScript.addEventListener('load', () => {
// 所有 SDK 加载完成后初始化
initApp();
});
uniScript.addEventListener('error', () => {
error.value = 'UniApp WebView SDK 加载失败';
loading.value = false;
});
document.head.append(uniScript);
});
wxScript.addEventListener('error', () => {
// 微信 SDK 加载失败,尝试只加载 UniApp SDK可能是其他小程序
const uniScript = document.createElement('script');
uniScript.type = 'text/javascript';
uniScript.src = 'https://unpkg.com/@dcloudio/uni-webview-js@0.0.3/index.js';
uniScript.addEventListener('load', () => {
initApp();
});
uniScript.addEventListener('error', () => {
error.value = 'SDK 加载失败';
loading.value = false;
});
document.head.append(uniScript);
});
document.head.append(wxScript);
// 初始化
initApp();
});
/**
* 初始化应用
*/
function initApp() {
// 设置主题为 light 模式
updatePreferences({
theme: {
mode: 'light',
},
});
// 初始化 token
initToken();
// 加载数据
if (route.query.processInstanceId) {
getDetail();
} else {
loading.value = false;
error.value = '缺少必要参数processInstanceId';
}
}
</script>
<template>
<div class="mobile-form-preview-antd">
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<Spin size="large" tip="加载中..." />
</div>
<!-- 错误状态 -->
<Empty v-else-if="error" :description="error" />
<!-- 表单内容 -->
<template v-else>
<!-- 有表单规则时渲染 form-create -->
<div v-if="hasFormContent" class="mt-4">
<form-create
v-model="detailForm.value"
v-model:api="fApi"
:option="detailForm.option"
:rule="detailForm.rule"
/>
<!-- 确定按钮 -->
<div class="form-footer">
<Button type="primary" size="large" block @click="handleConfirm">
确定
</Button>
</div>
</div>
<!-- 无表单内容时显示空状态 -->
<Empty v-else description="暂无表单内容" />
</template>
</div>
</template>
<style scoped>
/* 响应式适配 */
@media (max-width: 768px) {
.mobile-form-preview-antd {
padding: 12px;
}
:deep(.ant-form-item) {
margin-bottom: 20px;
}
}
.mobile-form-preview-antd {
min-height: 100px;
padding: 16px;
}
.loading-container {
display: flex;
align-items: center;
justify-content: center;
padding: 60px 0;
}
.form-footer {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
padding: 16px;
border-top: 1px solid #f0f0f0;
box-shadow: 0 -2px 8px rgb(0 0 0 / 5%);
}
.form-footer :deep(.ant-btn) {
height: 48px;
font-size: 16px;
font-weight: 500;
border-radius: 8px;
}
</style>

View File

@@ -33,6 +33,7 @@ watch(
view.value = newModelView;
}
},
{ immediate: true },
);
/** 监听 bpmnXml */

View File

@@ -56,6 +56,7 @@ watch(
simpleModel.value = newModelView.simpleModel || {};
}
},
{ immediate: true },
);
/** 监控模型结构数据 */

View File

@@ -70,17 +70,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }) => {
const queryParams: CrmReceivableApi.ReceivablePageParam = {
const queryParams = {
...(props.customerId ? { customerId: props.customerId } : {}),
...(props.customerId && props.contractId
? { contractId: props.contractId }
: {}),
pageNo: page.currentPage,
pageSize: page.pageSize,
};
if (props.customerId && !props.contractId) {
queryParams.customerId = props.customerId;
} else if (props.customerId && props.contractId) {
// 如果是合同的话客户编号也需要带上因为权限基于客户
queryParams.customerId = props.customerId;
queryParams.contractId = props.contractId;
}
return await getReceivablePageByCustomer(queryParams);
},
},

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { onMounted, reactive, ref } from 'vue';
@@ -32,7 +32,7 @@ import {
deleteDemo03StudentList,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/normal';
} from '#/api/infra/demo/demo03/inner';
import { DictTag } from '#/components/dict-tag';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { VxeTableInstance } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { ref, watch } from 'vue';
@@ -9,7 +9,7 @@ import { IconifyIcon } from '@vben/icons';
import { Button, Input } from 'ant-design-vue';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/normal';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/inner';
import { $t } from '#/locales';
const props = defineProps<{

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { nextTick, ref, watch } from 'vue';
@@ -7,7 +7,7 @@ import { ContentWrap } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/normal';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/inner';
const props = defineProps<{
studentId?: number; // 学生编号(主表的关联字段)

View File

@@ -1,13 +1,13 @@
<script lang="ts" setup>
import type { Rule } from 'ant-design-vue/es/form';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { nextTick, ref, watch } from 'vue';
import { Form, Input } from 'ant-design-vue';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/normal';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/inner';
const props = defineProps<{
studentId?: number; // 学生编号(主表的关联字段)

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { nextTick, ref, watch } from 'vue';
@@ -7,7 +7,7 @@ import { ContentWrap } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/normal';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/inner';
const props = defineProps<{
studentId?: number; // 学生编号(主表的关联字段)

View File

@@ -2,7 +2,7 @@
import type { Rule } from 'ant-design-vue/es/form';
import type { Dayjs } from 'dayjs';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { computed, ref } from 'vue';
@@ -24,7 +24,7 @@ import {
createDemo03Student,
getDemo03Student,
updateDemo03Student,
} from '#/api/infra/demo/demo03/normal';
} from '#/api/infra/demo/demo03/inner';
import { Tinymce as RichTextarea } from '#/components/tinymce';
import { $t } from '#/locales';

View File

@@ -67,8 +67,8 @@ async function handleExport() {
}
/** 获得每个 Tab 的数量 */
async function getTabCount() {
const res = await getTabsCount();
async function getTabCount(params?: Record<string, any>) {
const res = await getTabsCount(params ?? (await gridApi.formApi.getValues()));
for (const objName in res) {
const index = Number(objName);
if (tabsData.value[index]) {
@@ -160,12 +160,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getSpuPage({
const result = await getSpuPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
tabType: tabType.value,
...formValues,
});
void getTabCount(formValues); // 跟随筛选刷新 tab 数量
return result;
},
},
},

View File

@@ -35,11 +35,6 @@ export namespace CrmReceivableApi {
no: string;
totalPrice: number;
}
export interface ReceivablePageParam extends PageParam {
contractId?: number;
customerId?: number;
}
}
/** 查询回款列表 */
@@ -51,9 +46,7 @@ export function getReceivablePage(params: PageParam) {
}
/** 查询回款列表,基于指定客户 */
export function getReceivablePageByCustomer(
params: CrmReceivableApi.ReceivablePageParam,
) {
export function getReceivablePageByCustomer(params: PageParam) {
return requestClient.get<PageResult<CrmReceivableApi.Receivable>>(
'/crm/receivable/page-by-customer',
{ params },

View File

@@ -76,9 +76,9 @@ export function getSpuPage(params: PageParam) {
});
}
/** 获得商品 SPU 列表 tabsCount */
export function getTabsCount() {
return requestClient.get<Record<string, number>>('/product/spu/get-count');
/** 获得商品 SPU 列表 tabsCount(支持按 name/categoryId/createTime 筛选) */
export function getTabsCount(params?: Record<string, any>) {
return requestClient.get<Record<string, number>>('/product/spu/get-count', { params });
}
/** 创建商品 SPU */

View File

@@ -16,6 +16,7 @@ export namespace SystemUserApi {
sex: number;
avatar: string;
loginIp: string;
loginDate?: Date;
status: number;
remark: string;
createTime?: Date;
@@ -40,6 +41,13 @@ export function getUser(id: number) {
return requestClient.get<SystemUserApi.User>(`/system/user/get?id=${id}`);
}
/** 查询用户列表 */
export function getUserList(ids: number[]) {
return requestClient.get<SystemUserApi.User[]>('/system/user/list', {
params: { ids: ids.join(',') },
});
}
/** 新增用户 */
export function createUser(data: SystemUserApi.User) {
return requestClient.post('/system/user/create', data);
@@ -88,13 +96,6 @@ export function updateUserStatus(id: number, status: number) {
return requestClient.put('/system/user/update-status', { id, status });
}
/** 查询用户列表 */
export function getUserList(ids: number[]) {
return requestClient.get<SystemUserApi.User[]>('/system/user/list', {
params: { ids: ids.join(',') },
});
}
/** 获取用户精简信息列表 */
export function getSimpleUserList() {
return requestClient.get<SystemUserApi.User[]>('/system/user/simple-list');
@@ -111,6 +112,8 @@ export function getSimpleUser(id: number | string) {
export function getSimpleUserListByNickname(nickname: string) {
return requestClient.get<SystemUserApi.UserSimple[]>(
'/system/user/list-by-nickname',
{ params: { nickname } },
{
params: { nickname },
},
);
}

View File

@@ -109,21 +109,6 @@ const coreRoutes: RouteRecordRaw[] = [
},
],
},
/**
* 用于 bpm 移动端流程表单 web-view 的嵌入
*/
{
component: () => import('#/views/bpm/form/mobile/index.vue'),
meta: {
hideInBreadcrumb: true,
hideInMenu: true,
hideInTab: true,
ignoreAccess: true,
title: '移动端流程表单展示',
},
name: 'BpmMobileFormPreview',
path: '/bpm/mobile/form-preview',
},
];
export { coreRoutes, fallbackNotFoundRoute };

View File

@@ -1,405 +0,0 @@
<script lang="ts" setup>
/**
* 移动端流程表单展示页面 - Ant Design Vue 版本
* 使用 @form-create/antdv-next 渲染表单
* 用于 UniApp 通过 iframe/webview 嵌入
*
* URL 参数说明:
* - type: 环境类型(必填)'miniapp' 小程序(微信/支付宝/百度等) | 'h5' H5
* - processInstanceId: 流程实例ID查看已有流程时使用
* - taskId: 任务ID可选
* - activityId: 活动节点ID可选
* - token: 访问令牌(用于 API 认证)
*/
import { computed, nextTick, onMounted, ref, toRaw } from 'vue';
import { useRoute } from 'vue-router';
import { BpmFieldPermissionType, BpmModelFormType } from '@vben/constants';
import { updatePreferences } from '@vben/preferences';
import { useAccessStore } from '@vben/stores';
import { Button, Empty, Spin } from 'antdv-next';
import { getApprovalDetail } from '#/api/bpm/processInstance';
import { setConfAndFields2 } from '#/components/form-create';
type EnvType = 'h5' | 'miniapp'; // 环境类型
// UniApp WebView 类型声明
interface UniWebView {
postMessage: (options: { data: any }, targetOrigin?: string) => void;
getEnv: (callback: (res: any) => void) => void;
navigateTo: (options: {
fail?: () => void;
success?: () => void;
url: string;
}) => void;
navigateBack: (options?: { delta?: number }) => void;
switchTab: (options: { url: string }) => void;
reLaunch: (options: { url: string }) => void;
redirectTo: (options: { url: string }) => void;
}
declare global {
interface Window {
uni?: UniWebView;
}
}
defineOptions({ name: 'BpmMobileFormPreview' });
const route = useRoute();
const accessStore = useAccessStore();
const envType = ref<EnvType>('h5'); // 环境类型
const loading = ref(true); // 页面加载状态
const error = ref<null | string>(null);
const processInstance = ref<any>(null);
const processDefinition = ref<any>(null);
const detailForm = ref<{
option: any;
rule: any[];
value: Record<string, any>;
}>({
option: {},
rule: [],
value: {},
}); // 流程实例的表单详情
const fApi = ref<any>(null); // form-create API 引用
const fieldPermissions = ref<Record<string, string>>({}); // 字段权限
// 是否有表单内容
const hasFormContent = computed(() => {
return detailForm.value.rule && detailForm.value.rule.length > 0;
});
/**
* 初始化 Token
* 从 URL 参数获取 token 并设置到 store
*/
function initToken() {
const token = route.query.token as string;
if (token) {
accessStore.setAccessToken(token);
}
}
/**
* 验证并初始化环境类型
*/
function initEnvType(): boolean {
const type = route.query.type as string;
if (!type) {
error.value = '缺少必填参数: type';
return false;
}
if (type !== 'h5' && type !== 'miniapp') {
error.value = 'type 参数值无效,必须是 h5 或 miniapp';
return false;
}
envType.value = type as EnvType;
return true;
}
/**
* 获取审批详情
*/
async function getDetail() {
loading.value = true;
error.value = null;
try {
const processInstanceId = route.query.processInstanceId as string;
const taskId = route.query.taskId as string;
const activityId = route.query.activityId as string;
if (!processInstanceId) {
throw new Error('缺少流程实例ID参数');
}
const data = await getApprovalDetail({
processInstanceId,
taskId,
activityId,
});
if (!data) {
throw new Error('查询不到审批详情信息');
}
if (!data.processDefinition || !data.processInstance) {
throw new Error('查询不到流程信息');
}
processInstance.value = data.processInstance;
processDefinition.value = data.processDefinition;
// 设置普通表单信息
if (data.processDefinition.formType === BpmModelFormType.NORMAL) {
if (detailForm.value.rule?.length > 0) {
// 避免刷新 form-create 显示不了
detailForm.value.value = processInstance.value.formVariables;
} else {
setConfAndFields2(
detailForm,
processDefinition.value.formConf,
processDefinition.value.formFields,
processInstance.value.formVariables,
);
}
await nextTick();
fApi.value?.btn.show(false);
fApi.value?.resetBtn.show(false);
fApi.value?.disabled(true);
// 设置表单字段权限
if (data.formFieldsPermission) {
Object.keys(data.formFieldsPermission).forEach((item) => {
setFieldPermission(item, data.formFieldsPermission[item]);
});
}
}
} finally {
loading.value = false;
}
}
/**
* 向父页面发送消息
* 根据环境类型选择不同的通信方式
*/
function postMessageToParent(message: { data: any; type: string }) {
const messageData = {
source: 'bpm-mobile-form',
type: message.type,
data: message.data,
};
// 小程序环境:使用 uni.postMessage
if (envType.value === 'miniapp') {
if (window.uni?.postMessage) {
// 传递的消息信息,必须写在 data 对象中
window.uni.postMessage({ data: message.data }, window.location.origin);
} else {
console.error('小程序环境下 uni 对象未定义');
}
return;
}
// H5 环境:使用 window.postMessage
if (envType.value === 'h5' && window.parent !== window) {
window.parent.postMessage(messageData, '*');
}
}
/**
* 安全地克隆对象,移除不可序列化的属性
*/
function safeClone(obj: any): any {
try {
// 先使用 toRaw 移除 Vue 的响应式代理
const raw = toRaw(obj);
// 使用 JSON 序列化来移除函数、DOM 元素等不可序列化的内容
// eslint-disable-next-line unicorn/prefer-structured-clone
return JSON.parse(JSON.stringify(raw));
} catch (error) {
console.error('克隆对象失败:', error);
return {};
}
}
/** 设置表单权限 */
function setFieldPermission(field: string, permission: string) {
fieldPermissions.value[field] = permission;
if (permission === BpmFieldPermissionType.READ) {
fApi.value?.disabled(true, field);
}
if (permission === BpmFieldPermissionType.WRITE) {
fApi.value?.disabled(false, field);
}
if (permission === BpmFieldPermissionType.NONE) {
fApi.value?.hidden(true, field);
}
}
/**
* 确定按钮点击事件
* 获取表单数据并发送给父页面
*/
function handleConfirm() {
// 获取最新的表单值(转换为普通对象,避免 Proxy 序列化问题)
const rawValue = detailForm.value.value;
const currentValue = safeClone(rawValue);
// 发送表单数据给父页面
postMessageToParent({
type: 'FORM_SUBMIT',
data: {
formValue: currentValue,
fieldPermissions: safeClone(fieldPermissions.value),
processInstanceId: route.query.processInstanceId,
taskId: route.query.taskId,
},
});
window.uni?.navigateBack();
}
onMounted(() => {
// 验证环境类型
if (!initEnvType()) {
loading.value = false;
return;
}
// 1. 先加载微信 JSSDK微信小程序需要
const wxScript = document.createElement('script');
wxScript.type = 'text/javascript';
wxScript.src = 'https://res.wx.qq.com/open/js/jweixin-1.4.0.js';
wxScript.addEventListener('load', () => {
// 2. 微信 SDK 加载完成后,加载 UniApp WebView SDK
const uniScript = document.createElement('script');
uniScript.type = 'text/javascript';
uniScript.src = 'https://unpkg.com/@dcloudio/uni-webview-js@0.0.3/index.js';
uniScript.addEventListener('load', () => {
// 所有 SDK 加载完成后初始化
initApp();
});
uniScript.addEventListener('error', () => {
error.value = 'UniApp WebView SDK 加载失败';
loading.value = false;
});
document.head.append(uniScript);
});
wxScript.addEventListener('error', () => {
// 微信 SDK 加载失败,尝试只加载 UniApp SDK可能是其他小程序
const uniScript = document.createElement('script');
uniScript.type = 'text/javascript';
uniScript.src = 'https://unpkg.com/@dcloudio/uni-webview-js@0.0.3/index.js';
uniScript.addEventListener('load', () => {
initApp();
});
uniScript.addEventListener('error', () => {
error.value = 'SDK 加载失败';
loading.value = false;
});
document.head.append(uniScript);
});
document.head.append(wxScript);
// 初始化
initApp();
});
/**
* 初始化应用
*/
function initApp() {
// 设置主题为 light 模式
updatePreferences({
theme: {
mode: 'light',
},
});
// 初始化 token
initToken();
// 加载数据
if (route.query.processInstanceId) {
getDetail();
} else {
loading.value = false;
error.value = '缺少必要参数processInstanceId';
}
}
</script>
<template>
<div class="mobile-form-preview-antd">
<!-- 加载状态 -->
<div v-if="loading" class="loading-container">
<Spin size="large" description="加载中..." />
</div>
<!-- 错误状态 -->
<Empty v-else-if="error" :description="error" />
<!-- 表单内容 -->
<template v-else>
<!-- 有表单规则时渲染 form-create -->
<div v-if="hasFormContent" class="mt-4">
<form-create
v-model="detailForm.value"
v-model:api="fApi"
:option="detailForm.option"
:rule="detailForm.rule"
/>
<!-- 确定按钮 -->
<div class="form-footer">
<Button type="primary" size="large" block @click="handleConfirm">
确定
</Button>
</div>
</div>
<!-- 无表单内容时显示空状态 -->
<Empty v-else description="暂无表单内容" />
</template>
</div>
</template>
<style scoped>
/* 响应式适配 */
@media (max-width: 768px) {
.mobile-form-preview-antd {
padding: 12px;
}
:deep(.ant-form-item) {
margin-bottom: 20px;
}
}
.mobile-form-preview-antd {
min-height: 100px;
padding: 16px;
}
.loading-container {
display: flex;
align-items: center;
justify-content: center;
padding: 60px 0;
}
.form-footer {
position: fixed;
right: 0;
bottom: 0;
left: 0;
z-index: 100;
padding: 16px;
border-top: 1px solid #f0f0f0;
box-shadow: 0 -2px 8px rgb(0 0 0 / 5%);
}
.form-footer :deep(.ant-btn) {
height: 48px;
font-size: 16px;
font-weight: 500;
border-radius: 8px;
}
</style>

View File

@@ -33,6 +33,7 @@ watch(
view.value = newModelView;
}
},
{ immediate: true },
);
/** 监听 bpmnXml */

View File

@@ -56,6 +56,7 @@ watch(
simpleModel.value = newModelView.simpleModel || {};
}
},
{ immediate: true },
);
/** 监控模型结构数据 */

View File

@@ -70,17 +70,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }) => {
const queryParams: CrmReceivableApi.ReceivablePageParam = {
const queryParams = {
...(props.customerId ? { customerId: props.customerId } : {}),
...(props.customerId && props.contractId
? { contractId: props.contractId }
: {}),
pageNo: page.currentPage,
pageSize: page.pageSize,
};
if (props.customerId && !props.contractId) {
queryParams.customerId = props.customerId;
} else if (props.customerId && props.contractId) {
// 如果是合同的话客户编号也需要带上因为权限基于客户
queryParams.customerId = props.customerId;
queryParams.contractId = props.contractId;
}
return await getReceivablePageByCustomer(queryParams);
},
},

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { onMounted, reactive, ref } from 'vue';
@@ -33,7 +33,7 @@ import {
deleteDemo03StudentList,
exportDemo03Student,
getDemo03StudentPage,
} from '#/api/infra/demo/demo03/normal';
} from '#/api/infra/demo/demo03/inner';
import { DictTag } from '#/components/dict-tag';
import { $t } from '#/locales';
import { getRangePickerDefaultProps } from '#/utils';

View File

@@ -1,6 +1,6 @@
<script lang="ts" setup>
import type { VxeTableInstance } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { ref, watch } from 'vue';
@@ -9,7 +9,7 @@ import { IconifyIcon } from '@vben/icons';
import { Button, Input } from 'antdv-next';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/normal';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/inner';
import { $t } from '#/locales';
const props = defineProps<{

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { nextTick, ref, watch } from 'vue';
@@ -7,7 +7,7 @@ import { ContentWrap } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/normal';
import { getDemo03CourseListByStudentId } from '#/api/infra/demo/demo03/inner';
const props = defineProps<{
studentId?: number; // 学生编号(主表的关联字段)

View File

@@ -1,11 +1,11 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { nextTick, ref, watch } from 'vue';
import { Form, FormItem, Input } from 'antdv-next';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/normal';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/inner';
type Rule = any;
const props = defineProps<{

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { nextTick, ref, watch } from 'vue';
@@ -7,7 +7,7 @@ import { ContentWrap } from '@vben/common-ui';
import { formatDateTime } from '@vben/utils';
import { VxeColumn, VxeTable } from '#/adapter/vxe-table';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/normal';
import { getDemo03GradeByStudentId } from '#/api/infra/demo/demo03/inner';
const props = defineProps<{
studentId?: number; // 学生编号(主表的关联字段)

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { computed, ref } from 'vue';
@@ -22,7 +22,7 @@ import {
createDemo03Student,
getDemo03Student,
updateDemo03Student,
} from '#/api/infra/demo/demo03/normal';
} from '#/api/infra/demo/demo03/inner';
import { Tinymce as RichTextarea } from '#/components/tinymce';
import { $t } from '#/locales';

View File

@@ -73,8 +73,8 @@ async function handleExport() {
}
/** 获得每个 Tab 的数量 */
async function getTabCount() {
const res = await getTabsCount();
async function getTabCount(params?: Record<string, any>) {
const res = await getTabsCount(params ?? (await gridApi.formApi.getValues()));
for (const objName in res) {
const index = Number(objName);
if (tabsData.value[index]) {
@@ -166,12 +166,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getSpuPage({
const result = await getSpuPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
tabType: tabType.value,
...formValues,
});
void getTabCount(formValues); // 跟随筛选刷新 tab 数量
return result;
},
},
},

View File

@@ -3,14 +3,6 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace CrmReceivableApi {
/** 合同信息 */
export interface Contract {
id?: number;
name?: string;
no: string;
totalPrice: number;
}
/** 回款信息 */
export interface Receivable {
id: number;
@@ -35,6 +27,14 @@ export namespace CrmReceivableApi {
createTime: Date; // 创建时间
updateTime: Date; // 更新时间
}
/** 合同信息 */
export interface Contract {
id?: number;
name?: string;
no: string;
totalPrice: number;
}
}
/** 查询回款列表 */

View File

@@ -3,18 +3,6 @@ import type { PageParam, PageResult } from '@vben/request';
import { requestClient } from '#/api/request';
export namespace ErpFinancePaymentApi {
/** 付款单项 */
export interface FinancePaymentItem {
id?: number;
row_id?: number; // 前端使用的临时 ID
bizId: number; // 业务ID
bizType: number; // 业务类型
bizNo: string; // 业务编号
totalPrice: number; // 应付金额
paidPrice: number; // 已付金额
paymentPrice: number; // 本次付款
remark?: string; // 备注
}
/** 付款单信息 */
export interface FinancePayment {
id?: number; // 付款单编号
@@ -37,6 +25,19 @@ export namespace ErpFinancePaymentApi {
items?: FinancePaymentItem[]; // 付款明细
bizNo?: string; // 业务单号
}
/** 付款单项 */
export interface FinancePaymentItem {
id?: number;
row_id?: number; // 前端使用的临时 ID
bizId: number; // 业务ID
bizType: number; // 业务类型
bizNo: string; // 业务编号
totalPrice: number; // 应付金额
paidPrice: number; // 已付金额
paymentPrice: number; // 本次付款
remark?: string; // 备注
}
}
/** 查询付款单分页 */

View File

@@ -76,9 +76,9 @@ export function getSpuPage(params: PageParam) {
});
}
/** 获得商品 SPU 列表 tabsCount */
export function getTabsCount() {
return requestClient.get<Record<string, number>>('/product/spu/get-count');
/** 获得商品 SPU 列表 tabsCount(支持按 name/categoryId/createTime 筛选) */
export function getTabsCount(params?: Record<string, any>) {
return requestClient.get<Record<string, number>>('/product/spu/get-count', { params });
}
/** 创建商品 SPU */

View File

@@ -24,8 +24,8 @@ export namespace MesDvMachineryApi {
/** 设备导入结果 */
export interface MachineryImportRespVO {
createCodes?: string[]; // 新增成功的设备编码
failureCodes?: Record<string, string>; // 导入失败的设备编码及原因
updateCodes?: string[]; // 更新成功的设备编码
failureCodes?: Record<string, string>; // 导入失败的设备编码及原因
}
}

View File

@@ -83,51 +83,6 @@ const routes: RouteRecordRaw[] = [
},
component: () => import('#/views/crm/product/detail/index.vue'),
},
{
path: 'statistics/customer',
name: 'CrmStatisticsCustomer',
meta: {
title: '客户统计',
activePath: '/crm/statistics/customer',
},
component: () => import('#/views/crm/statistics/customer/index.vue'),
},
{
path: 'statistics/funnel',
name: 'CrmStatisticsFunnel',
meta: {
title: '销售漏斗',
activePath: '/crm/statistics/funnel',
},
component: () => import('#/views/crm/statistics/funnel/index.vue'),
},
{
path: 'statistics/performance',
name: 'CrmStatisticsPerformance',
meta: {
title: '员工业绩',
activePath: '/crm/statistics/performance',
},
component: () => import('#/views/crm/statistics/performance/index.vue'),
},
{
path: 'statistics/portrait',
name: 'CrmStatisticsPortrait',
meta: {
title: '客户画像',
activePath: '/crm/statistics/portrait',
},
component: () => import('#/views/crm/statistics/portrait/index.vue'),
},
{
path: 'statistics/rank',
name: 'CrmStatisticsRank',
meta: {
title: '排行榜',
activePath: '/crm/statistics/rank',
},
component: () => import('#/views/crm/statistics/rank/index.vue'),
},
],
},
];

View File

@@ -40,7 +40,7 @@ const props = defineProps({
default: '',
},
});
const prefix = inject('prefix');
const prefix = inject<string>('prefix', 'flowable');
const elementListenersList = ref<any[]>([]); // 监听器列表
const listenerForm = ref<any>({}); // 监听器详情表单
const fieldsListOfListener = ref<any[]>([]);
@@ -328,7 +328,7 @@ watch(
);
</script>
<template>
<div class="-mx-2 mb-2">
<div class="-mx-2">
<ListenerGrid :data="elementListenersList">
<template #action="{ row, rowIndex }">
<ElButton

View File

@@ -38,7 +38,7 @@ interface Props {
type?: string;
}
const prefix = inject<string>('prefix');
const prefix = inject<string>('prefix', 'flowable');
const elementListenersList = ref<any[]>([]);
const listenerEventTypeObject = ref(eventType);
@@ -324,7 +324,7 @@ watch(
);
</script>
<template>
<div class="-mx-2 mb-2">
<div class="-mx-2">
<ListenerGrid>
<template #action="{ row, rowIndex }">
<ElButton

View File

@@ -1,7 +1,35 @@
const bpmnInstances = () => (window as any)?.bpmnInstances;
interface ListenerFieldOptions {
expression?: string;
fieldType: string;
name: string;
string?: string;
}
interface ListenerOptions {
class?: string;
delegateExpression?: string;
event?: string;
eventDefinitionType?: string;
eventTimeDefinitions?: string;
expression?: string;
fields?: ListenerFieldOptions[];
id?: string;
listenerType?: string;
resource?: string;
scriptFormat?: string;
scriptType?: string;
value?: string;
}
// 创建监听器实例
export function createListenerObject(options: any, isTask: any, prefix: any) {
const listenerObj = Object.create(null);
export function createListenerObject(
options: ListenerOptions,
isTask: boolean,
prefix: string,
) {
const listenerObj: Record<string, any> = Object.create(null);
listenerObj.event = options.event;
isTask && (listenerObj.id = options.id); // 任务监听器特有的 id 字段
switch (options.listenerType) {
@@ -23,7 +51,7 @@ export function createListenerObject(options: any, isTask: any, prefix: any) {
}
// 注入字段
if (options.fields) {
listenerObj.fields = options.fields.map((field: any) => {
listenerObj.fields = options.fields.map((field) => {
return createFieldObject(field, prefix);
});
}
@@ -39,7 +67,7 @@ export function createListenerObject(options: any, isTask: any, prefix: any) {
'bpmn:TimerEventDefinition',
{
id: `TimerEventDefinition_${uuid(8)}`,
[`time${options.eventDefinitionType.replace(/^\S/, (s: any) => s.toUpperCase())}`]:
[`time${options.eventDefinitionType.replace(/^\S/, (s) => s.toUpperCase())}`]:
timeDefinition,
},
);
@@ -52,7 +80,10 @@ export function createListenerObject(options: any, isTask: any, prefix: any) {
}
// 创建 监听器的注入字段 实例
export function createFieldObject(option: any, prefix: any) {
export function createFieldObject(
option: ListenerFieldOptions,
prefix: string,
) {
const { name, fieldType, string, expression } = option;
const fieldConfig =
fieldType === 'string' ? { name, string } : { name, expression };
@@ -60,7 +91,7 @@ export function createFieldObject(option: any, prefix: any) {
}
// 创建脚本实例
export function createScriptObject(options: any, prefix: any) {
export function createScriptObject(options: ListenerOptions, prefix: string) {
const { scriptType, scriptFormat, value, resource } = options;
const scriptConfig =
scriptType === 'inlineScript'
@@ -70,7 +101,7 @@ export function createScriptObject(options: any, prefix: any) {
}
// 更新元素扩展属性
export function updateElementExtensions(element: any, extensionList: any) {
export function updateElementExtensions(element: any, extensionList: any[]) {
const extensions = bpmnInstances().moddle.create('bpmn:ExtensionElements', {
values: extensionList,
});

View File

@@ -8,7 +8,7 @@ import type { UserTaskFormType } from '../../helpers';
import { computed, nextTick, onMounted, reactive, ref } from 'vue';
import { useVbenDrawer } from '@vben/common-ui';
import { useVbenDrawer, useVbenModal } from '@vben/common-ui';
import {
BpmModelFormType,
BpmNodeTypeEnum,
@@ -23,6 +23,8 @@ import {
ElDivider,
ElForm,
ElFormItem,
ElInput,
ElInputNumber,
ElOption,
ElRadio,
ElRadioButton,
@@ -35,6 +37,8 @@ import {
ElTreeSelect,
} from 'element-plus';
import { ProcessExpressionSelectModal } from '#/views/bpm/processExpression/components';
import {
APPROVE_METHODS,
APPROVE_TYPE,
@@ -115,7 +119,11 @@ const [Drawer, drawerApi] = useVbenDrawer({
},
});
// TODO @jason和 antd 对应的文件,逻辑有点不一样;
const [ExpressionSelectModal, expressionSelectModalApi] = useVbenModal({
connectedComponent: ProcessExpressionSelectModal,
destroyOnClose: true,
showConfirmButton: false,
});
// 节点名称配置
// @ts-expect-error unused
@@ -224,9 +232,18 @@ function changeCandidateStrategy() {
configForm.value.deptLevel = 1;
configForm.value.formUser = '';
configForm.value.formDept = '';
configForm.value.expression = '';
configForm.value.approveMethod = ApproveMethodType.SEQUENTIAL_APPROVE;
}
function openExpressionSelect() {
expressionSelectModalApi.open();
}
function handleExpressionSelected(row: any) {
configForm.value.expression = row?.expression ?? '';
}
/** 审批方式改变 */
function approveMethodChanged() {
configForm.value.rejectHandlerType = RejectHandlerType.FINISH_PROCESS;
@@ -813,7 +830,6 @@ onMounted(() => {
/>
</ElSelect>
</ElFormItem>
<!-- TODO @jason后续要支持选择已经存好的表达式 -->
<ElFormItem
v-if="
configForm.candidateStrategy === CandidateStrategy.EXPRESSION
@@ -821,11 +837,21 @@ onMounted(() => {
label="流程表达式"
name="expression"
>
<ElInput
v-model="configForm.expression"
type="textarea"
:rows="4"
/>
<div class="flex gap-2">
<ElInput
v-model="configForm.expression"
type="textarea"
:rows="2"
/>
<div class="flex flex-col gap-2">
<ElButton type="primary" @click="openExpressionSelect">
选择
</ElButton>
<ElButton @click="configForm.expression = ''">
清空
</ElButton>
</div>
</div>
</ElFormItem>
<!-- 多人审批/办理 方式 -->
<ElFormItem :label="`多人${nodeTypeName}方式`" name="approveMethod">
@@ -1182,4 +1208,6 @@ onMounted(() => {
</ElTabPane>
</ElTabs>
</Drawer>
<ExpressionSelectModal @select="handleExpressionSelected" />
</template>

View File

@@ -1,11 +1,14 @@
<script lang="ts" setup>
import type { VxeGridPropTypes } from '#/adapter/vxe-table';
import type { VbenFormSchema } from '#/adapter/form';
import type {
VxeGridPropTypes,
VxeTableGridOptions,
} from '#/adapter/vxe-table';
import type { BpmProcessExpressionApi } from '#/api/bpm/processExpression';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { CommonStatusEnum } from '@vben/constants';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getProcessExpressionPage } from '#/api/bpm/processExpression';
@@ -16,35 +19,23 @@ const emit = defineEmits<{
select: [expression: BpmProcessExpressionApi.ProcessExpression];
}>();
// 查询参数
// TODO @jason这里的风格和 antd 对应的不一致;
const queryParams = ref({
status: CommonStatusEnum.ENABLE,
});
// 配置 VxeGrid
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: [
{ field: 'name', title: '名字', minWidth: 160 },
{ field: 'expression', title: '表达式', minWidth: 260 },
{
field: 'action',
title: '操作',
width: 120,
slots: { default: 'action' },
},
],
columns: useGridColumns(),
showOverflow: true,
minHeight: 300,
proxyConfig: {
ajax: {
// 查询表达式列表
query: async ({ page }) => {
query: async ({ page }, formValues) => {
return await getProcessExpressionPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
status: queryParams.value.status,
...formValues,
});
},
},
@@ -56,7 +47,7 @@ const [Grid] = useVbenVxeGrid({
toolbarConfig: {
enabled: false,
},
},
} as VxeTableGridOptions<BpmProcessExpressionApi.ProcessExpression>,
});
// 配置 Modal
@@ -70,6 +61,53 @@ function handleSelect(row: BpmProcessExpressionApi.ProcessExpression) {
emit('select', row);
modalApi.close();
}
/** 列表的搜索表单 */
function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'name',
label: '名字',
component: 'Input',
componentProps: {
placeholder: '请输入名字',
clearable: true,
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
defaultValue: CommonStatusEnum.ENABLE,
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
disabled: true,
},
},
];
}
function useGridColumns(): VxeTableGridOptions<BpmProcessExpressionApi.ProcessExpression>['columns'] {
return [
{ field: 'name', title: '名字', minWidth: 160 },
{ field: 'expression', title: '表达式', minWidth: 260 },
{
field: 'status',
title: '状态',
minWidth: 100,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{
field: 'action',
title: '操作',
width: 120,
slots: { default: 'action' },
},
];
}
</script>
<template>

View File

@@ -1,22 +1,32 @@
import type { VbenFormSchema } from '#/adapter/form';
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import { DICT_TYPE } from '@vben/constants';
import { CommonStatusEnum, DICT_TYPE } from '@vben/constants';
import { getDictOptions } from '@vben/hooks';
/** 选择监听器弹窗的列表字段 */
// TODO @jason和 antd 对应的,不太一致;
export function useGridColumns(): VxeTableGridOptions['columns'] {
return [
{ field: 'name', title: '名字', minWidth: 120 },
{ field: 'name', title: '名字', minWidth: 160 },
{
field: 'type',
title: '类型',
minWidth: 200,
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.BPM_PROCESS_LISTENER_TYPE },
},
},
{ field: 'event', title: '事件', minWidth: 200 },
{
field: 'status',
title: '状态',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.COMMON_STATUS },
},
},
{ field: 'event', title: '事件', minWidth: 120 },
{
field: 'valueType',
title: '值类型',
@@ -35,3 +45,29 @@ export function useGridColumns(): VxeTableGridOptions['columns'] {
},
];
}
/** 列表的搜索表单 */
export function useGridFormSchema(): VbenFormSchema[] {
return [
{
fieldName: 'name',
label: '名字',
component: 'Input',
componentProps: {
placeholder: '请输入名字',
clearable: true,
},
},
{
fieldName: 'status',
label: '状态',
component: 'Select',
defaultValue: CommonStatusEnum.ENABLE,
componentProps: {
options: getDictOptions(DICT_TYPE.COMMON_STATUS, 'number'),
placeholder: '请选择状态',
disabled: true,
},
},
];
}

View File

@@ -8,12 +8,11 @@ import type { BpmProcessListenerApi } from '#/api/bpm/processListener';
import { ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { CommonStatusEnum } from '@vben/constants';
import { TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import { getProcessListenerPage } from '#/api/bpm/processListener';
import { useGridColumns } from './data';
import { useGridColumns, useGridFormSchema } from './data';
defineOptions({ name: 'ProcessListenerSelectModal' });
@@ -21,27 +20,25 @@ const emit = defineEmits<{
select: [listener: BpmProcessListenerApi.ProcessListener];
}>();
// 查询参数
// TODO @jason这里的风格和 antd 对应的不一致;
const queryParams = ref({
type: '',
status: CommonStatusEnum.ENABLE,
});
const listenerType = ref('');
// 配置 VxeGrid
const [Grid] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
},
gridOptions: {
columns: useGridColumns(),
showOverflow: true,
minHeight: 300,
proxyConfig: {
ajax: {
query: async ({ page }) => {
query: async ({ page }, formValues) => {
return await getProcessListenerPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
type: queryParams.value.type,
status: queryParams.value.status,
type: listenerType.value,
...formValues,
});
},
},
@@ -61,12 +58,12 @@ const [Modal, modalApi] = useVbenModal({
showConfirmButton: false,
onOpenChange: async (isOpen: boolean) => {
if (!isOpen) {
queryParams.value.type = '';
listenerType.value = '';
return;
}
const data = modalApi.getData<{ type: string }>();
if (data?.type) {
queryParams.value.type = data.type;
listenerType.value = data.type;
}
},
destroyOnClose: true,

View File

@@ -69,17 +69,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }) => {
const queryParams: any = {
const queryParams = {
...(props.customerId ? { customerId: props.customerId } : {}),
...(props.customerId && props.contractId
? { contractId: props.contractId }
: {}),
pageNo: page.currentPage,
pageSize: page.pageSize,
};
if (props.customerId && !props.contractId) {
queryParams.customerId = props.customerId;
} else if (props.customerId && props.contractId) {
// 如果是合同的话客户编号也需要带上因为权限基于客户
queryParams.customerId = props.customerId;
queryParams.contractId = props.contractId;
}
return await getReceivablePageByCustomer(queryParams);
},
},

View File

@@ -40,6 +40,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
componentProps: {
disabled: formType === 'detail',
placeholder: '选择付款时间',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -40,6 +40,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
componentProps: {
disabled: formType === 'detail',
placeholder: '选择收款时间',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -43,7 +43,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
componentProps: {
disabled: formType === 'detail',
placeholder: '选择入库时间',
showTime: true,
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -41,7 +41,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
component: 'DatePicker',
componentProps: {
placeholder: '选择订单时间',
showTime: true,
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -43,7 +43,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
componentProps: {
disabled: formType === 'detail',
placeholder: '选择退货时间',
showTime: true,
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -41,6 +41,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
component: 'DatePicker',
componentProps: {
placeholder: '选择订单时间',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -43,6 +43,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
componentProps: {
disabled: formType === 'detail',
placeholder: '选择出库时间',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -43,6 +43,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
componentProps: {
disabled: formType === 'detail',
placeholder: '选择退货时间',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -38,6 +38,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
component: 'DatePicker',
componentProps: {
placeholder: '选择盘点时间',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -39,6 +39,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
component: 'DatePicker',
componentProps: {
placeholder: '选择入库时间',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -38,6 +38,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
component: 'DatePicker',
componentProps: {
placeholder: '选择调度时间',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -39,6 +39,7 @@ export function useFormSchema(formType: FormType): VbenFormSchema[] {
component: 'DatePicker',
componentProps: {
placeholder: '选择出库时间',
type: 'datetime',
format: 'YYYY-MM-DD HH:mm:ss',
valueFormat: 'x',
class: '!w-full',

View File

@@ -67,8 +67,8 @@ async function handleExport() {
}
/** 获得每个 Tab 的数量 */
async function getTabCount() {
const res = await getTabsCount();
async function getTabCount(params?: Record<string, any>) {
const res = await getTabsCount(params ?? (await gridApi.formApi.getValues()));
for (const objName in res) {
const index = Number(objName);
if (tabsData.value[index]) {
@@ -158,12 +158,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }, formValues) => {
return await getSpuPage({
const result = await getSpuPage({
pageNo: page.currentPage,
pageSize: page.pageSize,
tabType: Number(tabType.value),
...formValues,
});
void getTabCount(formValues); // 跟随筛选刷新 tab 数量
return result;
},
},
},

View File

@@ -26,6 +26,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }) => {
if (!props.feedbackId) {
return { list: [], total: 0 };
}
return await getItemConsumeLinePage({
feedbackId: props.feedbackId,
pageNo: page.currentPage,
@@ -39,7 +42,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
isHover: true,
},
toolbarConfig: {
refresh: true,
refresh: false,
search: false,
},
} as VxeTableGridOptions<MesWmItemConsumeLineApi.ItemConsumeLine>,
});

View File

@@ -37,6 +37,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
proxyConfig: {
ajax: {
query: async ({ page }) => {
if (!props.feedbackId) {
return { list: [], total: 0 };
}
return await getProductProduceLinePage({
feedbackId: props.feedbackId,
pageNo: page.currentPage,
@@ -50,7 +53,8 @@ const [Grid, gridApi] = useVbenVxeGrid({
isHover: true,
},
toolbarConfig: {
refresh: true,
refresh: false,
search: false,
},
} as VxeTableGridOptions<MesWmProductProduceLineApi.ProductProduceLine>,
});

View File

@@ -0,0 +1,27 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { DocAlert, IFrame, Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores';
defineOptions({ name: 'GoView' });
const accessStore = useAccessStore();
const src = ref(
`${import.meta.env.VITE_GOVIEW_URL}?accessToken=${accessStore.accessToken}&refreshToken=${accessStore.refreshToken}`,
);
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert
title="大屏设计器"
url="https://doc.iocoder.cn/report/screen/"
/>
</template>
<IFrame :src="src" />
</Page>
</template>

View File

@@ -0,0 +1,25 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { DocAlert, IFrame, Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores';
defineOptions({ name: 'JimuBI' });
const accessStore = useAccessStore();
const src = ref(
`${import.meta.env.VITE_BASE_URL}/drag/list?token=${
accessStore.refreshToken
}`,
);
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert title="大屏设计器" url="https://doc.iocoder.cn/screen/" />
</template>
<IFrame :src="src" />
</Page>
</template>

View File

@@ -0,0 +1,25 @@
<script lang="ts" setup>
import { ref } from 'vue';
import { DocAlert, IFrame, Page } from '@vben/common-ui';
import { useAccessStore } from '@vben/stores';
defineOptions({ name: 'JimuReport' });
const accessStore = useAccessStore();
const src = ref(
`${import.meta.env.VITE_BASE_URL}/jmreport/list?token=${
accessStore.refreshToken
}`,
);
</script>
<template>
<Page auto-content-height>
<template #doc>
<DocAlert title="报表设计器" url="https://doc.iocoder.cn/report/" />
</template>
<IFrame :src="src" />
</Page>
</template>

View File

@@ -1 +1,2 @@
export { default as WmsInventorySelect } from './inventory-select.vue';
export { default as WmsInventorySelect } from './select.vue';
export type { InventorySelectRow } from './select.vue';

View File

@@ -1 +1 @@
export { default as WmsItemBrandSelect } from './item-brand-select.vue';
export { default as WmsItemBrandSelect } from './select.vue';

View File

@@ -1,2 +1,2 @@
export { default as WmsItemCategorySelect } from './item-category-select.vue';
export { default as WmsItemCategoryTree } from './item-category-tree.vue';
export { default as WmsItemCategorySelect } from './select.vue';
export { default as WmsItemCategoryTree } from './tree.vue';

View File

@@ -1 +1 @@
export { default as WmsItemSkuSelect } from './item-sku-select.vue';
export { default as WmsItemSkuSelect } from './select.vue';

View File

@@ -1 +1 @@
export { default as WmsMerchantSelect } from './merchant-select.vue';
export { default as WmsMerchantSelect } from './select.vue';

View File

@@ -1 +1 @@
export { default as WmsWarehouseSelect } from './warehouse-select.vue';
export { default as WmsWarehouseSelect } from './select.vue';

View File

@@ -5,7 +5,7 @@ import type { VxeTableInstance } from '#/adapter/vxe-table';
import type { WmsWarehouseApi } from '#/api/wms/md/warehouse';
import type { WmsMovementOrderApi } from '#/api/wms/order/movement';
import type { WmsMovementOrderDetailApi } from '#/api/wms/order/movement/detail';
import type { InventorySelectRow } from '#/views/wms/inventory/components/inventory-select.vue';
import type { InventorySelectRow } from '#/views/wms/inventory/components';
import { computed, nextTick, ref } from 'vue';

View File

@@ -5,7 +5,7 @@ import type { VxeTableInstance } from '#/adapter/vxe-table';
import type { WmsWarehouseApi } from '#/api/wms/md/warehouse';
import type { WmsShipmentOrderApi } from '#/api/wms/order/shipment';
import type { WmsShipmentOrderDetailApi } from '#/api/wms/order/shipment/detail';
import type { InventorySelectRow } from '#/views/wms/inventory/components/inventory-select.vue';
import type { InventorySelectRow } from '#/views/wms/inventory/components';
import { computed, nextTick, ref } from 'vue';