From b61e5ebafe715cd9662fe1915069a60d4b52ed5d Mon Sep 17 00:00:00 2001 From: yuhawu <15545526+yuhawu@user.noreply.gitee.com> Date: Tue, 8 Jul 2025 18:57:05 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=90=8E=E5=8F=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/interceptors/response.js | 122 +++++-- src/api/modules/order.js | 44 +++ src/api/modules/shop.d.ts | 77 ++++ src/api/modules/shop.js | 66 ++++ src/layout/Sidebar.vue | 30 +- src/router/index.js | 10 + src/utils/request.js | 57 +++ src/views/SettledConfig/List.vue | 84 +++-- src/views/Shop/index.vue | 608 +++++++++++++++++++++++++++++++ vite.config.mjs | 2 +- 10 files changed, 1019 insertions(+), 81 deletions(-) create mode 100644 src/api/modules/order.js create mode 100644 src/api/modules/shop.d.ts create mode 100644 src/api/modules/shop.js create mode 100644 src/utils/request.js create mode 100644 src/views/Shop/index.vue diff --git a/src/api/interceptors/response.js b/src/api/interceptors/response.js index e8e0e0a..c31698f 100644 --- a/src/api/interceptors/response.js +++ b/src/api/interceptors/response.js @@ -1,39 +1,89 @@ //响应拦截器 +import axios from 'axios'; // 引入axios用于创建新请求 + export function setupResponseInterceptors(instance) { - instance.interceptors.response.use( - response => { - // 解析 response - const { data, config } = response; - - if(response.data.code != 200){ - // 创建可追踪的业务逻辑错误 - const error = new Error(data.message || '业务逻辑错误'); - error.name = 'BusinessError'; // 自定义错误类型标识 - error.config = config; // 保留请求配置 - error.data = data; // 保留响应数据 - error.code = data.code; // 业务错误码 - - //必须 return Promise.reject 才能触发 catch - return Promise.reject(error); - } - // 处理响应数据格式 - return data - }, - error => { - // 统一错误处理 - if (error.response) { - switch (error.response.status) { - case 401: - // 处理未授权 - break - case 403: - // 处理禁止访问 - break - default: - console.error('响应错误:', error) - } - } - return Promise.reject(error) - } - ) + // 用于存储正在刷新token的Promise + let isRefreshing = false; + // 存储等待token刷新的请求队列 + let requests = []; + + instance.interceptors.response.use( + response => { + const { data } = response; + if (data.code != 200) { + const error = new Error(data.message || '业务逻辑错误'); + error.name = 'BusinessError'; + error.data = data; + error.code = data.code; + return Promise.reject(error); + } + return data; + }, + async error => { + // 获取原始请求配置 + const originalRequest = error.config; + + // 判断是否401错误(token过期)且没有在刷新中 + if (error.response?.status === 401 && !originalRequest._retry) { + // 标记该请求已尝试过重试 + originalRequest._retry = true; + + // 如果当前没有在刷新token + if (!isRefreshing) { + isRefreshing = true; + + try { + // 从localStorage获取refreshToken + const refreshToken = localStorage.getItem('refreshToken'); + + if (!refreshToken) { + // 无refreshToken,跳转到登录页 + window.location.href = '/login'; + return Promise.reject(error); + } + + // 调用刷新token接口 + const response = await axios.post('/admin/getAccessToken', { refreshToken }); + + // 获取新的token + const { accessToken, refreshToken: newRefreshToken } = response.data.data; + + // 更新本地存储 + localStorage.setItem('accessToken', accessToken); + if (newRefreshToken) { + localStorage.setItem('refreshToken', newRefreshToken); + } + + // 更新当前请求的Authorization头 + originalRequest.headers.Authorization = accessToken; + + // 执行队列中的所有请求 + requests.forEach(cb => cb(accessToken)); + requests = []; + + // 重新发送之前失败的请求 + return instance(originalRequest); + } catch (refreshError) { + // 刷新token失败,清除token并跳转到登录页 + localStorage.removeItem('accessToken'); + localStorage.removeItem('refreshToken'); + window.location.href = '/login'; + return Promise.reject(refreshError); + } finally { + isRefreshing = false; + } + } else { + // 如果已经在刷新中,将请求加入队列 + return new Promise(resolve => { + requests.push(token => { + originalRequest.headers.Authorization = token; + resolve(instance(originalRequest)); + }); + }); + } + } + + return Promise.reject(error); + } + ); } \ No newline at end of file diff --git a/src/api/modules/order.js b/src/api/modules/order.js new file mode 100644 index 0000000..266bac9 --- /dev/null +++ b/src/api/modules/order.js @@ -0,0 +1,44 @@ +import request from '@/utils/axios' + +// 获取订单列表 +export function getOrderList(params) { + return request({ + url: '/wechatOrder/list', + method: 'get', + params + }) +} + +// 获取订单详情 +export function getOrderDetail(id) { + return request({ + url: `/wechatOrder/${id}`, + method: 'get' + }) +} + +// 新增订单 +export function addOrder(data) { + return request({ + url: '/wechatOrder', + method: 'post', + data + }) +} + +// 更新订单 +export function updateOrder(data) { + return request({ + url: '/wechatOrder', + method: 'put', + data + }) +} + +// 删除订单 +export function deleteOrder(id) { + return request({ + url: `/wechatOrder/${id}`, + method: 'delete' + }) +} \ No newline at end of file diff --git a/src/api/modules/shop.d.ts b/src/api/modules/shop.d.ts new file mode 100644 index 0000000..acae99e --- /dev/null +++ b/src/api/modules/shop.d.ts @@ -0,0 +1,77 @@ +// Shop API类型定义 +export interface ShopParams { + pageNum?: number; + pageSize?: number; + shopType?: string; + shopGroup?: string; + shopName?: string; + shopAliasName?: string; + shopAuthorize?: string; + status?: string; +} + +export interface ShopData { + id: number; + mallId?: string; + shopNike?: string; + shopType: string; + shopGroup?: string; + shopName: string; + shopAliasName: string; + shopAuthorize: string; + status: string; + account?: string; + password?: string; + isSynOrder: number; + addTime?: string; + expirationTime?: string; + createdBy?: number; + createdTime?: number; + updatedBy?: number; + updatedTime?: number; +} + +export interface ApiResponse { + code: number; + message: string; + data: T; +} + +export interface PageResponse { + endRow: number; + hasNextPage: boolean; + hasPreviousPage: boolean; + isFirstPage: boolean; + isLastPage: boolean; + list: T[]; + navigateFirstPage: number; + navigateLastPage: number; + navigatePages: number; + navigatepageNums: number[]; + nextPage: number; + pageNum: number; + pageSize: number; + pages: number; + prePage: number; + size: number; + startRow: number; + total: number; +} + +declare function getShopList(params: ShopParams): Promise<{data: ApiResponse>}>; +declare function getShopDetail(id: number): Promise<{data: ApiResponse}>; +declare function addShop(data: ShopData): Promise<{data: ApiResponse}>; +declare function updateShop(data: ShopData): Promise<{data: ApiResponse}>; +declare function deleteShop(id: number): Promise<{data: ApiResponse}>; +declare function batchDeleteShop(ids: string): Promise<{data: ApiResponse}>; +declare function updateSyncOrderStatus(id: number, isSynOrder: number): Promise<{data: ApiResponse}>; + +export { + getShopList, + getShopDetail, + addShop, + updateShop, + deleteShop, + batchDeleteShop, + updateSyncOrderStatus +}; \ No newline at end of file diff --git a/src/api/modules/shop.js b/src/api/modules/shop.js new file mode 100644 index 0000000..edd3746 --- /dev/null +++ b/src/api/modules/shop.js @@ -0,0 +1,66 @@ +import instance from '../../utils/axios.js' + +// 获取店铺列表 +export const getShopList = async (params = {}) => { + // 构造查询参数 + const queryParams = new URLSearchParams(); + + // 添加分页参数 + if (params.pageNum) queryParams.append('pageNum', params.pageNum); + if (params.pageSize) queryParams.append('pageSize', params.pageSize); + + // 添加搜索参数 + if (params.shopType) queryParams.append('shopType', params.shopType); + if (params.shopGroup) queryParams.append('shopGroup', params.shopGroup); + if (params.shopName) queryParams.append('shopName', params.shopName); + if (params.shopAliasName) queryParams.append('shopAliasName', params.shopAliasName); + if (params.shopAuthorize) queryParams.append('shopAuthorize', params.shopAuthorize); + if (params.status) queryParams.append('status', params.status); + + const url = `/shop/list?${queryParams.toString()}`; + return instance.get(url); +}; + +// 获取店铺详情 +export const getShopDetail = (id) => { + return instance.get(`/shop/${id}`); +}; + +// 新增店铺 +export const addShop = (data) => { + return instance.post('/shop', data); +}; + +// 修改店铺 +export const updateShop = (data) => { + return instance.put('/shop', data); +}; + +// 删除店铺 +export const deleteShop = (id) => { + return instance.delete(`/shop/${id}`); +}; + +// 批量删除店铺 +export const batchDeleteShop = (ids) => { + return instance.delete(`/shop/batch/${ids}`); +}; + +// 更新同步订单状态 +export const updateSyncOrderStatus = (id, isSynOrder) => { + return instance.put('/shop/syncOrder', { + id, + isSynOrder + }); +}; + +// 为了向后兼容,也导出整个对象 +export const shopApi = { + getShopList, + getShopDetail, + addShop, + updateShop, + deleteShop, + batchDeleteShop, + updateSyncOrderStatus +}; diff --git a/src/layout/Sidebar.vue b/src/layout/Sidebar.vue index 1d4944b..f606da5 100644 --- a/src/layout/Sidebar.vue +++ b/src/layout/Sidebar.vue @@ -24,7 +24,7 @@ diff --git a/src/router/index.js b/src/router/index.js index 1441072..4776297 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -33,6 +33,11 @@ const routes = [{ component: () => import('@/views/User/Edit.vue'), meta: { title: '新增用户' } }, + { + path: '/shop/list', + component: () => import('@/views/Shop/index.vue'), + meta: { title: '店铺列表' } + }, { path: '/tools/cards/list', component: () => import('@/views/Tools/Cards/List.vue'), @@ -62,6 +67,11 @@ const routes = [{ path: '/order/wechat/list', component: () => import('@/views/order/wechat/list.vue'), meta: { title: '订单列表' } + }, + { + path: '/websocket/demo', + component: () => import('@/views/websocket/index.vue'), + meta: { title: 'WebSocket演示' } } ] }] diff --git a/src/utils/request.js b/src/utils/request.js new file mode 100644 index 0000000..19356d5 --- /dev/null +++ b/src/utils/request.js @@ -0,0 +1,57 @@ +/** + * 外部API请求工具 + */ + +// 检查字符串是否为URL +const isValidUrl = (string) => { + try { + new URL(string); + return true; + } catch (_) { + return false; + } +}; + +// 发送GET请求到外部API +export const fetchExternalApi = async (url) => { + try { + const response = await fetch(url, { + method: 'GET', + headers: { + 'Accept': '*/*', + }, + // 禁用缓存 + cache: 'no-cache', + }); + + if (!response.ok) { + throw new Error(`HTTP error! Status: ${response.status}`); + } + + // 先尝试获取文本响应 + const text = await response.text(); + + // 如果文本是URL,直接返回 + if (isValidUrl(text)) { + console.log('直接返回URL:', text); + return { url: text.trim() }; + } + + // 尝试解析为JSON + try { + const jsonData = JSON.parse(text); + return jsonData; + } catch (jsonError) { + // 如果不是JSON,则作为文本返回 + return { text }; + } + } catch (error) { + console.error('外部API请求失败:', error); + throw error; + } +}; + +export default { + fetchExternalApi, + isValidUrl +}; \ No newline at end of file diff --git a/src/views/SettledConfig/List.vue b/src/views/SettledConfig/List.vue index f03cb9c..21e7d6c 100644 --- a/src/views/SettledConfig/List.vue +++ b/src/views/SettledConfig/List.vue @@ -592,39 +592,66 @@ - - - - + + - + + - + + + + + + @@ -639,16 +666,6 @@ {{ row.is_del === 0 ? '正常' : '已删除' }} - - - @@ -679,6 +696,11 @@ + + + + + @@ -1660,6 +1682,7 @@ const formData = reactive({ constraint_json: '', books_count_min: 0, books_count_max: 0, + member_type: 1, // 添加会员类型,默认为1(小程序) kickback_type: 0, kickback_value: '', kickback_point: 0, @@ -1887,6 +1910,7 @@ const resetFormData = () => { constraint_json: '', books_count_min: 0, books_count_max: 0, + member_type: 1, // 添加会员类型,默认为1(小程序) kickback_type: 0, kickback_value: '', kickback_point: 0, @@ -2202,6 +2226,7 @@ const handleEdit = (row: TableItem) => { id: row.id, title: row.title, settled_cost_key: row.settled_cost_key, + member_type: constraintData.member_type || 1, // 设置会员类型,默认为1(小程序) constraint_json: constraintData, books_count_min: min, books_count_max: max, @@ -2235,6 +2260,7 @@ const handleSubmit = async () => { const constraintObj = { books_count_min: formData.books_count_min, books_count_max: formData.books_count_max, + member_type: formData.member_type, // 添加会员类型 // 小程序收费 miniapp: { diff --git a/src/views/Shop/index.vue b/src/views/Shop/index.vue new file mode 100644 index 0000000..ce6b46b --- /dev/null +++ b/src/views/Shop/index.vue @@ -0,0 +1,608 @@ + + + + + \ No newline at end of file diff --git a/vite.config.mjs b/vite.config.mjs index 2898810..0b8a53d 100644 --- a/vite.config.mjs +++ b/vite.config.mjs @@ -46,7 +46,7 @@ export default defineConfig({ proxy: { '/api': { target: 'http://127.0.0.1:8089', - // target: 'http://146.56.227.42', + // target: 'http://146.56.227.42:8089', changeOrigin: true, rewrite: (path) => path.replace(/^\/api/,''), // 如需处理WebSocket