新后台

This commit is contained in:
yuhawu 2025-07-08 18:57:05 +08:00
parent 7eb2635fd0
commit b61e5ebafe
10 changed files with 1019 additions and 81 deletions

View File

@ -1,39 +1,89 @@
//响应拦截器 //响应拦截器
import axios from 'axios'; // 引入axios用于创建新请求
export function setupResponseInterceptors(instance) { export function setupResponseInterceptors(instance) {
// 用于存储正在刷新token的Promise
let isRefreshing = false;
// 存储等待token刷新的请求队列
let requests = [];
instance.interceptors.response.use( instance.interceptors.response.use(
response => { response => {
// 解析 response const { data } = response;
const { data, config } = response; if (data.code != 200) {
if(response.data.code != 200){
// 创建可追踪的业务逻辑错误
const error = new Error(data.message || '业务逻辑错误'); const error = new Error(data.message || '业务逻辑错误');
error.name = 'BusinessError'; // 自定义错误类型标识 error.name = 'BusinessError';
error.config = config; // 保留请求配置 error.data = data;
error.data = data; // 保留响应数据 error.code = data.code;
error.code = data.code; // 业务错误码
//必须 return Promise.reject 才能触发 catch
return Promise.reject(error); return Promise.reject(error);
} }
// 处理响应数据格式 return data;
return data
}, },
error => { async error => {
// 统一错误处理 // 获取原始请求配置
if (error.response) { const originalRequest = error.config;
switch (error.response.status) {
case 401: // 判断是否401错误token过期且没有在刷新中
// 处理未授权 if (error.response?.status === 401 && !originalRequest._retry) {
break // 标记该请求已尝试过重试
case 403: originalRequest._retry = true;
// 处理禁止访问
break // 如果当前没有在刷新token
default: if (!isRefreshing) {
console.error('响应错误:', error) 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)
return Promise.reject(error);
} }
) );
} }

44
src/api/modules/order.js Normal file
View File

@ -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'
})
}

77
src/api/modules/shop.d.ts vendored Normal file
View File

@ -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<T> {
code: number;
message: string;
data: T;
}
export interface PageResponse<T> {
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<PageResponse<ShopData>>}>;
declare function getShopDetail(id: number): Promise<{data: ApiResponse<ShopData>}>;
declare function addShop(data: ShopData): Promise<{data: ApiResponse<any>}>;
declare function updateShop(data: ShopData): Promise<{data: ApiResponse<any>}>;
declare function deleteShop(id: number): Promise<{data: ApiResponse<any>}>;
declare function batchDeleteShop(ids: string): Promise<{data: ApiResponse<any>}>;
declare function updateSyncOrderStatus(id: number, isSynOrder: number): Promise<{data: ApiResponse<any>}>;
export {
getShopList,
getShopDetail,
addShop,
updateShop,
deleteShop,
batchDeleteShop,
updateSyncOrderStatus
};

66
src/api/modules/shop.js Normal file
View File

@ -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
};

View File

@ -24,7 +24,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { shallowRef } from 'vue' import { shallowRef } from 'vue'
import {Document as DocIcon,Setting,User,Message,ShoppingCart } from '@element-plus/icons-vue' import {Document as DocIcon,Setting,User,Message,ShoppingCart,Shop,Connection } from '@element-plus/icons-vue'
const menuData = shallowRef([{ const menuData = shallowRef([{
title: '系统管理', title: '系统管理',
@ -80,6 +80,19 @@
} }
] ]
}, },
{
title: '店铺管理',
path: '/shop',
icon: Shop,
children: [{
title: '店铺列表',
path: '/shop/list',
children: [{
title: '店铺列表',
path: '/shop/list'
}]
}]
},
{ {
title: '工具管理', title: '工具管理',
path: '/tools', path: '/tools',
@ -128,20 +141,7 @@
path: '/useModule/vas/list' path: '/useModule/vas/list'
}] }]
}] }]
}, }
{
title: '订单管理',
path: '/order',
icon: ShoppingCart,
children: [{
title: '微信订单',
path: '/order/wechat',
children: [{
title: '订单列表',
path: '/order/wechat/list'
}]
}]
},
// ... // ...
]) ])
</script> </script>

View File

@ -33,6 +33,11 @@ const routes = [{
component: () => import('@/views/User/Edit.vue'), component: () => import('@/views/User/Edit.vue'),
meta: { title: '新增用户' } meta: { title: '新增用户' }
}, },
{
path: '/shop/list',
component: () => import('@/views/Shop/index.vue'),
meta: { title: '店铺列表' }
},
{ {
path: '/tools/cards/list', path: '/tools/cards/list',
component: () => import('@/views/Tools/Cards/List.vue'), component: () => import('@/views/Tools/Cards/List.vue'),
@ -62,6 +67,11 @@ const routes = [{
path: '/order/wechat/list', path: '/order/wechat/list',
component: () => import('@/views/order/wechat/list.vue'), component: () => import('@/views/order/wechat/list.vue'),
meta: { title: '订单列表' } meta: { title: '订单列表' }
},
{
path: '/websocket/demo',
component: () => import('@/views/websocket/index.vue'),
meta: { title: 'WebSocket演示' }
} }
] ]
}] }]

57
src/utils/request.js Normal file
View File

@ -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
};

View File

@ -592,39 +592,66 @@
<el-table-column type="selection" align="center" width="55" /> <el-table-column type="selection" align="center" width="55" />
<el-table-column prop="title" align="center" label="标题" width="120" /> <el-table-column prop="title" align="center" label="标题" width="120" />
<el-table-column prop="settled_cost_key" align="center" label="入驻标识" width="180" /> <el-table-column prop="settled_cost_key" align="center" label="入驻标识" width="180" />
<el-table-column prop="kickback_type" align="center" label="佣金类型" width="100"> <!-- 小程序收费佣金 -->
<template #default="{ row }">{{ formatKickbackType(row.kickback_type) }}</template> <el-table-column align="center" label="小程序佣金" width="180">
</el-table-column>
<el-table-column align="center" label="佣金值" width="180">
<template #default="{ row }"> <template #default="{ row }">
<div v-if="parseConstraint(row.constraint_json) && parseConstraint(row.constraint_json).miniapp">
<div> <div>
<div v-if="[1, 3].includes(row.kickback_type)"> 类型: {{ formatKickbackType(parseConstraint(row.constraint_json).miniapp.kickback_type || 0) }}
提点: {{ parseKickbackValue(row.kickback_value, 'point') }}%
</div> </div>
<div v-if="[2, 3].includes(row.kickback_type)"> <div v-if="[1, 3].includes(parseConstraint(row.constraint_json).miniapp.kickback_type || 0)">
固定费用: {{ parseKickbackValue(row.kickback_value, 'fixed') }} 提点: {{ parseKickbackValueJson(parseConstraint(row.constraint_json).miniapp.kickback_value || '{}', 'point') }}%
</div> </div>
<div v-if="![1, 2, 3].includes(row.kickback_type)"> <div v-if="[2, 3].includes(parseConstraint(row.constraint_json).miniapp.kickback_type || 0)">
{{ row.kickback_value }} 固费: {{ parseKickbackValueJson(parseConstraint(row.constraint_json).miniapp.kickback_value || '{}', 'fixed') }}
</div> </div>
</div> </div>
<span v-else>-</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="resource_cost_type" align="center" label="资源费类型" width="120">
<el-table-column label="操作" align="center" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
{{ formatResourceCostType(row.resource_cost_type) }} <el-button-group>
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" size="small" @click="handleDelete(row)" :disabled="row.is_del === 1">
删除
</el-button>
</el-button-group>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column align="center" label="资源费值/服务费率" width="180"> <!-- 孔夫子店铺佣金 -->
<el-table-column align="center" label="孔夫子佣金" width="180">
<template #default="{ row }"> <template #default="{ row }">
<div v-if="parseConstraint(row.constraint_json) && parseConstraint(row.constraint_json).kongfz">
<div> <div>
<div v-if="[1, 3].includes(row.resource_cost_type)"> 类型: {{ formatKickbackType(parseConstraint(row.constraint_json).kongfz.kickback_type || 0) }}
服务费率: {{ row.service_rate }}%
</div> </div>
<div v-if="[2, 3].includes(row.resource_cost_type)"> <div v-if="[1, 3].includes(parseConstraint(row.constraint_json).kongfz.kickback_type || 0)">
资源费值: {{ row.resource_cost_value }} 提点: {{ parseKickbackValueJson(parseConstraint(row.constraint_json).kongfz.kickback_value || '{}', 'point') }}%
</div>
<div v-if="[2, 3].includes(parseConstraint(row.constraint_json).kongfz.kickback_type || 0)">
固费: {{ parseKickbackValueJson(parseConstraint(row.constraint_json).kongfz.kickback_value || '{}', 'fixed') }}
</div> </div>
</div> </div>
<span v-else>-</span>
</template>
</el-table-column>
<!-- 资源费分级佣金 -->
<el-table-column align="center" label="资源费佣金" width="180">
<template #default="{ row }">
<div v-if="parseConstraint(row.constraint_json) && parseConstraint(row.constraint_json).resource_tier">
<div>
类型: {{ formatKickbackType(parseConstraint(row.constraint_json).resource_tier.kickback_type || 0) }}
</div>
<div v-if="[1, 3].includes(parseConstraint(row.constraint_json).resource_tier.kickback_type || 0)">
提点: {{ parseKickbackValueJson(parseConstraint(row.constraint_json).resource_tier.kickback_value || '{}', 'point') }}%
</div>
<div v-if="[2, 3].includes(parseConstraint(row.constraint_json).resource_tier.kickback_type || 0)">
固费: {{ parseKickbackValueJson(parseConstraint(row.constraint_json).resource_tier.kickback_value || '{}', 'fixed') }}
</div>
</div>
<span v-else>-</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="price" align="center" label="价格(元)" width="120" /> <el-table-column prop="price" align="center" label="价格(元)" width="120" />
@ -639,16 +666,6 @@
{{ row.is_del === 0 ? '正常' : '已删除' }} {{ row.is_del === 0 ? '正常' : '已删除' }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" align="center" fixed="right">
<template #default="{ row }">
<el-button-group>
<el-button type="primary" size="small" @click="handleEdit(row)">编辑</el-button>
<el-button type="danger" size="small" @click="handleDelete(row)" :disabled="row.is_del === 1">
删除
</el-button>
</el-button-group>
</template>
</el-table-column>
</el-table> </el-table>
<!-- 分页 --> <!-- 分页 -->
@ -679,6 +696,11 @@
<el-form-item label="入驻标识" prop="settled_cost_key"> <el-form-item label="入驻标识" prop="settled_cost_key">
<el-input v-model="formData.settled_cost_key" placeholder="请输入入驻标识" :disabled="dialogType === 'edit'" /> <el-input v-model="formData.settled_cost_key" placeholder="请输入入驻标识" :disabled="dialogType === 'edit'" />
</el-form-item> </el-form-item>
<el-form-item label="会员类型" prop="member_type">
<el-select v-model="formData.member_type" placeholder="请选择会员类型" clearable>
<el-option label="小程序" :value="1" />
</el-select>
</el-form-item>
<el-form-item label="最小本数" prop="books_count_min"> <el-form-item label="最小本数" prop="books_count_min">
<el-input-number v-model="formData.books_count_min" :min="0" :precision="0" placeholder="请输入最小本数" /> <el-input-number v-model="formData.books_count_min" :min="0" :precision="0" placeholder="请输入最小本数" />
</el-form-item> </el-form-item>
@ -1660,6 +1682,7 @@ const formData = reactive({
constraint_json: '', constraint_json: '',
books_count_min: 0, books_count_min: 0,
books_count_max: 0, books_count_max: 0,
member_type: 1, // 1
kickback_type: 0, kickback_type: 0,
kickback_value: '', kickback_value: '',
kickback_point: 0, kickback_point: 0,
@ -1887,6 +1910,7 @@ const resetFormData = () => {
constraint_json: '', constraint_json: '',
books_count_min: 0, books_count_min: 0,
books_count_max: 0, books_count_max: 0,
member_type: 1, // 1
kickback_type: 0, kickback_type: 0,
kickback_value: '', kickback_value: '',
kickback_point: 0, kickback_point: 0,
@ -2202,6 +2226,7 @@ const handleEdit = (row: TableItem) => {
id: row.id, id: row.id,
title: row.title, title: row.title,
settled_cost_key: row.settled_cost_key, settled_cost_key: row.settled_cost_key,
member_type: constraintData.member_type || 1, // 1
constraint_json: constraintData, constraint_json: constraintData,
books_count_min: min, books_count_min: min,
books_count_max: max, books_count_max: max,
@ -2235,6 +2260,7 @@ const handleSubmit = async () => {
const constraintObj = { const constraintObj = {
books_count_min: formData.books_count_min, books_count_min: formData.books_count_min,
books_count_max: formData.books_count_max, books_count_max: formData.books_count_max,
member_type: formData.member_type, //
// //
miniapp: { miniapp: {

608
src/views/Shop/index.vue Normal file
View File

@ -0,0 +1,608 @@
<template>
<div class="list-container">
<div class="search-area">
<el-form :model="queryParams" inline>
<el-form-item label="店铺类型">
<el-select v-model="queryParams.shopType" placeholder="请选择店铺类型" clearable>
<el-option label="拼多多" value="1" />
</el-select>
</el-form-item>
<el-form-item label="分组">
<el-input v-model="queryParams.shopGroup" placeholder="请输入分组" />
</el-form-item>
<el-form-item label="店铺名称">
<el-input v-model="queryParams.shopName" placeholder="请输入店铺名称" />
</el-form-item>
<el-form-item label="三方名称">
<el-input v-model="queryParams.shopAliasName" placeholder="请输入三方名称" />
</el-form-item>
<el-form-item label="是否授权">
<el-select v-model="queryParams.shopAuthorize" placeholder="请选择是否授权" clearable>
<el-option label="已授权" value="1" />
<el-option label="未授权" value="0" />
<el-option label="已过期" value="2" />
</el-select>
</el-form-item>
<el-form-item label="店铺状态">
<el-select v-model="queryParams.status" placeholder="请选择店铺状态" clearable>
<el-option label="正常" value="0" />
<el-option label="停用" value="1" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleSearch">搜索</el-button>
<el-button icon="Refresh" @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<div class="action-bar">
<div class="action-left">
<el-button type="primary" @click="handleAdd">新增</el-button>
<el-button type="primary" @click="handleEdit">编辑</el-button>
<el-button type="danger" @click="handleDelete">删除</el-button>
</div>
<div class="action-right">
<RefreshButton @refresh="getShopListData" />
</div>
</div>
<el-table
v-loading="loading"
:data="shopList"
@selection-change="handleSelectionChange"
border
stripe
style="width: 100%"
row-key="id"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="店铺编码" prop="id" align="center" />
<el-table-column label="店铺类型" align="center">
<template #default="scope">
{{ scope.row.shopType === '1' ? '拼多多' : '-' }}
</template>
</el-table-column>
<el-table-column label="分组" prop="shopGroup" align="center" />
<el-table-column label="店铺名称" prop="shopName" align="center" />
<el-table-column label="三方名称" prop="shopAliasName" align="center" />
<el-table-column label="是否授权" align="center">
<template #default="scope">
<el-tag :type="getAuthorizeTagType(scope.row.shopAuthorize)" v-if="scope.row.shopAuthorize === '1'">
{{ getAuthorizeText(scope.row.shopAuthorize) }}
</el-tag>
<el-button
v-else
size="small"
type="primary"
@click="handleAuthorize(scope.row)"
>
授权
</el-button>
</template>
</el-table-column>
<el-table-column label="到期时间" align="center">
<template #default="scope">
{{ scope.row.expirationTime }}
</template>
</el-table-column>
<el-table-column label="添加时间" prop="addTime" align="center" />
<el-table-column label="店铺状态" align="center">
<template #default="scope">
<el-tag :type="scope.row.status === '0' ? 'success' : 'danger'">
{{ scope.row.status === '0' ? '正常' : '停用' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="同步订单" align="center">
<template #default="scope">
<el-switch
v-model="scope.row.isSynOrder"
:active-value="1"
:inactive-value="0"
class="ml-2"
inline-prompt
active-text="开"
inactive-text="关"
@change="handleSyncOrderChange(scope.row)"
/>
</template>
</el-table-column>
<el-table-column label="设置" align="center" width="60">
<template #default="scope">
<el-button type="primary" icon="Setting" circle plain size="small" @click="handleSetting(scope.row)" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="220" fixed="right">
<template #default="scope">
<el-button size="small" type="primary" @click="handleEdit(scope.row)">编辑</el-button>
<el-button size="small" type="danger" @click="handleDelete(scope.row)">删除</el-button>
<el-button
v-if="scope.row.status === '0'"
size="small"
type="warning"
@click="handleChangeStatus(scope.row, '1')"
>停用</el-button>
<el-button
v-if="scope.row.status === '1'"
size="small"
type="success"
@click="handleChangeStatus(scope.row, '0')"
>启用</el-button>
</template>
</el-table-column>
</el-table>
<div class="pagination-container">
<el-pagination
v-model:current-page="queryParams.pageNum"
v-model:page-size="queryParams.pageSize"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
:total="total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
<!-- 添加/编辑对话框 -->
<el-dialog
:title="dialog.title"
v-model="dialog.visible"
width="600px"
:close-on-click-modal="false"
append-to-body
>
<el-form :model="form" :rules="rules" ref="formRef" label-width="100px">
<el-form-item label="店铺类型" prop="shopType">
<el-select v-model="form.shopType" placeholder="请选择店铺类型" style="width: 100%">
<el-option label="拼多多" value="1" />
</el-select>
</el-form-item>
<el-form-item label="分组" prop="shopGroup">
<el-input v-model="form.shopGroup" placeholder="请输入分组" />
</el-form-item>
<el-form-item label="店铺名称" prop="shopName">
<el-input v-model="form.shopName" placeholder="请输入店铺名称" />
</el-form-item>
<el-form-item label="三方名称" prop="shopAliasName">
<el-input v-model="form.shopAliasName" placeholder="请输入三方名称" />
</el-form-item>
<el-form-item label="店铺ID" prop="mallId">
<el-input v-model="form.mallId" placeholder="请输入三方店铺ID" />
</el-form-item>
<el-form-item label="万里牛ID" prop="shopNike">
<el-input v-model="form.shopNike" placeholder="请输入万里牛系统ID" />
</el-form-item>
<el-form-item label="第三方账号" prop="account">
<el-input v-model="form.account" placeholder="请输入第三方平台账号" />
</el-form-item>
<el-form-item label="第三方密码" prop="password">
<el-input v-model="form.password" type="password" placeholder="请输入第三方平台密码" show-password />
</el-form-item>
<el-form-item label="同步订单" prop="isSynOrder">
<el-switch
v-model="form.isSynOrder"
:active-value="1"
:inactive-value="0"
inline-prompt
active-text="开"
inactive-text="关"
/>
</el-form-item>
<el-form-item label="店铺状态" prop="status">
<el-radio-group v-model="form.status">
<el-radio label="0">正常</el-radio>
<el-radio label="1">停用</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="dialog.visible = false">取消</el-button>
<el-button type="primary" @click="submitForm">确定</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, onMounted } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
// import { shopApi } from '@/api'
import {
getShopList,
getShopDetail,
addShop,
updateShop,
deleteShop,
batchDeleteShop,
updateSyncOrderStatus
} from '@/api/modules/shop'
import { fetchExternalApi } from '@/utils/request'
import RefreshButton from '@/components/RefreshButton.vue'
//
const queryParams = reactive({
shopType: '',
shopGroup: '',
shopName: '',
shopAliasName: '',
shopAuthorize: '',
status: '',
pageNum: 1,
pageSize: 20
})
//
const loading = ref(false)
const shopList = ref([])
const total = ref(0)
const selectedRows = ref<any[]>([])
//
const dialog = reactive({
visible: false,
title: ''
})
//
const formRef = ref()
const form = reactive({
id: undefined,
mallId: '',
shopNike: '',
shopType: '1',
shopGroup: '',
shopName: '',
shopAliasName: '',
shopAuthorize: '0',
status: '0',
account: '',
password: '',
isSynOrder: 0
})
//
const rules = {
shopType: [{ required: true, message: '请选择店铺类型', trigger: 'change' }],
shopName: [{ required: true, message: '请输入店铺名称', trigger: 'blur' }],
shopAliasName: [{ required: true, message: '请输入三方名称', trigger: 'blur' }]
}
//
onMounted(() => {
getShopListData()
})
//
const getAuthorizeTagType = (authorize: string) => {
switch (authorize) {
case '1': return 'success'
case '0': return 'info'
case '2': return 'warning'
default: return 'info'
}
}
//
const getAuthorizeText = (authorize: string) => {
switch (authorize) {
case '1': return '已授权'
case '0': return '未授权'
case '2': return '已过期'
default: return '未知'
}
}
//
const getShopListData = async () => {
loading.value = true
try {
const res = await getShopList(queryParams)
console.log("res", res)
if (res.code === 200) {
shopList.value = res.data.list
total.value = res.data.total
} else {
ElMessage.error(res.message || '获取数据失败')
}
} catch (error) {
ElMessage.error('获取店铺列表失败')
} finally {
loading.value = false
}
}
//
const handleSearch = () => {
queryParams.pageNum = 1
getShopListData()
}
//
const handleReset = () => {
queryParams.shopType = ''
queryParams.shopGroup = ''
queryParams.shopName = ''
queryParams.shopAliasName = ''
queryParams.shopAuthorize = ''
queryParams.status = ''
queryParams.pageNum = 1
getShopListData()
}
//
const handleSizeChange = (val: number) => {
queryParams.pageSize = val
getShopListData()
}
const handleCurrentChange = (val: number) => {
queryParams.pageNum = val
getShopListData()
}
//
const handleSelectionChange = (selection) => {
selectedRows.value = selection
}
//
const handleAdd = () => {
dialog.title = '添加店铺'
dialog.visible = true
form.id = undefined
form.mallId = ''
form.shopNike = ''
form.shopType = '1'
form.shopGroup = ''
form.shopName = ''
form.shopAliasName = ''
form.shopAuthorize = '0'
form.status = '0'
form.account = ''
form.password = ''
form.isSynOrder = 0
}
//
const handleEdit = (row) => {
dialog.title = '编辑店铺'
dialog.visible = true
if (row) {
//
const shopId = row.id
if (shopId) {
getShopDetail(shopId).then(res => {
if (res.data.code === 200) {
Object.assign(form, res.data.data)
}
})
}
} else {
//
if (selectedRows.value.length !== 1) {
ElMessage.warning('请选择一条记录进行修改')
dialog.visible = false
return
}
const shopId = selectedRows.value[0].id
if (shopId) {
getShopDetail(shopId).then(res => {
if (res.data.code === 200) {
Object.assign(form, res.data.data)
}
})
}
}
}
//
const handleDelete = (row) => {
if (row) {
//
ElMessageBox.confirm('确认删除该店铺记录吗?', '警告', {
type: 'warning'
}).then(() => {
if (row.id) {
deleteShop(row.id).then(res => {
if (res.data.code === 200) {
ElMessage.success('删除成功')
getShopListData()
}
})
}
}).catch(() => {})
} else {
//
if (selectedRows.value.length === 0) {
ElMessage.warning('请至少选择一条记录')
return
}
ElMessageBox.confirm(`确认删除选中的${selectedRows.value.length}条店铺记录吗?`, '警告', {
type: 'warning'
}).then(() => {
const ids = selectedRows.value.map(item => item.id).filter(id => id !== undefined)
if (ids.length > 0) {
batchDeleteShop(ids.join(',')).then(res => {
if (res.data.code === 200) {
ElMessage.success('批量删除成功')
getShopListData()
}
})
}
}).catch(() => {})
}
}
//
const handleSetting = (row) => {
ElMessage.info(`设置店铺: ${row.shopName}`)
//
}
//
const handleSyncOrderChange = (row) => {
const status = row.isSynOrder === 1 ? '开启' : '关闭'
if (row.id) {
updateSyncOrderStatus(row.id, row.isSynOrder).then(res => {
if (res.data.code === 200) {
ElMessage.success(`${status}同步订单成功`)
} else {
ElMessage.error(`${status}同步订单失败`)
//
row.isSynOrder = row.isSynOrder === 1 ? 0 : 1
}
}).catch(() => {
//
row.isSynOrder = row.isSynOrder === 1 ? 0 : 1
ElMessage.error(`${status}同步订单失败`)
})
}
}
//
const handleAuthorize = async (row) => {
try {
//
ElMessage.info('正在获取授权链接,请稍候...');
// API
const apiUrl = `https://api.buzhiyushu.cn/huidiao/pdd/toPddGetCode?id=${row.id}&type=4`;
const result = await fetchExternalApi(apiUrl);
//
let authUrl = null;
if (result) {
if (result.url) {
// url
authUrl = result.url;
} else if (typeof result === 'string' && result.includes('http')) {
// URL
authUrl = result;
}
}
if (authUrl) {
console.log('授权URL:', authUrl);
//
const newTab = window.open(authUrl, '_blank');
//
if (!newTab || newTab.closed || typeof newTab.closed === 'undefined') {
window.location.href = authUrl;
}
} else {
console.error('无法解析授权链接:', result);
ElMessage.error('获取授权链接失败');
}
} catch (error) {
console.error('授权请求失败:', error);
ElMessage.error('授权请求失败');
}
}
//
const handleChangeStatus = async (row, newStatus) => {
const statusText = newStatus === '0' ? '启用' : '停用';
ElMessageBox.confirm(`确认要${statusText}该店铺吗?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}).then(async () => {
try {
// API
const updatedData = { ...row, status: newStatus };
await updateShop(updatedData);
ElMessage.success(`${statusText}成功`);
getShopListData(); //
} catch (error) {
console.error(`${statusText}失败:`, error);
ElMessage.error(`${statusText}失败`);
}
}).catch(() => {});
}
//
const submitForm = () => {
formRef.value.validate((valid) => {
if (valid) {
if (form.id) {
//
updateShop(form).then(res => {
if (res.data.code === 200) {
ElMessage.success('修改成功')
dialog.visible = false
getShopListData()
}
})
} else {
//
addShop(form).then(res => {
if (res.data.code === 200) {
ElMessage.success('新增成功')
dialog.visible = false
getShopListData()
}
})
}
}
})
}
</script>
<style scoped>
.list-container {
padding: 20px;
}
.search-area {
margin-bottom: 20px;
padding: 18px;
background-color: #fff;
border-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
text-align: left;
}
.search-area :deep(.el-form-item) {
margin-right: 18px;
margin-bottom: 10px;
}
.action-bar {
margin-bottom: 20px;
display: flex;
justify-content: space-between;
}
.action-left {
display: flex;
gap: 10px;
}
.action-right {
display: flex;
align-items: center;
}
.pagination-container {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.dialog-footer {
display: flex;
justify-content: flex-end;
gap: 10px;
}
</style>

View File

@ -46,7 +46,7 @@ export default defineConfig({
proxy: { proxy: {
'/api': { '/api': {
target: 'http://127.0.0.1:8089', target: 'http://127.0.0.1:8089',
// target: 'http://146.56.227.42', // target: 'http://146.56.227.42:8089',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/,''), rewrite: (path) => path.replace(/^\/api/,''),
// 如需处理WebSocket // 如需处理WebSocket