| | |
| | | #跳转iframe地址 |
| | | VITE_IFREAM_URL='http://36.133.126.111:7099' |
| | | # 扫码登录页地址 登录完会自动跳转到大屏 |
| | | VITE_BASE_LOGIN_URL='https://portal.ccccltd.cn/sso_sys?cb=http://zynlpt.ccccltd.cn' |
| | | VITE_BASE_LOGIN_URL='https://portal.ccccltd.cn/sso_sys?cb=http://zynlpt.ccccltd.cn' |
| | |
| | | }) |
| | | } |
| | | provide('reload', reload) |
| | | // 临时初始化:写入 userId 与 unitId,便于本地联调后端下单接口 |
| | | if (!userStore.getUserId) { |
| | | userStore.userId = '1' |
| | | } |
| | | if (!userStore.getUnitId) { |
| | | userStore.unitId = '1' |
| | | } |
| | | |
| | | setInterval(() => { |
| | | // 这个判断代表登录成功后才开始写入时间 |
| | | if(userStore.getAdminToken) { |
| | |
| | | |
| | | const cartApi = { |
| | | // 添加商品到购物车 |
| | | addToCart(data: any, userId: number, unitId: number): ApiPromise { |
| | | addToCart(data: any, userId: string, unitId: string): ApiPromise { |
| | | return createAxios({ |
| | | url: `${baseUrl}/add`, |
| | | method: 'post', |
| | |
| | | }, |
| | | |
| | | // 从购物车移除商品 |
| | | removeFromCart(userId: number, unitId: number, pricingId: number): ApiPromise { |
| | | removeFromCart(userId: string, unitId: string, pricingId: string): ApiPromise { |
| | | return createAxios({ |
| | | url: `${baseUrl}/remove`, |
| | | method: 'delete', |
| | |
| | | }, |
| | | |
| | | // 更新购物车商品数量 |
| | | updateCartItem(userId: number, unitId: number, pricingId: number, quantity: number): ApiPromise { |
| | | updateCartItem(userId: string, unitId: string, pricingId: number, quantity: number): ApiPromise { |
| | | return createAxios({ |
| | | url: `${baseUrl}/update`, |
| | | method: 'put', |
| | |
| | | }, |
| | | |
| | | // 获取购物车信息 |
| | | getCartInfo(userId: number, unitId: number): ApiPromise { |
| | | getCartInfo(userId: string, unitId: string): ApiPromise { |
| | | return createAxios({ |
| | | url: `${baseUrl}/info`, |
| | | method: 'get', |
| | |
| | | }, |
| | | |
| | | // 获取购物车商品列表 |
| | | getCartItems(userId: number, unitId: number): ApiPromise { |
| | | getCartItems(userId: string, unitId: string): ApiPromise { |
| | | return createAxios({ |
| | | url: `${baseUrl}/items`, |
| | | method: 'get', |
| | |
| | | }, |
| | | |
| | | // 清空购物车 |
| | | clearCart(userId: number, unitId: number): ApiPromise { |
| | | clearCart(userId: string, unitId: string): ApiPromise { |
| | | return createAxios({ |
| | | url: `${baseUrl}/clear`, |
| | | method: 'delete', |
| | |
| | | data |
| | | }) as ApiPromise |
| | | }, |
| | | getBuyerOrderPageWithProductConditions(data: any): ApiPromise { |
| | | return createAxios({ |
| | | url: `${url}/buyer/page/with-product-conditions`, |
| | | method: 'post', |
| | | data |
| | | }) as ApiPromise |
| | | }, |
| | | getSellerOrderPage(data: any): ApiPromise { |
| | | return createAxios({ |
| | | url: `${url}/seller/page`, |
| | | method: 'post', |
| | | data |
| | | }) as ApiPromise |
| | | }, |
| | | getSellerOrderPageWithProductConditions(data: any): ApiPromise { |
| | | return createAxios({ |
| | | url: `${url}/seller/page/with-product-conditions`, |
| | | method: 'post', |
| | | data |
| | | }) as ApiPromise |
| | |
| | | orderId |
| | | } |
| | | }) as ApiPromise |
| | | }, |
| | | |
| | | // 启动并完成审批流程(外部工作流服务) |
| | | startWorkflowAndComplete(data: { processdefId: string; userid: string; businessKey: string }): ApiPromise { |
| | | return createAxios({ |
| | | url: `/test/app/workflow/startProcessAndComplete`, |
| | | method: 'post', |
| | | data |
| | | }) as ApiPromise |
| | | }, |
| | | |
| | | // 更新订单的工作流ID |
| | | updateWorkflowId(orderId: string, workflowId: string): ApiPromise { |
| | | return createAxios({ |
| | | url: `${url}/workflow/update`, |
| | | method: 'post', |
| | | params: { orderId, workflowId } |
| | | }) as ApiPromise |
| | | } |
| | | } |
| | | |
New file |
| | |
| | | import createAxios from '@/utils/axios' |
| | | |
| | | let url = '/test/report/product/' |
| | | |
| | | const productApi = { |
| | | // 获取产品列表 |
| | | getProductList(data: any): ApiPromise { |
| | | return createAxios({ |
| | | url: `${url}accessApplyList`, |
| | | method: 'POST', |
| | | data: data, |
| | | }) as ApiPromise |
| | | }, |
| | | |
| | | // 根据ID获取产品详情 |
| | | getProductById(data: any): ApiPromise { |
| | | return createAxios({ |
| | | url: `${url}get`, |
| | | method: 'post', |
| | | data: data, |
| | | }) as ApiPromise |
| | | }, |
| | | getCategoryByParent(data: any):ApiPromise{ |
| | | return createAxios({ |
| | | url: `${url}categoryByParent`, |
| | | method: 'post', |
| | | data: data, |
| | | }) as ApiPromise |
| | | } |
| | | } |
| | | |
| | | export default productApi |
| | |
| | | import createAxios from '@/utils/axios' |
| | | let url = '/test' |
| | | |
| | | // 修改密码 |
| | | export function updatePassw(data: object = {}): ApiPromise { |
| | |
| | | // 查看个人信息 |
| | | export function queryUserDetail(data: object = {}): ApiPromise { |
| | | return createAxios({ |
| | | url: `/admin/common/userDetail`, |
| | | url: `${url}/admin/common/userDetail`, |
| | | headers: { |
| | | 'Content-Type': 'application/json;charset=UTF-8' |
| | | }, |
| | |
| | | import { useUserInfo } from '@/stores/modules/userInfo' |
| | | import { useCommonInfo } from '@/stores/modules/common' // 状态管理器 |
| | | import { useNavTabs } from '@/stores/modules/navTabs' // 标签导航 |
| | | import { queryUserDetail } from '@/api/userInfo' |
| | | |
| | | const router = createRouter({ |
| | | history: createWebHistory('/manage/'), |
| | |
| | | return { top: 0 } |
| | | }, |
| | | }) |
| | | router.beforeEach((to, from, next) => { |
| | | router.beforeEach(async (to, from, next) => { |
| | | console.log("to",to) |
| | | const userStore = useUserInfo() |
| | | // * 在跳转路由之前,清除所有的请求 |
| | |
| | | } |
| | | // 如果没有token,设置一个默认token并继续 |
| | | if (!userStore.getAdminToken) { |
| | | const obj: any = { |
| | | adminToken: 'dev-token', |
| | | screenToken: '', |
| | | const localUserInfo = localStorage.getItem('userInfo') |
| | | if (localUserInfo) { |
| | | const userInfo = JSON.parse(localUserInfo) |
| | | if (userInfo.adminToken) { |
| | | const obj: any = { |
| | | adminToken: userInfo.adminToken, |
| | | screenToken: '', |
| | | } |
| | | userStore.updateUserInfo(obj) |
| | | } |
| | | } |
| | | userStore.updateUserInfo(obj) |
| | | } |
| | | next() |
| | | return |
| | |
| | | } else { |
| | | if (to.query.token) { |
| | | const token = to.query.token as string |
| | | localStorage.setItem('lastRecordTime', new Date().getTime()) |
| | | localStorage.setItem('lastRecordTime', new Date().getTime().toString()) |
| | | const obj: any = { |
| | | adminToken: token, |
| | | screenToken: '', |
| | | } |
| | | userStore.updateUserInfo(obj) |
| | | const localUserInfo = localStorage.getItem('userInfo') |
| | | if (localUserInfo) { |
| | | const userInfo = JSON.parse(localUserInfo) |
| | | userStore.updateUserDetail(userInfo) |
| | | } |
| | | // chrome |
| | | document.body.scrollTop = 0 |
| | | // firefox |
| | |
| | | window.pageYOffset = 0 |
| | | next() |
| | | } else if (!userStore.getAdminToken) { |
| | | // 如果没有,则跳至登录页面 |
| | | next({ path: '/loginT' }) |
| | | // 从 localStorage 中获取 userInfo 的 adminToken |
| | | const localUserInfo = localStorage.getItem('userInfo') |
| | | if (localUserInfo) { |
| | | try { |
| | | const userInfo = JSON.parse(localUserInfo) |
| | | if (userInfo.adminToken) { |
| | | // 如果获取到 adminToken,则更新 userStore |
| | | userStore.updateUserInfo({ |
| | | adminToken: userInfo.adminToken, |
| | | screenToken: userInfo.screenToken || '', |
| | | fullUnitName: userInfo.fullUnitName || '', |
| | | empCode: userInfo.empCode || '', |
| | | menuList: userInfo.menuList || [] |
| | | }) |
| | | // 同时更新用户详细信息 |
| | | userStore.updateUserDetail(userInfo) |
| | | next() |
| | | } else { |
| | | // 如果没有 adminToken,则用户未登录 |
| | | next({ path: '/loginT' }) |
| | | } |
| | | } catch (error) { |
| | | console.error('解析 localStorage 中的 userInfo 失败:', error) |
| | | // 解析失败,认为用户未登录 |
| | | next({ path: '/loginT' }) |
| | | } |
| | | } else { |
| | | // 如果没有 userInfo,则用户未登录 |
| | | next({ path: '/loginT' }) |
| | | } |
| | | } else { |
| | | // 获取用户信息 |
| | | const localUserInfo = localStorage.getItem('userInfo') |
| | | if (localUserInfo) { |
| | | const userInfo = JSON.parse(localUserInfo) |
| | | userStore.updateUserDetail(userInfo) |
| | | } |
| | | next() |
| | | } |
| | | } |
| | |
| | | }, |
| | | }, |
| | | { |
| | | path: 'track/:id', |
| | | name: 'tradeTrack', |
| | | component: () => import('@/views/tradeManage/track/index.vue'), |
| | | meta: { |
| | | title: '订单审批轨迹', |
| | | keepAlive: false, |
| | | }, |
| | | }, |
| | | { |
| | | path: 'seller', |
| | | name: 'tradeSellerCenter', |
| | | component: () => import('@/views/tradeManage/seller/index.vue'), |
| | |
| | | import { defineStore } from 'pinia' |
| | | import { UserInfo } from '@/stores/interface' |
| | | import router from '@/router/index' |
| | | import { updateUserDetail } from '@/api/userInfo' |
| | | |
| | | export const useUserInfo = defineStore('userInfo', { |
| | | state: (): UserInfo => { |
New file |
| | |
| | | /** |
| | | * 订单工作流程控制器 |
| | | * 管理订单状态流转和操作权限 |
| | | */ |
| | | |
| | | // 订单状态枚举(按照流程顺序) |
| | | export enum OrderStatus { |
| | | WAIT_UPLOAD = '待上传文件', // 1 |
| | | WAIT_AUTHORIZE = '待授权', // 2 |
| | | WAIT_CONFIRM = '待交易确认', // 3 |
| | | COMPLETED = '已完成', // 4 |
| | | EVALUATED = '已评价' // 5 (最终状态) |
| | | } |
| | | |
| | | // 操作类型枚举 |
| | | export enum ActionType { |
| | | VIEW = '查看', |
| | | TRACK = '追踪', |
| | | UPLOAD_FILE = '提交文件', |
| | | AUTHORIZE = '授权', |
| | | CONFIRM_TRADE = '交易确认', |
| | | EVALUATE = '评价', |
| | | CANCEL_ORDER = '取消订单' |
| | | } |
| | | |
| | | // 页面类型枚举 |
| | | export enum PageType { |
| | | TRADE_APPROVAL = 'tradeApproval', // 交易审核 |
| | | BUYER_CENTER = 'buyerCenter', // 买家中心 |
| | | SELLER_CENTER = 'sellerCenter' // 卖家中心 |
| | | } |
| | | |
| | | // 操作配置接口 |
| | | interface ActionConfig { |
| | | type: ActionType |
| | | routeName?: string |
| | | params?: Record<string, any> |
| | | } |
| | | |
| | | // 各页面在不同状态下的操作配置 |
| | | const PAGE_ACTION_CONFIG: Record<PageType, Record<OrderStatus, ActionConfig[]>> = { |
| | | [PageType.TRADE_APPROVAL]: { |
| | | [OrderStatus.WAIT_UPLOAD]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK } |
| | | ], |
| | | [OrderStatus.WAIT_AUTHORIZE]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK }, |
| | | { type: ActionType.AUTHORIZE, routeName: 'tradeApproval' } |
| | | ], |
| | | [OrderStatus.WAIT_CONFIRM]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK } |
| | | ], |
| | | [OrderStatus.COMPLETED]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK } |
| | | ], |
| | | [OrderStatus.EVALUATED]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK } |
| | | ] |
| | | }, |
| | | [PageType.BUYER_CENTER]: { |
| | | [OrderStatus.WAIT_UPLOAD]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK }, |
| | | { type: ActionType.UPLOAD_FILE, routeName: 'tradeOrderUpload' }, |
| | | { type: ActionType.CANCEL_ORDER } |
| | | ], |
| | | [OrderStatus.WAIT_AUTHORIZE]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK }, |
| | | { type: ActionType.CANCEL_ORDER } |
| | | ], |
| | | [OrderStatus.WAIT_CONFIRM]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK }, |
| | | { type: ActionType.CONFIRM_TRADE, routeName: 'tradeOrderConfirm' }, |
| | | { type: ActionType.CANCEL_ORDER } |
| | | ], |
| | | [OrderStatus.COMPLETED]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK }, |
| | | { type: ActionType.EVALUATE, routeName: 'tradeOrderEvaluate' } |
| | | ], |
| | | [OrderStatus.EVALUATED]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK } |
| | | ] |
| | | }, |
| | | [PageType.SELLER_CENTER]: { |
| | | [OrderStatus.WAIT_UPLOAD]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK } |
| | | ], |
| | | [OrderStatus.WAIT_AUTHORIZE]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK } |
| | | ], |
| | | [OrderStatus.WAIT_CONFIRM]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK } |
| | | ], |
| | | [OrderStatus.COMPLETED]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK } |
| | | ], |
| | | [OrderStatus.EVALUATED]: [ |
| | | { type: ActionType.VIEW, routeName: 'tradeOrderDetail' }, |
| | | { type: ActionType.TRACK } |
| | | ] |
| | | } |
| | | } |
| | | |
| | | // 状态流转映射(当前状态 -> 下一个状态) |
| | | const STATUS_FLOW_MAP: Record<OrderStatus, OrderStatus | null> = { |
| | | [OrderStatus.WAIT_UPLOAD]: OrderStatus.WAIT_AUTHORIZE, |
| | | [OrderStatus.WAIT_AUTHORIZE]: OrderStatus.WAIT_CONFIRM, |
| | | [OrderStatus.WAIT_CONFIRM]: OrderStatus.COMPLETED, |
| | | [OrderStatus.COMPLETED]: OrderStatus.EVALUATED, |
| | | [OrderStatus.EVALUATED]: null // 最终状态,无下一状态 |
| | | } |
| | | |
| | | /** |
| | | * 订单工作流程控制器类 |
| | | */ |
| | | export class OrderWorkflowController { |
| | | |
| | | /** |
| | | * 获取指定页面和状态下的可用操作列表 |
| | | * @param pageType 页面类型 |
| | | * @param currentStatus 当前订单状态 |
| | | * @returns 操作配置列表 |
| | | */ |
| | | static getAvailableActions(pageType: PageType, currentStatus: OrderStatus): ActionConfig[] { |
| | | const pageConfig = PAGE_ACTION_CONFIG[pageType] |
| | | if (!pageConfig) { |
| | | console.warn(`未找到页面类型 ${pageType} 的配置`) |
| | | return [] |
| | | } |
| | | |
| | | const statusConfig = pageConfig[currentStatus] |
| | | if (!statusConfig) { |
| | | console.warn(`未找到状态 ${currentStatus} 在页面 ${pageType} 中的配置`) |
| | | return [] |
| | | } |
| | | |
| | | return statusConfig |
| | | } |
| | | |
| | | /** |
| | | * 判断是否可以执行指定操作 |
| | | * @param pageType 页面类型 |
| | | * @param currentStatus 当前订单状态 |
| | | * @param actionType 操作类型 |
| | | * @returns 是否可以执行 |
| | | */ |
| | | static canExecuteAction(pageType: PageType, currentStatus: OrderStatus, actionType: ActionType): boolean { |
| | | const actions = this.getAvailableActions(pageType, currentStatus) |
| | | return actions.some(action => action.type === actionType) |
| | | } |
| | | |
| | | /** |
| | | * 获取下一个状态 |
| | | * @param currentStatus 当前状态 |
| | | * @returns 下一个状态,如果已是最终状态则返回null |
| | | */ |
| | | static getNextStatus(currentStatus: OrderStatus): OrderStatus | null { |
| | | return STATUS_FLOW_MAP[currentStatus] || null |
| | | } |
| | | |
| | | /** |
| | | * 判断当前状态是否为最终状态 |
| | | * @param currentStatus 当前状态 |
| | | * @returns 是否为最终状态 |
| | | */ |
| | | static isFinalStatus(currentStatus: OrderStatus): boolean { |
| | | return currentStatus === OrderStatus.EVALUATED |
| | | } |
| | | |
| | | /** |
| | | * 验证状态流转是否合法 |
| | | * @param fromStatus 起始状态 |
| | | * @param toStatus 目标状态 |
| | | * @returns 是否为合法的状态流转 |
| | | */ |
| | | static validateStatusTransition(fromStatus: OrderStatus, toStatus: OrderStatus): boolean { |
| | | const expectedNextStatus = this.getNextStatus(fromStatus) |
| | | return expectedNextStatus === toStatus |
| | | } |
| | | |
| | | /** |
| | | * 获取所有状态的顺序列表 |
| | | * @returns 状态列表 |
| | | */ |
| | | static getAllStatusInOrder(): OrderStatus[] { |
| | | return [ |
| | | OrderStatus.WAIT_UPLOAD, |
| | | OrderStatus.WAIT_AUTHORIZE, |
| | | OrderStatus.WAIT_CONFIRM, |
| | | OrderStatus.COMPLETED, |
| | | OrderStatus.EVALUATED |
| | | ] |
| | | } |
| | | |
| | | /** |
| | | * 根据产品套件判断初始状态 |
| | | * @param orderDetails 订单详情列表 |
| | | * @returns 初始状态 |
| | | */ |
| | | static determineInitialStatus(orderDetails: any[]): OrderStatus { |
| | | // 检查是否有价格类型为"协议"的套件 |
| | | const hasAgreementPrice = orderDetails.some(detail => { |
| | | const priceType = String(detail.priceType || '').toLowerCase() |
| | | return priceType.includes('协议') || priceType.includes('agreement') |
| | | }) |
| | | |
| | | // 如果有协议价格,从"待上传文件"开始,否则从"待授权"开始 |
| | | return hasAgreementPrice ? OrderStatus.WAIT_UPLOAD : OrderStatus.WAIT_AUTHORIZE |
| | | } |
| | | } |
| | | |
| | | // 状态映射工具函数(用于前后端状态转换) |
| | | export const StatusMapper = { |
| | | /** |
| | | * 前端枚举状态转后端中文状态 |
| | | */ |
| | | toServerStatus: (uiStatus: string): string => { |
| | | // 直接返回中文状态,因为枚举值本身就是中文 |
| | | return uiStatus |
| | | }, |
| | | |
| | | /** |
| | | * 后端中文状态转前端枚举状态 |
| | | */ |
| | | toUIStatus: (serverStatus: string): OrderStatus => { |
| | | // 标准化状态名称 |
| | | const normalizedStatus = serverStatus?.trim() |
| | | |
| | | switch (normalizedStatus) { |
| | | case '待上传文件': |
| | | return OrderStatus.WAIT_UPLOAD |
| | | case '待授权': |
| | | return OrderStatus.WAIT_AUTHORIZE |
| | | case '待交易确认': |
| | | return OrderStatus.WAIT_CONFIRM |
| | | case '已完成': |
| | | return OrderStatus.COMPLETED |
| | | case '已评价': |
| | | return OrderStatus.EVALUATED |
| | | default: |
| | | console.warn(`未知的订单状态: ${serverStatus},默认返回待授权状态`) |
| | | return OrderStatus.WAIT_AUTHORIZE |
| | | } |
| | | } |
| | | } |
| | |
| | | <script lang="ts" setup> |
| | | import { reactive, ref, toRefs, onMounted } from 'vue' |
| | | import { login } from '@/api/login' |
| | | import { FormInstance, FormRules } from 'element-plus' |
| | | import { ElMessage, FormInstance, FormRules } from 'element-plus' |
| | | import router from '@/router' |
| | | import { useUserInfo } from '@/stores/modules/userInfo' |
| | | import { getAssetsImages } from '@/utils/common' |
| | |
| | | // 正常登录接口调用 |
| | | login(state.userInfo).then((res: any) => { |
| | | if (res.code == 200) { |
| | | localStorage.setItem('lastRecordTime', new Date().getTime()) |
| | | localStorage.setItem('lastRecordTime', new Date().getTime().toString()) |
| | | // 将缓存进行缓存或者状态管理器中 |
| | | userStore.removeMenuList() |
| | | userStore.updateUserInfo(res.data) |
| | |
| | | } from '@element-plus/icons-vue' |
| | | import pointsApi from '@/api/pointsApi' |
| | | import type { PointsStats, PointsFlow, PointsQueryParams } from '@/types/points' |
| | | import { useUserInfo } from '@/stores/modules/userInfo' |
| | | import { queryUserDetail } from '@/api/userInfo' |
| | | |
| | | // 获取用户信息 store |
| | | const userStore = useUserInfo() |
| | | |
| | | // 积分统计 |
| | | const stats = ref<PointsStats>({ |
| | |
| | | |
| | | // 查询参数 |
| | | const queryParams = reactive<PointsQueryParams>({ |
| | | userId: '1', |
| | | userId: '', |
| | | dataCategory: '', |
| | | dataType: '', |
| | | pageNum: 1, |
| | |
| | | // 获取积分统计 |
| | | const getPointsStats = async () => { |
| | | try { |
| | | const userId = 1; |
| | | const userId = userStore.getUserId || userStore.getUserInfo?.userId |
| | | if (!userId) { |
| | | console.error('无法获取用户ID') |
| | | return |
| | | } |
| | | // 更新查询参数中的 userId |
| | | queryParams.userId = userId.toString() |
| | | const res = await pointsApi.getPersonalPointsStats(userId) |
| | | if (res.code === 200 && res.data) { |
| | | stats.value = res.data |
| | |
| | | // 获取积分流水 |
| | | const getPointsFlow = async () => { |
| | | try { |
| | | const userId = userStore.getUserId || userStore.getUserInfo?.userId |
| | | if (!userId) { |
| | | console.error('无法获取用户ID') |
| | | return |
| | | } |
| | | // 更新查询参数中的 userId |
| | | queryParams.userId = userId.toString() |
| | | const res = await pointsApi.getPersonalPointsFlow(queryParams) |
| | | if (res.code === 200) { |
| | | flowList.value = res.data.list || [] |
| | |
| | | ElMessage.info('关闭全部功能') |
| | | } |
| | | |
| | | onMounted(() => { |
| | | onMounted(async () => { |
| | | // 页面挂载时,检查 userStore 是否已有用户信息 |
| | | let userId = userStore.getUserId || userStore.getUserInfo?.userId |
| | | if (!userId) { |
| | | try { |
| | | const res: any = await queryUserDetail() |
| | | if (res?.code === 200 && res.data) { |
| | | userStore.updateUserDetail(res.data) |
| | | userId = res.data.userId || res.data.id |
| | | } else { |
| | | ElMessage.error(res?.msg || '无法获取用户信息,请先登录') |
| | | return |
| | | } |
| | | } catch (e) { |
| | | console.error('获取用户详情失败:', e) |
| | | ElMessage.error('获取用户信息失败,请稍后重试') |
| | | return |
| | | } |
| | | } |
| | | |
| | | getPointsStats() |
| | | getPointsFlow() |
| | | getCategoryList() |
| | |
| | | |
| | | <el-descriptions v-if="productDetail" :column="2" border> |
| | | <el-descriptions-item label="产品名称" label-width="10%">{{ productDetail.name }}</el-descriptions-item> |
| | | <el-descriptions-item label="提报单位" label-width="10%">{{ productDetail.submitUnit }}</el-descriptions-item> |
| | | <el-descriptions-item label="提报人" label-width="10%">{{ productDetail.submitter }}</el-descriptions-item> |
| | | <el-descriptions-item label="行业领域" label-width="10%">{{ productDetail.industry }}</el-descriptions-item> |
| | | <el-descriptions-item label="单位工程" label-width="10%">{{ productDetail.projectUnit }}</el-descriptions-item> |
| | | <el-descriptions-item label="产业阶段" label-width="10%">{{ productDetail.industryStage }}</el-descriptions-item> |
| | | <el-descriptions-item label="产品类型"label-width="10%">{{ productDetail.productType }}</el-descriptions-item> |
| | | <el-descriptions-item label="提报单位" label-width="10%">{{ productDetail.submissionUnit }}</el-descriptions-item> |
| | | <el-descriptions-item label="提报人" label-width="10%">{{ productDetail.createBy }}</el-descriptions-item> |
| | | <el-descriptions-item label="行业领域" label-width="10%">{{ productDetail.industrialChainName }}</el-descriptions-item> |
| | | <el-descriptions-item label="单位工程" label-width="10%">{{ productDetail.importantAreaName }}</el-descriptions-item> |
| | | <el-descriptions-item label="产业阶段" label-width="10%">{{ productDetail.businessProcessName }}</el-descriptions-item> |
| | | <el-descriptions-item label="产品类型"label-width="10%">{{ productDetail.typeName }}</el-descriptions-item> |
| | | <el-descriptions-item label="产品简介" :span="3"> |
| | | <div class="intro">{{ productDetail.description }}</div> |
| | | <div class="intro">{{ productDetail.describe }}</div> |
| | | </el-descriptions-item> |
| | | </el-descriptions> |
| | | <el-empty v-else description="未找到该产品的详情" /> |
| | |
| | | class="pricing-group" |
| | | > |
| | | <div class="group-header">{{ group.suiteName }}</div> |
| | | <div class="pricing-cards-wrapper"> |
| | | <div |
| | | <div class="pricing-cards-wrapper"> |
| | | <div |
| | | v-for="pricing in group.items" |
| | | :key="pricing.id" |
| | | class="pricing-card" |
| | | :class="{ |
| | | 'pricing-card-enabled': pricing.enableStatus === 'ENABLED', |
| | | 'pricing-card-disabled': pricing.enableStatus === 'DISABLED' |
| | | }" |
| | | > |
| | | :key="pricing.id" |
| | | class="pricing-card" |
| | | :class="{ |
| | | 'pricing-card-enabled': pricing.enableStatus === 'ENABLED', |
| | | 'pricing-card-disabled': pricing.enableStatus === 'DISABLED' |
| | | }" |
| | | > |
| | | <div class="pricing-card-table"> |
| | | <div class="pricing-row"> |
| | | <div class="pricing-cell label-cell">销售形式</div> |
| | |
| | | class="price-form" |
| | | > |
| | | |
| | | <el-form-item label="产品套件" prop="productSuite"> |
| | | <el-form-item label="产品套件" prop="productSuite"> |
| | | <el-select v-model="formData.productSuite" placeholder="请选择" style="width: 60%"> |
| | | <el-option v-for="opt in productSuiteOptions" :key="opt.value" :label="opt.label" :value="opt.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="客户对象" prop="customerObject"> |
| | | <el-form-item label="客户对象" prop="customerObject"> |
| | | <el-radio-group v-model="formData.customerObject"> |
| | | <el-radio v-for="opt in customerObjectOptions" :key="opt.value" :label="opt.value">{{ opt.label }}</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | </el-form-item> |
| | | <el-form-item label="销售形式" prop="salesForm" class="sales-form-item"> |
| | | <el-radio-group v-model="formData.salesForm" class="sales-form-group"> |
| | | <el-radio v-for="opt in salesFormOptions" :key="opt.value" :label="opt.value">{{ opt.label }}</el-radio> |
| | |
| | | <el-radio v-for="opt in enableStatusOptions" :key="opt.value" :label="opt.value">{{ opt.label }}</el-radio> |
| | | </el-radio-group> |
| | | </el-form-item> |
| | | |
| | | |
| | | |
| | | <el-form-item label="备注"> |
| | | <el-input type="textarea" v-model="formData.remark" :rows="3" placeholder="请输入备注" /> |
| | |
| | | import { Goods, Lock, Unlock } from '@element-plus/icons-vue' |
| | | import ProductPriceViewer from '@/views/productManage/productPriceViewer/index.vue' |
| | | import productPricingApi from '@/api/productPricingApi' |
| | | import productApi from '@/api/productApi' |
| | | |
| | | |
| | | interface ProductDetail { |
| | | id: string |
| | | name: string |
| | | submitUnit: string |
| | | submitter: string |
| | | industry: string |
| | | projectUnit: string |
| | | industryStage: string |
| | | productType: string |
| | | description: string |
| | | submissionUnit: string |
| | | createBy: string |
| | | industrialChainName: string |
| | | importantAreaName: string |
| | | businessProcessName: string |
| | | typeName: string |
| | | describe: string |
| | | shelfStatus?: '待上架' | '已上架' | '已下架' |
| | | } |
| | | |
| | |
| | | }) |
| | | |
| | | // 模拟产品详情数据源 |
| | | const mockProductMap: Record<string, ProductDetail> = { |
| | | '1': { |
| | | id: '1', |
| | | name: '数字化产品A', |
| | | submitUnit: '中交一公局', |
| | | submitter: '张三', |
| | | industry: '交通基础设施', |
| | | projectUnit: '某高速公路工程', |
| | | industryStage: '应用阶段', |
| | | productType: '软件/平台', |
| | | description: '本产品定位为以建设期BIM数字资产作为数字底盘,结合项目运营维保需求的实时性、交互性、便捷性的三维可视化运维管理系统。系统提供项目数字化、智能化运维管理功能,能够解决建筑运行维护管理中的实际问题,实现信息快速整合与查询、信息有效共享与传递,提升项目综合管理与维护水平。', |
| | | shelfStatus: '待上架' |
| | | }, |
| | | '2': { |
| | | id: '2', |
| | | name: '数字化产品B', |
| | | submitUnit: '中交二航局', |
| | | submitter: '李四', |
| | | industry: '市政工程', |
| | | projectUnit: '智慧管廊项目', |
| | | industryStage: '研发阶段', |
| | | productType: '硬件/传感', |
| | | description: '面向城市管廊监测的传感设备与采集网关,支持边缘计算与远程运维。', |
| | | shelfStatus: '已上架' |
| | | } |
| | | } |
| | | // const mockProductMap: Record<string, ProductDetail> = { |
| | | // '1': { |
| | | // id: '1', |
| | | // name: '数字化产品A', |
| | | // submitUnit: '中交一公局', |
| | | // submitter: '张三', |
| | | // industry: '交通基础设施', |
| | | // projectUnit: '某高速公路工程', |
| | | // industryStage: '应用阶段', |
| | | // productType: '软件/平台', |
| | | // description: '本产品定位为以建设期BIM数字资产作为数字底盘,结合项目运营维保需求的实时性、交互性、便捷性的三维可视化运维管理系统。系统提供项目数字化、智能化运维管理功能,能够解决建筑运行维护管理中的实际问题,实现信息快速整合与查询、信息有效共享与传递,提升项目综合管理与维护水平。', |
| | | // shelfStatus: '待上架' |
| | | // }, |
| | | // '2': { |
| | | // id: '2', |
| | | // name: '数字化产品B', |
| | | // submitUnit: '中交二航局', |
| | | // submitter: '李四', |
| | | // industry: '市政工程', |
| | | // projectUnit: '智慧管廊项目', |
| | | // industryStage: '研发阶段', |
| | | // productType: '硬件/传感', |
| | | // description: '面向城市管廊监测的传感设备与采集网关,支持边缘计算与远程运维。', |
| | | // shelfStatus: '已上架' |
| | | // } |
| | | // } |
| | | |
| | | const loading = ref(false) |
| | | const priceList = ref<any[]>([]) |
| | |
| | | } |
| | | loading.value = true |
| | | try { |
| | | productDetail.value = mockProductMap[productId] || null |
| | | // const detailRes: any = await productApi.getProductDetail({ id: productId }) |
| | | // if (detailRes?.code === 200) { |
| | | // productDetail.value = detailRes.data || null |
| | | // } else { |
| | | // productDetail.value = null |
| | | // ElMessage.error(detailRes?.msg || '获取产品详情失败') |
| | | // } |
| | | // productDetail.value = mockProductMap[productId] || null |
| | | const data = { |
| | | id: productId |
| | | } |
| | | const detailRes: any = await productApi.getProductById(data) |
| | | if (detailRes?.code === 200) { |
| | | productDetail.value = detailRes.data || null |
| | | } else { |
| | | productDetail.value = null |
| | | ElMessage.error(detailRes?.msg || '获取产品详情失败') |
| | | } |
| | | await loadPricingList(productId) |
| | | } catch (e) { |
| | | productDetail.value = null |
| | |
| | | |
| | | return { |
| | | id: row.id, |
| | | productId: Number(row.productId), |
| | | productId: row.productId, |
| | | productName: productDetail.value?.name, |
| | | suiteName: row.productSuite, |
| | | salesForm: mapSalesFormToCN(row.salesForm), |
| | |
| | | |
| | | return { |
| | | id: isEditMode.value && formData.id ? formData.id : undefined, |
| | | productId: Number(currentProductId.value), |
| | | productId: currentProductId.value, |
| | | productName: productDetail.value?.name, |
| | | suiteName: formData.productSuite, |
| | | salesForm: mapSalesFormToCN(formData.salesForm), |
| | |
| | | const res: any = await productPricingApi.update(payload) |
| | | if (res?.code === 200) { |
| | | ElMessage.success('修改成功') |
| | | } else { |
| | | } else { |
| | | ElMessage.error(res?.msg || '修改失败') |
| | | return |
| | | } |
| | |
| | | > |
| | | <el-table-column prop="id" label="产品ID" width="120" /> |
| | | <el-table-column prop="name" label="产品名称" min-width="200" /> |
| | | <el-table-column prop="productType" label="产品类型" width="120" /> |
| | | <el-table-column prop="industry" label="行业领域" width="150" /> |
| | | <el-table-column prop="submitUnit" label="提报单位" width="150" /> |
| | | <el-table-column prop="typeName" label="产品类型" width="120" /> |
| | | <el-table-column prop="importantAreaName" label="行业领域" width="150" /> |
| | | <el-table-column prop="submissionUnit" label="提报单位" width="150" /> |
| | | <el-table-column prop="shelfStatus" label="上架状态" width="100"> |
| | | <template #default="{ row }"> |
| | | <el-tag :type="getStatusType(row.shelfStatus)" size="small"> |
| | |
| | | </el-card> |
| | | |
| | | <!-- 产品价格查看弹窗 --> |
| | | <ProductPriceViewer |
| | | <!-- <ProductPriceViewer |
| | | v-model="priceViewerVisible" |
| | | :product-id="currentProductId" |
| | | @order="handleOrder" |
| | | /> |
| | | /> --> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { useRouter } from 'vue-router' |
| | | import { ElMessage } from 'element-plus' |
| | | import ProductPriceViewer from '@/views/productManage/productPriceViewer/index.vue' |
| | | import productApi from '@/api/productApi' |
| | | |
| | | interface ProductItem { |
| | | id: string |
| | |
| | | const priceViewerVisible = ref(false) |
| | | const currentProductId = ref('') |
| | | |
| | | // 模拟产品数据 |
| | | const mockProductList: ProductItem[] = [ |
| | | { |
| | | id: '1', |
| | | name: '数字化产品A', |
| | | productType: '软件/平台', |
| | | industry: '交通基础设施', |
| | | submitUnit: '中交一公局', |
| | | submitter: '张三', |
| | | projectUnit: '某高速公路工程', |
| | | industryStage: '应用阶段', |
| | | description: '本产品定位为以建设期BIM数字资产作为数字底盘,结合项目运营维保需求的实时性、交互性、便捷性的三维可视化运维管理系统。系统提供项目数字化、智能化运维管理功能,能够解决建筑运行维护管理中的实际问题,实现信息快速整合与查询、信息有效共享与传递,提升项目综合管理与维护水平。', |
| | | shelfStatus: '待上架' |
| | | }, |
| | | { |
| | | id: '2', |
| | | name: '数字化产品B', |
| | | productType: '硬件/传感', |
| | | industry: '市政工程', |
| | | submitUnit: '中交二航局', |
| | | submitter: '李四', |
| | | projectUnit: '智慧管廊项目', |
| | | industryStage: '研发阶段', |
| | | description: '面向城市管廊监测的传感设备与采集网关,支持边缘计算与远程运维。', |
| | | shelfStatus: '已上架' |
| | | }, |
| | | { |
| | | id: '3', |
| | | name: '数字化产品C', |
| | | productType: '软件/平台', |
| | | industry: '建筑工程', |
| | | submitUnit: '中交三航局', |
| | | submitter: '王五', |
| | | projectUnit: '智慧建筑项目', |
| | | industryStage: '应用阶段', |
| | | description: '基于BIM技术的建筑工程管理平台,提供设计、施工、运维全生命周期管理。', |
| | | shelfStatus: '已下架' |
| | | }, |
| | | { |
| | | id: '10004', |
| | | name: '数字化产品D', |
| | | productType: '硬件/传感', |
| | | industry: '水利工程', |
| | | submitUnit: '中交四航局', |
| | | submitter: '赵六', |
| | | projectUnit: '智慧水利项目', |
| | | industryStage: '研发阶段', |
| | | description: '水利工程监测设备与数据采集系统,支持实时监测和预警。', |
| | | shelfStatus: '已上架' |
| | | } |
| | | ] |
| | | |
| | | // 获取状态类型 |
| | | const getStatusType = (status: string) => { |
| | |
| | | loading.value = true |
| | | try { |
| | | // 模拟API调用 |
| | | await new Promise(resolve => setTimeout(resolve, 500)) |
| | | productList.value = mockProductList |
| | | // await new Promise(resolve => setTimeout(resolve, 500)) |
| | | let data = {"name":"","industrialChainId":"","importantAreaId":"","importantAreaIdList":[],"typeId":"","typeChildId":[],"startDate":"","endDate":"","page":{"current":1,"size":10,"total":3}} |
| | | const res: any = await productApi.getProductList(data) |
| | | if (res?.code === 200) { |
| | | productList.value = res.data.records || [] |
| | | } else { |
| | | productList.value = [] |
| | | ElMessage.error(res?.msg || '获取产品列表失败') |
| | | } |
| | | // productList.value = mockProductList |
| | | } catch (error) { |
| | | console.error('加载产品列表失败:', error) |
| | | ElMessage.error('加载产品列表失败') |
| | |
| | | |
| | | // 查看定价 |
| | | const handleViewPricing = (row: ProductItem) => { |
| | | currentProductId.value = row.id |
| | | priceViewerVisible.value = true |
| | | // currentProductId.value = row.id |
| | | // priceViewerVisible.value = true |
| | | router.push({ |
| | | path: '/product/priceViewer', |
| | | query: { productId: row.id} |
| | | }) |
| | | } |
| | | |
| | | // 处理订购 |
| | |
| | | <template> |
| | | <el-dialog |
| | | v-model="visible" |
| | | :title="dialogTitle" |
| | | :before-close="handleClose" |
| | | destroy-on-close |
| | | class="product-price-dialog" |
| | | :width="dialogWidth" |
| | | > |
| | | <div> |
| | | <div class="price-viewer-container" v-loading="loading"> |
| | | <!-- 价格对比表格 --> |
| | | <div class="pricing-table-container" v-if="showPricePanel && priceList.length > 0"> |
| | |
| | | > |
| | | <div class="pricing-table-wrapper"> |
| | | <table class="pricing-table"> |
| | | <thead> |
| | | <thead> |
| | | <tr> |
| | | <th class="feature-column">功能对比</th> |
| | | <th |
| | |
| | | <span class="label">产品名称:</span><span class="value strong">{{ productHeader.name }}</span> |
| | | </div> |
| | | <div class="grid-item"> |
| | | <span class="label">行业板块:</span><span class="value">{{ productHeader.industry }}</span> |
| | | <span class="label">行业板块:</span><span class="value">{{ productHeader.industrialChainName }}</span> |
| | | </div> |
| | | <div class="grid-item"> |
| | | <span class="label">提供者:</span><span class="value">{{ productHeader.provider }}</span> |
| | | <span class="label">提供者:</span><span class="value">{{ productHeader.submissionUnit }}</span> |
| | | </div> |
| | | <div class="grid-item"> |
| | | <span class="label">用户姓名:</span><span class="value">{{ userInfo.name }}</span> |
| | | </div> |
| | | <div class="grid-item"> |
| | | <span class="label">单位:</span><span class="value">{{ userInfo.unit }}</span> |
| | | <span class="label">单位:</span><span class="value">{{ userInfo.unitName }}</span> |
| | | </div> |
| | | <div class="grid-item"> |
| | | <span class="label">部门:</span><span class="value">{{ userInfo.department }}</span> |
| | | <span class="label">部门:</span><span class="value">{{ userInfo.departmentName }}</span> |
| | | </div> |
| | | </div> |
| | | </el-card> |
| | |
| | | <el-empty description="暂无价格信息" /> |
| | | </div> |
| | | </div> |
| | | |
| | | <template #footer v-if="!showOrderStatus"> |
| | | <span class="dialog-footer"> |
| | | <template v-if="showPricePanel"> |
| | | <el-button @click="handleClose">关闭</el-button> |
| | | <el-button type="primary" @click="handleOrder">立即订购</el-button> |
| | | </template> |
| | | <template v-else> |
| | | <el-button @click="returnPricePanel">返回价格对比</el-button> |
| | | <el-button type="primary" @click="submitOrder">提交申请</el-button> |
| | | </template> |
| | | </span> |
| | | </template> |
| | | </el-dialog> |
| | | <div class="footer" v-if="showPricePanel"> |
| | | <el-button type="primary" @click="handleOrder">立即订购</el-button> |
| | | </div> |
| | | <div class="footer" v-else> |
| | | <el-button @click="returnPricePanel">返回价格对比</el-button> |
| | | <el-button type="primary" @click="submitOrder">提交申请</el-button> |
| | | </div> |
| | | </div> |
| | | |
| | | <!-- 产品订购对话框移除,改为内嵌展示 --> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, watch, computed } from 'vue' |
| | | import { ref, watch, computed, onMounted } from 'vue' |
| | | import { ElMessage } from 'element-plus' |
| | | import { useRoute } from 'vue-router' |
| | | import productPricingApi from '@/api/productPricingApi' |
| | | import cartApi from '@/api/cartApi' |
| | | import orderApi from '@/api/orderApi' |
| | | import { ShoppingCart, Coin, Money } from '@element-plus/icons-vue' |
| | | import { useUserInfo } from '@/stores/modules/userInfo' |
| | | import { queryUserDetail } from '@/api/userInfo' |
| | | import productApi from '@/api/productApi' |
| | | |
| | | const route = useRoute() |
| | | |
| | |
| | | enableStatus: 'ENABLED' | 'DISABLED' |
| | | } |
| | | |
| | | interface Props { |
| | | modelValue: boolean |
| | | productId?: string |
| | | width?: string |
| | | } |
| | | |
| | | interface Emits { |
| | | (e: 'update:modelValue', value: boolean): void |
| | | (e: 'order', selectedItems: PriceItem[]): void |
| | | } |
| | | |
| | | // 兼容 path 参数 productId 与 query 参数 id/productId |
| | | const currentProductId = computed<string | undefined>(() => { |
| | | return (route.params.productId as string) || (route.query.productId as string) || (route.query.id as string) |
| | | }) |
| | | |
| | | |
| | | const props = withDefaults(defineProps<Props>(), { |
| | | modelValue: false, |
| | | productId: '', |
| | | width: '90%' |
| | | }) |
| | | |
| | | |
| | | const emit = defineEmits<Emits>() |
| | | |
| | | const visible = computed({ |
| | | get: () => props.modelValue, |
| | | set: (value) => emit('update:modelValue', value) |
| | | }) |
| | | |
| | | const loading = ref(false) |
| | | const activeTab = ref('enterprise') |
| | |
| | | const showPricePanel = ref(true) |
| | | const showOrderStatus = ref(false) |
| | | // 模拟用户信息(实际应从用户状态获取) |
| | | const currentUserId = ref(1) |
| | | const currentUnitId = ref(1) |
| | | const userStore = useUserInfo() |
| | | let currentUserId = computed(() => userStore.getUserId || userStore.getUserInfo?.userId) |
| | | const currentUnitId = computed(() => userStore.getUnitId || userStore.getUserInfo?.unitId || '') |
| | | |
| | | onMounted(async () => { |
| | | if (!currentUserId.value) { |
| | | try { |
| | | const res: any = await queryUserDetail() |
| | | if (res?.code === 200 && res.data) { |
| | | userStore.updateUserDetail(res.data) |
| | | currentUserId = res.data.userId || res.data.id |
| | | userInfo.value = res.data |
| | | } else { |
| | | ElMessage.error(res?.msg || '无法获取用户信息,请先登录') |
| | | return |
| | | } |
| | | } catch (e) { |
| | | console.error('获取用户详情失败:', e) |
| | | ElMessage.error('获取用户信息失败,请稍后重试') |
| | | return |
| | | } |
| | | }else{ |
| | | userInfo.value = userStore.getUserInfos |
| | | } |
| | | |
| | | }) |
| | | type PriceTypeKey = 'POINTS' | 'CURRENCY' | 'AGREEMENT' | 'FREE' |
| | | interface OrderSuite extends PriceItem { |
| | | selected: boolean |
| | |
| | | |
| | | // 产品信息/用户信息(静态) |
| | | const productHeader = ref({ |
| | | name: '中交方远智能实测实量管理系统', |
| | | industry: '公路,市政,建筑', |
| | | provider: '中交建筑集团第一工程有限公司' |
| | | name: '', |
| | | industrialChainName: '', |
| | | submissionUnit: '', |
| | | createUserId: '' |
| | | }) |
| | | const userInfo = ref({ |
| | | name: '张静', |
| | | unit: '信科集团', |
| | | department: '门户系统临时组' |
| | | name: '', |
| | | departmentName: '', |
| | | unitName: '' |
| | | }) |
| | | |
| | | // 全选 |
| | |
| | | const suite = orderSuites.value[index] |
| | | if (!suite) return |
| | | try { |
| | | await removeFromCart(suite.id) |
| | | await removeFromCart(String(suite.id)) |
| | | orderSuites.value.splice(index, 1) |
| | | } catch (e) { |
| | | } |
| | |
| | | ElMessage.success('订购申请提交成功!') |
| | | // 清空选择状态 |
| | | selectedSuiteIds.value.clear() |
| | | // 关闭价格查看器 |
| | | visible.value = false |
| | | } |
| | | |
| | | // 弹窗标题 |
| | |
| | | return `【${productName.value}】产品定价` |
| | | }) |
| | | |
| | | // 弹窗宽度 |
| | | const dialogWidth = computed(() => { |
| | | return props.width |
| | | }) |
| | | |
| | | // 按客户对象分组的价格数据 |
| | | const groupedPriceData = computed(() => { |
| | |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | //获取产品详情信息 |
| | | try { |
| | | // productDetail.value = mockProductMap[productId] || null |
| | | const data = { |
| | | id: productId |
| | | } |
| | | const detailRes: any = await productApi.getProductById(data) |
| | | if (detailRes?.code === 200) { |
| | | productHeader.value = detailRes.data || { name: '', industrialChainName: '', submissionUnit: '', createUserId: '' } |
| | | } else { |
| | | productHeader.value = { name: '', industrialChainName: '', submissionUnit: '', createUserId: '' } |
| | | ElMessage.error(detailRes?.msg || '获取产品详情失败') |
| | | } |
| | | } catch (e) { |
| | | productHeader.value = { name: '', industrialChainName: '', submissionUnit: '', createUserId: '' } |
| | | ElMessage.error('获取产品详情失败') |
| | | } finally { |
| | | loading.value = false |
| | | } |
| | | } |
| | | |
| | | // 监听产品ID变化 |
| | | watch(() => props.productId, (newProductId) => { |
| | | if (newProductId && visible.value) { |
| | | watch(currentProductId, (newProductId) => { |
| | | if (newProductId) { |
| | | fetchProductData(newProductId) |
| | | } |
| | | }) |
| | | |
| | | // 监听弹窗显示状态 |
| | | watch(() => visible.value, (newVisible) => { |
| | | if (newVisible && props.productId) { |
| | | fetchProductData(props.productId) |
| | | } |
| | | }) |
| | | |
| | | // 关闭弹窗 |
| | | const handleClose = () => { |
| | | visible.value = false |
| | | showOrderPanel.value = false |
| | | showOrderStatus.value = false |
| | | showPricePanel.value = true |
| | | orderSuites.value = [] |
| | | selectedSuiteIds.value.clear() |
| | | } |
| | | },{ immediate: true }) |
| | | |
| | | // 套件选择处理 |
| | | const handleSuiteSelect = (suiteId: number, checked: any) => { |
| | |
| | | } else { |
| | | selectedSuiteIds.value.delete(suiteId) |
| | | // 从购物车移除 |
| | | removeFromCart(suiteId) |
| | | removeFromCart(String(suiteId)) |
| | | } |
| | | } |
| | | |
| | |
| | | ElMessage.error('暂不支持货币交易,请选择积分或协议支付方式') |
| | | return |
| | | } |
| | | |
| | | |
| | | if (!idempotencyToken.value) { |
| | | ElMessage.error('下单Token获取失败,请返回重试') |
| | | return |
| | | } |
| | | |
| | | const hasPoints = items.some(item => item.priceType === 'POINTS') |
| | | const hasAGREEMENT = items.some(item => item.priceType === 'AGREEMENT') |
| | | let paymentType = '' |
| | | if(hasPoints){ |
| | | paymentType = '积分' |
| | | }else{ |
| | | paymentType = '协议' |
| | | } |
| | | // 组装创建订单参数(CreateOrderDTO) |
| | | const payload = { |
| | | userId: currentUserId.value, |
| | | unitId: currentUnitId.value, |
| | | productName: productHeader.value.name, |
| | | providerName: productHeader.value.provider, |
| | | providerId: 3, |
| | | paymentType: '积分', |
| | | providerName: productHeader.value.submissionUnit, |
| | | providerId: productHeader.value.createUserId, |
| | | paymentType: paymentType, |
| | | buyerRemarks: '', |
| | | items: items.map(it => ({ |
| | | pricingId: it.id, |
| | | productId: Number(it.productId), |
| | | productId: it.productId, |
| | | suiteName: getProductSuiteText(it.productSuite), |
| | | salesForm: getSalesFormText(it.salesForm), |
| | | customerType: getCustomerObjectText(it.customerObject), |
| | |
| | | quantity: it.quantity, |
| | | duration: it.duration, |
| | | totalPrice: undefined, |
| | | providerId: 3, |
| | | providerName: productHeader.value.provider, |
| | | providerId: productHeader.value.createUserId, |
| | | providerName: productHeader.value.submissionUnit, |
| | | remarks: '' |
| | | })) |
| | | } |
| | |
| | | orderStatus.value = { |
| | | id: data.orderId || '—', |
| | | productName: data.productName || productHeader.value.name, |
| | | provider: data.providerName || productHeader.value.provider, |
| | | provider: data.providerName || productHeader.value.submissionUnit, |
| | | status: 'SUBMITTED', |
| | | submitTime: data.applyTime ? String(data.applyTime) : new Date().toLocaleString(), |
| | | applyTime: data.applyTime ? String(data.applyTime) : new Date().toLocaleString() |
| | | } |
| | | // 调用工作流接口发起审批流程,拿到流程实例ID后回写订单workflow_id |
| | | try { |
| | | // 根据是否包含协议明细,配置不同流程定义与业务Key(先用静态值占位) |
| | | const processdefId = hasAGREEMENT ? 'Process_Agreement_Static' : 'Process_Points_Static' |
| | | const businessKey = hasAGREEMENT ? 'agreement_biz_key' : 'points_biz_key' |
| | | const wfRes: any = await orderApi.startWorkflowAndComplete({ |
| | | processdefId, |
| | | userid: String(currentUserId.value || ''), |
| | | businessKey |
| | | }) |
| | | if (wfRes?.code === 200 && wfRes.data?.processinstId) { |
| | | await orderApi.updateWorkflowId(data.orderId, wfRes.data.processinstId) |
| | | } |
| | | } catch (e) { |
| | | console.warn('启动工作流失败或更新workflow_id失败', e) |
| | | } |
| | | |
| | | // 清空购物车(后端 + 本地状态) |
| | | try { |
| | | const clearRes: any = await cartApi.clearCart(currentUserId.value, currentUnitId.value) |
| | |
| | | try { |
| | | const cartData = { |
| | | pricingId: priceItem.id, |
| | | productId: Number(priceItem.productId), |
| | | productId: priceItem.productId, |
| | | productName: productName.value, |
| | | suiteName: getProductSuiteText(priceItem.productSuite), |
| | | salesForm: getSalesFormText(priceItem.salesForm), |
| | |
| | | } |
| | | } |
| | | |
| | | const removeFromCart = async (pricingId: number) => { |
| | | const removeFromCart = async (pricingId: string) => { |
| | | try { |
| | | const res: any = await cartApi.removeFromCart(currentUserId.value, currentUnitId.value, pricingId) |
| | | if (res?.code === 200) { |
| | |
| | | .pricing-table-container { |
| | | .pricing-tabs { |
| | | :deep(.el-tabs__header) { |
| | | margin-bottom: 5px; |
| | | margin-bottom: 12px; |
| | | } |
| | | |
| | | /* 居中 tabs 导航 */ |
| | | :deep(.el-tabs__nav-wrap) { |
| | | display: flex; |
| | | justify-content: center; |
| | | } |
| | | :deep(.el-tabs__nav) { |
| | | margin: 0 auto; |
| | | border: none !important; |
| | | } |
| | | /* 卡片型 tabs 美化为圆角胶囊效果 */ |
| | | :deep(.el-tabs--card > .el-tabs__header .el-tabs__nav) { |
| | | border: none; |
| | | } |
| | | :deep(.el-tabs--card > .el-tabs__header .el-tabs__item) { |
| | | border: none; |
| | | } |
| | | :deep(.el-tabs__item) { |
| | | border-radius: 999px; |
| | | padding: 8px 18px; |
| | | margin: 0 6px; |
| | | color: #606266; |
| | | background: #f6f8fb; |
| | | transition: all .2s ease; |
| | | } |
| | | :deep(.el-tabs__item:hover) { |
| | | color: #3a7afe; |
| | | background: #eff4ff; |
| | | } |
| | | :deep(.el-tabs__item.is-active) { |
| | | font-weight: bold; |
| | | font-weight: 600; |
| | | color: #fff; |
| | | background: #3a7afe; |
| | | box-shadow: 0 6px 16px rgba(58, 122, 254, 0.25); |
| | | } |
| | | } |
| | | |
| | | .pricing-table-wrapper { |
| | | overflow-x: auto; |
| | | background: #fff; |
| | | border-radius: 12px; |
| | | box-shadow: 0 6px 24px rgba(0, 0, 0, 0.06); |
| | | padding: 8px; |
| | | |
| | | .pricing-table { |
| | | width: 100%; |
| | | border-collapse: collapse; |
| | | border: 1px solid #e4e7ed; |
| | | border-collapse: separate; |
| | | border-spacing: 0; |
| | | background: #fff; |
| | | border-radius: 10px; |
| | | |
| | | th, td { |
| | | border: 1px solid #e4e7ed; |
| | | padding: 6px 8px; |
| | | border-bottom: 1px solid #eef2f7; |
| | | border-right: 1px solid #f3f5f7; |
| | | padding: 12px 14px; |
| | | text-align: center; |
| | | vertical-align: middle; |
| | | font-size: 16px; |
| | | font-size: 14px; |
| | | } |
| | | tr th:first-child, td:first-child{ |
| | | font-weight: 700; |
| | |
| | | } |
| | | |
| | | th { |
| | | background-color: #f5f7fa; |
| | | background: linear-gradient(180deg, #f7f9fc 0%, #eef2f7 100%); |
| | | font-weight: 600; |
| | | color: #303133; |
| | | } |
| | |
| | | |
| | | .sub-header { |
| | | th { |
| | | background-color: #fafafa; |
| | | background-color: #f8fafc; |
| | | font-weight: 500; |
| | | font-size: 16px; |
| | | font-size: 13px; |
| | | color: #606266; |
| | | } |
| | | } |
| | |
| | | } |
| | | |
| | | .feature-value { |
| | | color: #606266; |
| | | background: #fafafa; /* 行为灰色 */ |
| | | color: #303133; |
| | | background: #fff; |
| | | } |
| | | |
| | | tbody tr:hover td { |
| | | background: #fafcff; |
| | | } |
| | | |
| | | .order-methods { |
| | | display: flex; |
| | | flex-direction: column; |
| | | gap: 4px; |
| | | gap: 6px; |
| | | align-items: center; |
| | | |
| | | .method-item { |
| | | font-size: 16px; |
| | | color: #409eff; |
| | | font-size: 12px; |
| | | color: #3a7afe; |
| | | background: #f4f7ff; |
| | | border: 1px solid #e3ebff; |
| | | border-radius: 999px; |
| | | padding: 2px 10px; |
| | | } |
| | | } |
| | | |
| | | .price-info { |
| | | display: flex; |
| | | flex-direction: column; /* 竖排 */ |
| | | gap: 8px; |
| | | gap: 10px; |
| | | align-items: center; |
| | | |
| | | .price-item { |
| | |
| | | align-items: flex-start; |
| | | flex-direction: column; |
| | | gap: 6px; |
| | | padding: 10px 12px; |
| | | border: 1px solid #f0f2f5; |
| | | border-radius: 10px; |
| | | background: #fff; |
| | | box-shadow: inset 0 -1px 0 rgba(0,0,0,0.02); |
| | | .price-lable-icon{ |
| | | display: flex; |
| | | gap: 6px; |
| | | } |
| | | .price-label { |
| | | font-size: 16px; |
| | | color: #606266; |
| | | font-size: 12px; |
| | | color: #909399; |
| | | } |
| | | |
| | | .price-value { |
| | | font-weight: 500; |
| | | font-size: 20px; |
| | | font-weight: 600; |
| | | font-size: 18px; |
| | | &.points { |
| | | color: #e7900d; |
| | | } |
| | | |
| | | &.currency { |
| | | color: #e7900d; |
| | | color: #10b981; |
| | | } |
| | | |
| | | &.free { |
| | |
| | | } |
| | | } |
| | | .price-icon.points { color: #e6a23c; } |
| | | .price-icon.currency { color: #e7900d; } |
| | | .price-icon.currency { color: #16a34a; } |
| | | } |
| | | |
| | | .add-checkbox { |
| | |
| | | justify-content: center; |
| | | align-items: center; |
| | | padding: 8px 0; |
| | | .add-cart-icon { color: #409eff; font-size:22px} |
| | | .add-cart-icon { color: #3a7afe; font-size:22px} |
| | | } |
| | | } |
| | | } |
| | |
| | | .order-summary .summary-value.highlight{ color:#409eff; } |
| | | .order-summary .summary-value.total{ color:#e6a23c; } |
| | | .order-summary .summary-value.currency{ color:#f56c6c; } |
| | | .footer { display: flex; flex-direction: row-reverse; margin-right: 50px; gap: 50px; margin-top: 20px;} |
| | | </style> |
| | |
| | | <el-input v-model="query.productName" placeholder="请输入产品名称" clearable style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="行业领域" class="col-25"> |
| | | <el-select v-model="query.industry" placeholder="请选择行业领域" clearable style="width: 100%"> |
| | | <el-select v-model="query.industry" placeholder="请选择行业领域" clearable style="width: 100%" @change="handleIndustryChange"> |
| | | <el-option v-for="item in industryOptions" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | <el-input v-model="query.orderNo" placeholder="请输入订单编号" clearable style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="产品类型" class="col-25"> |
| | | <el-select v-model="query.productType" placeholder="请选择产品类型" clearable style="width: 100%"> |
| | | <el-select v-model="query.productType" placeholder="请选择产品类型" clearable style="width: 100%" @change="handleProductTypeChange"> |
| | | <el-option v-for="item in productTypeOptions" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | <template #default="{ row }"> |
| | | <div v-if="row.isMainOrder" class="main-order-info"> |
| | | <div class="order-header"> |
| | | <div class="order-item status-item"> |
| | | <el-tag :type="getStatusType(row.status)" size="small">{{ row.statusName }}</el-tag> |
| | | </div> |
| | | |
| | | <div class="order-item"> |
| | | <span class="label">申请时间:</span> |
| | | <span class="value">{{ row.applyTime }}</span> |
| | |
| | | <div class="order-item"> |
| | | <span class="label">供应方:</span> |
| | | <span class="value">{{ row.supplySide }}</span> |
| | | </div> |
| | | <div class="order-item status-item"> |
| | | <el-tag :type="getStatusType(row.status)" size="small">{{ row.statusName }}</el-tag> |
| | | </div> |
| | | </div> |
| | | </div> |
| | |
| | | </el-card> |
| | | |
| | | <!-- 订单状态对话框 --> |
| | | <ProductOrderStatusDialog |
| | | <!-- <ProductOrderStatusDialog |
| | | v-model="orderStatusDialogVisible" |
| | | :order-id="currentOrderId" |
| | | /> |
| | | /> --> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { Search, Refresh } from '@element-plus/icons-vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import orderApi from '@/api/orderApi' |
| | | import productApi from '@/api/productApi' |
| | | import { useUserInfo } from '@/stores/modules/userInfo' |
| | | import ProductOrderStatusDialog from '@/views/productManage/productOrderStatusDialog/index.vue' |
| | | import { OrderWorkflowController, OrderStatus, ActionType, PageType, StatusMapper } from '@/utils/orderWorkflow' |
| | | import { queryUserDetail } from '@/api/userInfo' |
| | | |
| | | const router = useRouter() |
| | | const userStore = useUserInfo() |
| | |
| | | { label: '已评价', value: 'EVALUATED' }, |
| | | ] |
| | | |
| | | // 行业领域选项 |
| | | const industryOptions = [ |
| | | { label: '建筑工程', value: 'construction' }, |
| | | { label: '交通工程', value: 'transportation' }, |
| | | { label: '水利工程', value: 'water' }, |
| | | { label: '电力工程', value: 'power' }, |
| | | ] |
| | | |
| | | // 单位工程选项 |
| | | const unitProjectOptions = [ |
| | | { label: '土建工程', value: 'civil' }, |
| | | { label: '安装工程', value: 'installation' }, |
| | | { label: '装饰工程', value: 'decoration' }, |
| | | ] |
| | | |
| | | // 产品类型选项 |
| | | const productTypeOptions = [ |
| | | { label: '企业私有SaaS版许可', value: 'enterprise_private' }, |
| | | { label: '企业公有SaaS版许可', value: 'enterprise_public' }, |
| | | { label: '项目公有SaaS版许可', value: 'project_public' }, |
| | | { label: '个人公有SaaS版许可', value: 'personal_public' }, |
| | | ] |
| | | |
| | | // 产品类型子级选项 |
| | | const productSubTypeOptions = [ |
| | | { label: '基础版', value: 'basic' }, |
| | | { label: '标准版', value: 'standard' }, |
| | | { label: '高级版', value: 'premium' }, |
| | | ] |
| | | // 动态选项数据 |
| | | const industryOptions = ref<any[]>([]) |
| | | const unitProjectOptions = ref<any[]>([]) |
| | | const productTypeOptions = ref<any[]>([]) |
| | | const productSubTypeOptions = ref<any[]>([]) |
| | | |
| | | // 查询条件 |
| | | const query = reactive({ |
| | |
| | | if (/(协议|agreement)/i.test(s)) return 'agreement' |
| | | if (/(免费|free)/i.test(s)) return 'free' |
| | | return 'currency' |
| | | } |
| | | |
| | | // 获取行业领域选项 |
| | | const getIndustryOptions = async () => { |
| | | try { |
| | | const res = await productApi.getCategoryByParent({ parentCode: 'business_direction' }) |
| | | if (res?.code === 200 && res.data) { |
| | | industryOptions.value = res.data.map((item: any) => ({ |
| | | label: item.name, |
| | | value: item.id |
| | | })) |
| | | } |
| | | } catch (error) { |
| | | console.error('获取行业领域选项失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 获取产品类型选项 |
| | | const getProductTypeOptions = async () => { |
| | | try { |
| | | const res = await productApi.getCategoryByParent({ parentCode: 'product_type' }) |
| | | if (res?.code === 200 && res.data) { |
| | | productTypeOptions.value = res.data.map((item: any) => ({ |
| | | label: item.name, |
| | | value: item.id |
| | | })) |
| | | } |
| | | } catch (error) { |
| | | console.error('获取产品类型选项失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 根据行业领域获取单位工程选项 |
| | | const getUnitProjectOptions = async (industryCode: string) => { |
| | | if (!industryCode) { |
| | | unitProjectOptions.value = [] |
| | | return |
| | | } |
| | | try { |
| | | const res = await productApi.getCategoryByParent({ parentId: industryCode }) |
| | | if (res?.code === 200 && res.data) { |
| | | unitProjectOptions.value = res.data.map((item: any) => ({ |
| | | label: item.name, |
| | | value: item.id |
| | | })) |
| | | } |
| | | } catch (error) { |
| | | console.error('获取单位工程选项失败:', error) |
| | | unitProjectOptions.value = [] |
| | | } |
| | | } |
| | | |
| | | // 根据产品类型获取产品子级选项 |
| | | const getProductSubTypeOptions = async (productTypeCode: string) => { |
| | | if (!productTypeCode) { |
| | | productSubTypeOptions.value = [] |
| | | return |
| | | } |
| | | try { |
| | | const res = await productApi.getCategoryByParent({ parentId: productTypeCode }) |
| | | if (res?.code === 200 && res.data) { |
| | | productSubTypeOptions.value = res.data.map((item: any) => ({ |
| | | label: item.name, |
| | | value: item.id |
| | | })) |
| | | } |
| | | } catch (error) { |
| | | console.error('获取产品子级选项失败:', error) |
| | | productSubTypeOptions.value = [] |
| | | } |
| | | } |
| | | |
| | | // 处理行业领域变化 |
| | | const handleIndustryChange = async (value: string) => { |
| | | // 清空单位工程选择 |
| | | query.unitProject = '' |
| | | // 获取对应的单位工程选项 |
| | | await getUnitProjectOptions(value) |
| | | } |
| | | |
| | | // 处理产品类型变化 |
| | | const handleProductTypeChange = async (value: string) => { |
| | | // 清空产品子级选择 |
| | | query.productSubType = '' |
| | | // 获取对应的产品子级选项 |
| | | await getProductSubTypeOptions(value) |
| | | } |
| | | |
| | | // 获取状态类型 |
| | |
| | | pageSize: page.size, |
| | | productName: query.productName || undefined, |
| | | orderId: query.orderNo || undefined, |
| | | userId: userStore.getUserId ? Number(userStore.getUserId) : undefined, |
| | | userId: userStore.getUserId ? userStore.getUserId : undefined, |
| | | } |
| | | if (query.status) payload.orderStatus = statusUiToServer[query.status] |
| | | if (Array.isArray(query.dateRange) && query.dateRange.length === 2) { |
| | | payload.applyTimeStart = query.dateRange[0] |
| | | payload.applyTimeEnd = query.dateRange[1] |
| | | } |
| | | |
| | | // 添加产品条件查询 |
| | | if (query.industry) payload.industryId = query.industry |
| | | if (query.unitProject) payload.unitProjectId = query.unitProject |
| | | if (query.productType) payload.productTypeId = query.productType |
| | | if (query.productSubType) payload.productSubTypeId = query.productSubType |
| | | |
| | | const res = (await orderApi.getBuyerOrderPage(payload)) as any |
| | | // 根据是否有产品条件选择不同的API |
| | | const hasProductConditions = query.industry || query.unitProject || query.productType || query.productSubType |
| | | const apiMethod = hasProductConditions ? orderApi.getBuyerOrderPageWithProductConditions : orderApi.getBuyerOrderPage |
| | | |
| | | const res = (await apiMethod(payload)) as any |
| | | const pageData = res?.data |
| | | const list: any[] = Array.isArray(pageData?.list) ? pageData.list : [] |
| | | page.total = Number(pageData?.total || 0) |
| | | |
| | | // 并发获取每个订单的详情(用于构造子订单行) |
| | | const detailsArr = await Promise.all( |
| | | list.map(async (order: any) => { |
| | | try { |
| | | const detailRes = (await orderApi.getOrderDetail(order.orderId)) as any |
| | | return detailRes?.data |
| | | } catch (e) { |
| | | return null |
| | | } |
| | | }) |
| | | ) |
| | | |
| | | const flatData: any[] = [] |
| | | list.forEach((order: any, idx: number) => { |
| | | list.forEach((order: any) => { |
| | | const uiStatus = statusServerToUi[order.orderStatus] || 'WAIT_UPLOAD' |
| | | const mainRow: any = { |
| | | id: order.orderId, |
| | |
| | | status: uiStatus, |
| | | statusName: order.orderStatus || '', |
| | | orderStatus: StatusMapper.toUIStatus(order.orderStatus), // 转换为标准状态枚举 |
| | | workFlowId: order.workflowId || '' |
| | | } |
| | | |
| | | const detail = detailsArr[idx] |
| | | const subOrders: any[] = Array.isArray(detail?.orderDetails) |
| | | ? detail.orderDetails.map((d: any, i: number) => ({ |
| | | const subOrders: any[] = Array.isArray(order?.orderDetails) |
| | | ? order.orderDetails.map((d: any, i: number) => ({ |
| | | id: `${order.orderId}-${i + 1}`, |
| | | isMainOrder: false, |
| | | productName: order.productName || '', |
| | |
| | | status: '', |
| | | dateRange: [], |
| | | }) |
| | | // 清空动态选项 |
| | | unitProjectOptions.value = [] |
| | | productSubTypeOptions.value = [] |
| | | page.current = 1 |
| | | handleSearch() |
| | | } |
| | |
| | | const toUpload = (row: any) => router.push({ name: 'tradeOrderUpload', params: { id: row.id } }) |
| | | const toConfirm = (row: any) => router.push({ name: 'tradeOrderConfirm', params: { id: row.id } }) |
| | | const toEvaluate = (row: any) => router.push({ name: 'tradeOrderEvaluate', params: { id: row.id } }) |
| | | const toTrack = (row: any) => router.push({ name: 'tradeTrack', params: { id: row.id } }) |
| | | const toTrack = (row: any) => { |
| | | if (!row.workFlowId) { |
| | | ElMessage.warning('该订单暂无工作流信息') |
| | | return |
| | | } |
| | | router.push({ name: 'tradeTrack', params: { id: row.workFlowId } }) |
| | | } |
| | | |
| | | // 追踪订单 - 显示订单状态对话框 |
| | | const showOrderTrack = (row: any) => { |
| | |
| | | toDetail(order) |
| | | break |
| | | case ActionType.TRACK: |
| | | showOrderTrack(order) |
| | | // showOrderTrack(order) |
| | | toTrack(order) |
| | | break |
| | | case ActionType.UPLOAD_FILE: |
| | | toUpload(order) |
| | |
| | | } |
| | | } |
| | | |
| | | onMounted(handleSearch) |
| | | onMounted(async ()=>{ |
| | | // 获取用户信息 |
| | | if (!userStore.getUserId) { |
| | | try { |
| | | const res: any = await queryUserDetail() |
| | | if (res?.code === 200 && res.data) { |
| | | userStore.updateUserDetail(res.data) |
| | | } else { |
| | | ElMessage.error(res?.msg || '无法获取用户信息,请先登录') |
| | | return |
| | | } |
| | | } catch (e) { |
| | | console.error('获取用户详情失败:', e) |
| | | ElMessage.error('获取用户信息失败,请稍后重试') |
| | | return |
| | | } |
| | | } |
| | | |
| | | // 获取初始选项数据 |
| | | await Promise.all([ |
| | | getIndustryOptions(), |
| | | getProductTypeOptions() |
| | | ]) |
| | | |
| | | // 执行搜索 |
| | | handleSearch() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
| | |
| | | gap: 20px; |
| | | align-items: center; |
| | | overflow: hidden; |
| | | |
| | | .status-item{ |
| | | flex-direction: row-reverse; |
| | | } |
| | | .order-item { |
| | | display: flex; |
| | | align-items: center; |
| | | gap: 8px; |
| | | flex-shrink: 0; |
| | | flex: 1; |
| | | |
| | | .label { |
| | | color: #909399; |
| | |
| | | </el-card> |
| | | |
| | | <!-- 审批追踪 --> |
| | | <el-card class="mt15" shadow="never" v-if="detail.records?.length"> |
| | | <el-card class="mt15" shadow="never"> |
| | | <div class="title">审批追踪</div> |
| | | |
| | | <!-- 标签页 --> |
| | | <el-tabs v-model="activeTab" class="approval-tabs"> |
| | | <!-- <el-tabs v-model="activeTab" class="approval-tabs"> |
| | | <el-tab-pane label="审批记录" name="records"> |
| | | <el-table |
| | | :data="detail.records" |
| | |
| | | </el-table-column> |
| | | </el-table> |
| | | </el-tab-pane> |
| | | </el-tabs> |
| | | </el-tabs> --> |
| | | |
| | | <!-- 返回按钮 --> |
| | | <div class="action-buttons"> |
| | | <!-- 外部系统审批轨迹 iframe --> |
| | | <div class="iframe-wrap" v-if="workflowIframeUrl"> |
| | | <iframe :src="workflowIframeUrl" class="workflow-iframe" referrerpolicy="no-referrer"></iframe> |
| | | </div> |
| | | |
| | | </el-card> |
| | | <!-- 返回按钮 --> |
| | | <div class="action-buttons"> |
| | | <el-button @click="goBack">返回</el-button> |
| | | </div> |
| | | </el-card> |
| | | </div> |
| | | </template> |
| | | |
| | |
| | | import { ElMessage } from 'element-plus' |
| | | import orderApi from '@/api/orderApi' |
| | | import createAxios from '@/utils/axios' |
| | | import productApi from '@/api/productApi' |
| | | import { useUserInfo } from '@/stores/modules/userInfo' |
| | | |
| | | const hostUrl = import.meta.env.VITE_AXIOS_BASE_URL |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | const detail = reactive<any>({ items: [], records: [], nodes: [] }) |
| | | const orderTableWrapRef = ref<HTMLElement | null>(null) |
| | | const activeTab = ref('records') |
| | | const userStore = useUserInfo() |
| | | // 外部审批轨迹iframe地址 |
| | | const workflowIframeUrl = computed(() => { |
| | | const pid = (detail.workflowId || route.query.processinstId || '').toString().trim() |
| | | if (!pid) return '' |
| | | return `${hostUrl}/activity/history?processinstId=${encodeURIComponent(pid)}&token=${userStore.getAdminToken}` |
| | | }) |
| | | const labelStyle = { width: '180px', maxWidth: '180px' } |
| | | const contentStyle = { width: 'calc(50% - 180px)' } |
| | | |
| | |
| | | const statusName: string = data.orderStatus || '' |
| | | const uiStatus = statusServerToUi[statusName] || 'INFO' |
| | | |
| | | // 根据产品id获取产品信息,更新头部展示 |
| | | try { |
| | | if (data.productId) { |
| | | const detailRes: any = await productApi.getProductById({ id: data.productId }) |
| | | if (detailRes?.code === 200 && detailRes.data) { |
| | | // 用产品详情补全头信息 |
| | | data.productName = detailRes.data.name || data.productName |
| | | data.providerName = detailRes.data.submissionUnit || data.providerName |
| | | data.industry = detailRes.data.industrialChainName || data.industry |
| | | data.productDesc = detailRes.data.describe || data.productDesc |
| | | data.projectUnit = detailRes.data.importantAreaName || data.productDesc |
| | | data.productType = detailRes.data.typeName || data.productDesc |
| | | } |
| | | } |
| | | } catch (e) { |
| | | // 忽略产品详情失败,不阻塞订单详情 |
| | | } |
| | | |
| | | // 获取用户信息 |
| | | |
| | | // 映射订单详情头部信息 |
| | | const head = { |
| | | orderNo: data.orderId, |
| | |
| | | cashTotal: cashTotalNum.toLocaleString(), |
| | | records: [], |
| | | nodes: [], |
| | | workflowId: data.workflowId || data.processinstId || '' |
| | | }) |
| | | |
| | | // 初始化交易信息备注数据 |
| | |
| | | text-align: left !important; |
| | | } |
| | | |
| | | /* 外部审批轨迹 iframe 样式 */ |
| | | .iframe-wrap { margin-top: 10px; } |
| | | .workflow-iframe { width: 100%; height: 500px; border: none; border-radius: 6px; } |
| | | |
| | | /* 文件操作按钮样式 */ |
| | | .file-actions { |
| | | display: flex; |
| | |
| | | <el-input v-model="query.productName" placeholder="请输入产品名称" clearable style="width: 100%" /> |
| | | </el-form-item> |
| | | <el-form-item label="行业领域" class="col-25"> |
| | | <el-select v-model="query.industry" placeholder="请选择行业领域" clearable style="width: 100%"> |
| | | <el-select v-model="query.industry" placeholder="请选择行业领域" clearable style="width: 100%" @change="handleIndustryChange"> |
| | | <el-option v-for="item in industryOptions" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | </el-select> |
| | | </el-form-item> |
| | | <el-form-item label="产品类型" class="col-30"> |
| | | <el-select v-model="query.productType" placeholder="请选择产品类型" clearable style="width: 100%"> |
| | | <el-select v-model="query.productType" placeholder="请选择产品类型" clearable style="width: 100%" @change="handleProductTypeChange"> |
| | | <el-option v-for="item in productTypeOptions" :key="item.value" :label="item.label" :value="item.value" /> |
| | | </el-select> |
| | | </el-form-item> |
| | |
| | | > |
| | | 查看 |
| | | </el-button> |
| | | <el-button |
| | | <!-- <el-button |
| | | v-else-if="action.type === ActionType.TRACK" |
| | | type="primary" |
| | | link |
| | |
| | | @click="handleAction(action, row.parentOrder)" |
| | | > |
| | | 追踪 |
| | | </el-button> |
| | | </el-button> --> |
| | | </template> |
| | | </div> |
| | | </div> |
| | |
| | | import { Search, Refresh } from '@element-plus/icons-vue' |
| | | import { ElMessage, ElMessageBox } from 'element-plus' |
| | | import orderApi from '@/api/orderApi' |
| | | import productApi from '@/api/productApi' |
| | | import { useUserInfo } from '@/stores/modules/userInfo' |
| | | import ProductOrderStatusDialog from '@/views/productManage/productOrderStatusDialog/index.vue' |
| | | import { OrderWorkflowController, OrderStatus, ActionType, PageType, StatusMapper } from '@/utils/orderWorkflow' |
| | | import { queryUserDetail } from '@/api/userInfo' |
| | | |
| | | const router = useRouter() |
| | | const userStore = useUserInfo() |
| | |
| | | { label: '已评价', value: 'EVALUATED' }, |
| | | ] |
| | | |
| | | // 行业领域选项 |
| | | const industryOptions = [ |
| | | { label: '建筑工程', value: 'construction' }, |
| | | { label: '交通工程', value: 'transportation' }, |
| | | { label: '水利工程', value: 'water' }, |
| | | { label: '电力工程', value: 'power' }, |
| | | ] |
| | | |
| | | // 单位工程选项 |
| | | const unitProjectOptions = [ |
| | | { label: '土建工程', value: 'civil' }, |
| | | { label: '安装工程', value: 'installation' }, |
| | | { label: '装饰工程', value: 'decoration' }, |
| | | ] |
| | | |
| | | // 产品类型选项 |
| | | const productTypeOptions = [ |
| | | { label: '企业私有SaaS版许可', value: 'enterprise_private' }, |
| | | { label: '企业公有SaaS版许可', value: 'enterprise_public' }, |
| | | { label: '项目公有SaaS版许可', value: 'project_public' }, |
| | | { label: '个人公有SaaS版许可', value: 'personal_public' }, |
| | | ] |
| | | |
| | | // 产品类型子级选项 |
| | | const productSubTypeOptions = [ |
| | | { label: '基础版', value: 'basic' }, |
| | | { label: '标准版', value: 'standard' }, |
| | | { label: '高级版', value: 'premium' }, |
| | | ] |
| | | // 动态选项数据 |
| | | const industryOptions = ref<any[]>([]) |
| | | const unitProjectOptions = ref<any[]>([]) |
| | | const productTypeOptions = ref<any[]>([]) |
| | | const productSubTypeOptions = ref<any[]>([]) |
| | | |
| | | // 查询条件 |
| | | const query = reactive({ |
| | |
| | | if (/(协议|agreement)/i.test(s)) return 'agreement' |
| | | if (/(免费|free)/i.test(s)) return 'free' |
| | | return 'currency' |
| | | } |
| | | |
| | | // 获取行业领域选项 |
| | | const getIndustryOptions = async () => { |
| | | try { |
| | | const res = await productApi.getCategoryByParent({ parentCode: 'business_direction' }) |
| | | if (res?.code === 200 && res.data) { |
| | | industryOptions.value = res.data.map((item: any) => ({ |
| | | label: item.name, |
| | | value: item.id |
| | | })) |
| | | } |
| | | } catch (error) { |
| | | console.error('获取行业领域选项失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 获取产品类型选项 |
| | | const getProductTypeOptions = async () => { |
| | | try { |
| | | const res = await productApi.getCategoryByParent({ parentCode: 'product_type' }) |
| | | if (res?.code === 200 && res.data) { |
| | | productTypeOptions.value = res.data.map((item: any) => ({ |
| | | label: item.name, |
| | | value: item.id |
| | | })) |
| | | } |
| | | } catch (error) { |
| | | console.error('获取产品类型选项失败:', error) |
| | | } |
| | | } |
| | | |
| | | // 根据行业领域获取单位工程选项 |
| | | const getUnitProjectOptions = async (industryCode: string) => { |
| | | if (!industryCode) { |
| | | unitProjectOptions.value = [] |
| | | return |
| | | } |
| | | try { |
| | | const res = await productApi.getCategoryByParent({ parentId: industryCode }) |
| | | if (res?.code === 200 && res.data) { |
| | | unitProjectOptions.value = res.data.map((item: any) => ({ |
| | | label: item.name, |
| | | value: item.id |
| | | })) |
| | | } |
| | | } catch (error) { |
| | | console.error('获取单位工程选项失败:', error) |
| | | unitProjectOptions.value = [] |
| | | } |
| | | } |
| | | |
| | | // 根据产品类型获取产品子级选项 |
| | | const getProductSubTypeOptions = async (productTypeCode: string) => { |
| | | if (!productTypeCode) { |
| | | productSubTypeOptions.value = [] |
| | | return |
| | | } |
| | | try { |
| | | const res = await productApi.getCategoryByParent({ parentId: productTypeCode }) |
| | | if (res?.code === 200 && res.data) { |
| | | productSubTypeOptions.value = res.data.map((item: any) => ({ |
| | | label: item.name, |
| | | value: item.id |
| | | })) |
| | | } |
| | | } catch (error) { |
| | | console.error('获取产品子级选项失败:', error) |
| | | productSubTypeOptions.value = [] |
| | | } |
| | | } |
| | | |
| | | // 处理行业领域变化 |
| | | const handleIndustryChange = async (value: string) => { |
| | | // 清空单位工程选择 |
| | | query.unitProject = '' |
| | | // 获取对应的单位工程选项 |
| | | await getUnitProjectOptions(value) |
| | | } |
| | | |
| | | // 处理产品类型变化 |
| | | const handleProductTypeChange = async (value: string) => { |
| | | // 清空产品子级选择 |
| | | query.productSubType = '' |
| | | // 获取对应的产品子级选项 |
| | | await getProductSubTypeOptions(value) |
| | | } |
| | | |
| | | |
| | |
| | | try { |
| | | // 获取用户ID作为providerId,如果没有则使用默认值 |
| | | const userId = userStore.getUserId |
| | | const providerId = userId ? Number(userId) : 1 // 使用默认值1作为临时解决方案 |
| | | const providerId = userId ? userId : '' // 使用默认值1作为临时解决方案 |
| | | |
| | | const payload: any = { |
| | | pageNum: page.current, |
| | |
| | | payload.applyTimeEnd = query.dateRange[1] |
| | | } |
| | | |
| | | const res = (await orderApi.getSellerOrderPage(payload)) as any |
| | | // 添加产品条件查询 |
| | | if (query.industry) payload.industryId = query.industry |
| | | if (query.unitProject) payload.unitProjectId = query.unitProject |
| | | if (query.productType) payload.productTypeId = query.productType |
| | | if (query.productSubType) payload.productSubTypeId = query.productSubType |
| | | |
| | | // 根据是否有产品条件选择不同的API |
| | | const hasProductConditions = query.industry || query.unitProject || query.productType || query.productSubType |
| | | const apiMethod = hasProductConditions ? orderApi.getSellerOrderPageWithProductConditions : orderApi.getSellerOrderPage |
| | | |
| | | const res = (await apiMethod(payload)) as any |
| | | const pageData = res?.data |
| | | const list: any[] = Array.isArray(pageData?.list) ? pageData.list : [] |
| | | page.total = Number(pageData?.total || 0) |
| | | |
| | | // 并发获取每个订单的详情(用于构造子订单行) |
| | | const detailsArr = await Promise.all( |
| | | list.map(async (order: any) => { |
| | | try { |
| | | const detailRes = (await orderApi.getOrderDetail(order.orderId)) as any |
| | | return detailRes?.data |
| | | } catch (e) { |
| | | return null |
| | | } |
| | | }) |
| | | ) |
| | | |
| | | const flatData: any[] = [] |
| | | list.forEach((order: any, idx: number) => { |
| | | list.forEach((order: any) => { |
| | | const uiStatus = statusServerToUi[order.orderStatus] || 'WAIT_UPLOAD' |
| | | const mainRow: any = { |
| | | id: order.orderId, |
| | |
| | | status: uiStatus, |
| | | statusName: order.orderStatus || '', |
| | | orderStatus: StatusMapper.toUIStatus(order.orderStatus), // 转换为标准状态枚举 |
| | | workFlowId: order.workflowId || '' |
| | | } |
| | | |
| | | const detail = detailsArr[idx] |
| | | const subOrders: any[] = Array.isArray(detail?.orderDetails) |
| | | ? detail.orderDetails.map((d: any, i: number) => ({ |
| | | const subOrders: any[] = Array.isArray(order?.orderDetails) |
| | | ? order.orderDetails.map((d: any, i: number) => ({ |
| | | id: `${order.orderId}-${i + 1}`, |
| | | isMainOrder: false, |
| | | productName: order.productName || '', |
| | |
| | | status: '', |
| | | dateRange: [], |
| | | }) |
| | | // 清空动态选项 |
| | | unitProjectOptions.value = [] |
| | | productSubTypeOptions.value = [] |
| | | page.current = 1 |
| | | handleSearch() |
| | | } |
| | |
| | | // 路由跳转 |
| | | const toDetail = (row: any) => router.push({ name: 'tradeOrderDetail', params: { id: row.id } }) |
| | | |
| | | onMounted(handleSearch) |
| | | onMounted(async ()=>{ |
| | | // 获取用户信息 |
| | | if (!userStore.getUserId) { |
| | | try { |
| | | const res: any = await queryUserDetail() |
| | | if (res?.code === 200 && res.data) { |
| | | userStore.updateUserDetail(res.data) |
| | | } else { |
| | | ElMessage.error(res?.msg || '无法获取用户信息,请先登录') |
| | | return |
| | | } |
| | | } catch (e) { |
| | | console.error('获取用户详情失败:', e) |
| | | ElMessage.error('获取用户信息失败,请稍后重试') |
| | | return |
| | | } |
| | | } |
| | | |
| | | // 获取初始选项数据 |
| | | await Promise.all([ |
| | | getIndustryOptions(), |
| | | getProductTypeOptions() |
| | | ]) |
| | | |
| | | // 执行搜索 |
| | | handleSearch() |
| | | }) |
| | | </script> |
| | | |
| | | <style scoped lang="scss"> |
New file |
| | |
| | | <template> |
| | | <div class="track-page"> |
| | | <el-card shadow="never"> |
| | | |
| | | <div class="content"> |
| | | <div class="iframe-wrap" v-if="iframeUrl"> |
| | | <iframe :src="iframeUrl" class="workflow-iframe" referrerpolicy="no-referrer"></iframe> |
| | | </div> |
| | | <div v-else class="empty">暂无流程实例ID</div> |
| | | </div> |
| | | </el-card> |
| | | <div class="action-buttons"> |
| | | <el-button @click="goBack">返回</el-button> |
| | | </div> |
| | | </div> |
| | | </template> |
| | | |
| | | <script setup lang="ts"> |
| | | import { ref, computed } from 'vue' |
| | | import { useRoute, useRouter } from 'vue-router' |
| | | |
| | | const route = useRoute() |
| | | const router = useRouter() |
| | | |
| | | const processId = computed(() => { |
| | | return route.query.processinstId || route.params.id || '' |
| | | }) |
| | | |
| | | const hostUrl = import.meta.env.VITE_AXIOS_BASE_URL |
| | | |
| | | const iframeUrl = computed(() => { |
| | | const pid = processId.value |
| | | if (pid && pid.toString().trim()) { |
| | | return `${hostUrl}/activity/history?processinstId=${encodeURIComponent(pid.toString().trim())}` |
| | | } |
| | | return '' |
| | | }) |
| | | |
| | | const goBack = () => router.back() |
| | | </script> |
| | | |
| | | <style scoped> |
| | | .track-page { |
| | | padding: 20px; |
| | | } |
| | | .title { |
| | | font-weight: 600; |
| | | margin-bottom: 10px; |
| | | } |
| | | .content { |
| | | margin-top: 10px; |
| | | } |
| | | .iframe-wrap { |
| | | margin-top: 10px; |
| | | } |
| | | .workflow-iframe { |
| | | width: 100%; |
| | | height: 600px; |
| | | border: none; |
| | | border-radius: 6px; |
| | | } |
| | | .action-buttons { |
| | | display: flex; |
| | | justify-content: center; |
| | | margin-top: 15px; |
| | | } |
| | | .empty { |
| | | text-align: center; |
| | | color: #909399; |
| | | padding: 40px 0; |
| | | } |
| | | </style> |
| | |
| | | const viteConfig = ({ mode }: ConfigEnv): UserConfig => { |
| | | let proxy: Record<string, string | ProxyOptions> = {} |
| | | proxy = { |
| | | '/api/test': { |
| | | target: 'http://36.133.126.111:7099', |
| | | changeOrigin: true, |
| | | rewrite: (path) => path.replace(/^\/api\/test/, '/api'), |
| | | }, |
| | | '/api': { |
| | | // target: 'http://192.168.0.38:8088', // 李 |
| | | // target: 'http://10.88.211.191:8088', // 李 |