Commit eb4bc7fe authored by daniel's avatar daniel

feat(guide): 页面优化+1

parent dea22533
...@@ -174,8 +174,6 @@ ...@@ -174,8 +174,6 @@
c.city as city, c.city as city,
c.province as province, c.province as province,
c.area as area, c.area as area,
c.cmpt_status as cmptStatus,
c.audit_status as auditStatus,
c.location as location c.location as location
from gk_cmpt a left join common.neucmpt b on a.cmpt_id=b.gtCmptId from gk_cmpt a left join common.neucmpt b on a.cmpt_id=b.gtCmptId
left join raceenroll.competition c on b.gcmptid=c.cmptid left join raceenroll.competition c on b.gcmptid=c.cmptid
......
...@@ -136,7 +136,7 @@ ...@@ -136,7 +136,7 @@
</n-tabs> </n-tabs>
</div> </div>
<div class="ml-auto" v-if="!isConfigTab"> <div class="ml-auto" v-if="!isConfigTab">
<n-input v-model:value="query.markNo" placeholder="请输入参赛号" :clearable="true" @change="fetchOrders" /> <n-input v-model:value="query.markNo" placeholder="请输入参赛号" :clearable="true" @change="fetchOrders(true)" />
</div> </div>
</div> </div>
<div class="w-full gap-x-2 flex flex-row items-center mt-3"> <div class="w-full gap-x-2 flex flex-row items-center mt-3">
...@@ -144,6 +144,9 @@ ...@@ -144,6 +144,9 @@
<n-button type="error" @click="refundAll"> <n-button type="error" @click="refundAll">
一键退款 一键退款
</n-button> </n-button>
<n-button type="success" @click="exportOrder">
一键导出
</n-button>
<n-button v-if="checkedRowKeys.length" @click="batchRefund" type="error"> <n-button v-if="checkedRowKeys.length" @click="batchRefund" type="error">
批量退款{{checkedRowKeys.length>0?'(已选择'+checkedRowKeys.length+'条订单)':''}} 批量退款{{checkedRowKeys.length>0?'(已选择'+checkedRowKeys.length+'条订单)':''}}
</n-button> </n-button>
...@@ -478,6 +481,8 @@ ...@@ -478,6 +481,8 @@
const ORDER_REFUND_ALL_URL = BASE_ECC_URL + "/api-refund/v1/refund-all" const ORDER_REFUND_ALL_URL = BASE_ECC_URL + "/api-refund/v1/refund-all"
// 编辑订单 // 编辑订单
const EDIT_ORDER_URL = BASE_ECC_URL + "/api-order/v1/edit" const EDIT_ORDER_URL = BASE_ECC_URL + "/api-order/v1/edit"
// 导出名单
const EXPORT_ORDER = BASE_ECC_URL + "/api-order/v1/export-order"
// qr二维码 // qr二维码
const qrCodeUrl = window.location.origin + '/gk/wechatMini/wxQrCode' const qrCodeUrl = window.location.origin + '/gk/wechatMini/wxQrCode'
// 一键退款 // 一键退款
...@@ -760,7 +765,6 @@ ...@@ -760,7 +765,6 @@
const pagination = reactive({ const pagination = reactive({
pageSize: 20, pageSize: 20,
page: 1, page: 1,
pageCount: 0,
showSizePicker: true, showSizePicker: true,
itemCount: 0, itemCount: 0,
pageSizes: [20, 50, 200, 500], pageSizes: [20, 50, 200, 500],
...@@ -868,9 +872,10 @@ ...@@ -868,9 +872,10 @@
} }
} }
// 获取自助选号分页列表 // 获取自助选号分页列表
const fetchOrders = async () => { const fetchOrders = async (isReset = false) => {
orders.value = [] orders.value = []
loading.value = true loading.value = true
pagination.page = isReset ? 1 : pagination.page
let params = { let params = {
...query, ...query,
current: pagination.page, current: pagination.page,
...@@ -1182,6 +1187,56 @@ ...@@ -1182,6 +1187,56 @@
showRefundModal.value = true showRefundModal.value = true
refundOrderValue.refundAmount = depositForm.value.amount refundOrderValue.refundAmount = depositForm.value.amount
} }
const exportOrder = async () => {
loading.value = true
try {
const response = await fetch(EXPORT_ORDER, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
tradeStatus: query.tradeStatus,
orderType: 1,
cmptId: getCId()
})
})
if (!response.ok) {
throw new Error('导出失败')
}
// ⭐ 关键:转成 blob
const blob = await response.blob()
const status = tradeStatusOptions.value.find(item => item.value === query.tradeStatus)?.name || '_所有'
// 从 header 中取文件名(可选但推荐)
const contentDisposition = response.headers.get('content-disposition')
let filename = getCId() + status + '订单_' + new Date().getTime() + '.xlsx'
if (contentDisposition) {
const match = contentDisposition.match(/filename\*=UTF-8''(.+)/)
if (match) filename = decodeURIComponent(match[1])
}
// 触发下载
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
document.body.removeChild(a)
window.URL.revokeObjectURL(url)
message.success('导出成功')
} catch (err) {
message.error(err.message || '导出失败')
} finally {
loading.value = false
}
}
// 一键退款 // 一键退款
const refundAll = async () => { const refundAll = async () => {
const confirmFlag = confirm('是否确定发起一键退款,该操作会将已归还设备的押金全额退款?') const confirmFlag = confirm('是否确定发起一键退款,该操作会将已归还设备的押金全额退款?')
...@@ -1264,6 +1319,7 @@ ...@@ -1264,6 +1319,7 @@
getDeposit, getDeposit,
batchRefund, batchRefund,
refundAll, refundAll,
exportOrder,
// 编辑相关 // 编辑相关
editOrderValue, editOrderValue,
editOrderRules, editOrderRules,
......
...@@ -53,19 +53,13 @@ ...@@ -53,19 +53,13 @@
href="${ctxStatic}/cmpt/index.css?v=<%=System.currentTimeMillis()%>"> href="${ctxStatic}/cmpt/index.css?v=<%=System.currentTimeMillis()%>">
<link rel="stylesheet" <link rel="stylesheet"
href="${ctxStatic}/cmpt/setting.css?v=<%=System.currentTimeMillis()%>"> href="${ctxStatic}/cmpt/setting.css?v=<%=System.currentTimeMillis()%>">
<!-- Vue and Naive UI -->
<script src="${ctxStatic}/vue/vue.global.min.js"></script>
<script src="${ctxStatic}/navie-ui/index.min.js"></script>
<title>赛事引导</title> <title>赛事引导</title>
<style> <style>
.inner { [v-cloak] {
height: 180px; display: none;
background: #ce3939;
border-radius: 4px;
position: relative;
color: white;
}
.inner:hover {
background: #5d82af !important;
/* 添加!important确保优先级 */
} }
.main-body { .main-body {
...@@ -81,28 +75,40 @@ ...@@ -81,28 +75,40 @@
</style> </style>
<div class="page"> <div class="page">
<jsp:include page="../common/header.jsp"></jsp:include> <jsp:include page="../common/header.jsp"></jsp:include>
<div id="app" <div id="app" class="page-content main-body" v-cloak>
class="page-content main-body grid grid-cols-3 gap-5 p-3 box-border"> <n-config-provider :locale="locale"
<div class="inner p-3 shadow-sm" :date-locale="dateLocale">
style="background: #ce3939;" v-for="item in cmpts" <n-modal-provider>
@click="toDetail(item)" :key="item.cmptId"> <n-message-provider>
<div class="flex flex-col h-full w-full gap-y-2"> <n-space vertical style="width: 100%;">
<div class="flex-1 font-bold text-2xl"> <!-- 搜索栏 -->
{{item.cmptName}} <n-card :bordered="false">
</div> <div
<!-- <div class="flex flex-row items-center"> class="w-full flex flex-row items-center gap-x-2">
<span>状态:</span> <n-input
<span> v-model:value="query.keyword"
{{item.cmptStatus}} placeholder="请输入赛事名称关键字"
</span> :clearable="true"
</div> --> @change="fetchData(true)"
<span class="time">{{item.startTimeStr}}</span> style="width: 300px;" />
<span class="btn btn-light"> <n-button type="primary"
查看赛事 @click="fetchData(true)">搜索</n-button>
</span>
</div>
</div> </div>
</n-card>
<!-- 赛事列表 -->
<n-card :bordered="false">
<n-data-table
:columns="columns"
:data="cmpts"
:pagination="pagination"
:remote="true"
:loading="loading" />
</n-card>
</n-space>
</n-message-provider>
</n-modal-provider>
</n-config-provider>
</div> </div>
</div> </div>
...@@ -115,7 +121,7 @@ ...@@ -115,7 +121,7 @@
<script <script
src="${ctxStatic}/newdermon/vendor/jquery-validation/jquery.validate.min.js"></script> src="${ctxStatic}/newdermon/vendor/jquery-validation/jquery.validate.min.js"></script>
<script src="${ctxStatic}/jscolor/jscolor.js"></script> <script src="${ctxStatic}/jscolor/jscolor.js"></script>
<script src="${ctxStatic}/vue/vue.js"></script>
<!-- Main File--> <!-- Main File-->
<script src="${ctxStatic}/newdermon/js/front.js"></script> <script src="${ctxStatic}/newdermon/js/front.js"></script>
<script <script
...@@ -125,63 +131,333 @@ ...@@ -125,63 +131,333 @@
// 或者如果cmpt是复杂对象,可以序列化为JSON // 或者如果cmpt是复杂对象,可以序列化为JSON
const cmpt = JSON.parse('${fns:toJson(cmpt)}'); const cmpt = JSON.parse('${fns:toJson(cmpt)}');
$(".header-second-titile").text("赛事列表") $(".header-second-titile").text("赛事列表")
// // to 方法,封装try catch
new Vue({ const to = async (
el: '#app', promise,
data: { errorExt,
cmpts: [], ) => {
cmptStatusEnum: { try {
0: '未开始', const data = await promise
1: '进行中', const result = [null, data]
2: '已结束' return result
} catch (error) {
if (errorExt) {
const parsedError = Object.assign({}, error, errorExt)
return [parsedError, undefined]
}
return [error, undefined]
}
}
// 枚举class
class SuperEnum {
constructor(param) {
const keyObj = {};
const valueObj = {};
const nameObj = {};
if (!Array.isArray(param)) {
throw new TypeError('param is not an array!');
}
param.forEach((element) => {
if (!element.key || !element.name) {
return;
}
// 使用对象存储键值对,方便快速查找
keyObj[element.key] = element;
valueObj[element.value] = element;
nameObj[element.name] = element;
// 动态添加属性
this[element.key] = element;
this[element.name] = element;
if (element.key !== element.value) {
this[element.value] = element;
}
});
// 保存源数组
this.data = param;
this.keyObj = keyObj;
this.valueObj = valueObj;
this.nameObj = nameObj;
// 防止被修改
Object.freeze(this);
}
getColorByKey(key) {
const prop = this.keyOf(key);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.color;
}
getData() {
return this.data;
}
getKeyByName(name) {
const prop = this.nameOf(name);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.key;
}
getNameByKey(key) {
const prop = this.keyOf(key);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.name;
}
getNameByValue(value) {
const prop = this.valueOf(value);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.name;
}
getValueByKey(key) {
const prop = this.keyOf(key);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.value;
}
getValueByName(name) {
const prop = this.nameOf(name);
if (!prop) {
const error = new Error('No enum constant');
error.code = 'ENUM_NOT_FOUND';
throw error;
}
return prop.value;
}
keyOf(key) {
return this.keyObj[key];
}
nameOf(name) {
return this.nameObj[name];
} }
valueOf(value) {
return this.valueObj[value];
}
}
// 赛事状态枚举
const CmptStatusEnum = new SuperEnum([
{ key: 'UNSHELVE', name: '下架', color: 'default', value: 0 },
{ key: 'ENDED', name: '上架', color: 'success', value: 2 }
]);
// 赛事状态枚举
const AuditStatusEnum = new SuperEnum([
{ key: 'PENDING', name: '未通过', color: 'default', value: 0 },
// { key: 'IN_PROGRESS', name: '进行中', color: 'success', value: 1 },
{ key: 'PASS', name: '通过', color: 'success', value: 2 }
]);
const { createApp, reactive, ref, onMounted, h } = Vue;
const { NCard, NInput, NButton, NDataTable, NSpace, NConfigProvider, NModalProvider, NMessageProvider, zhCN, dateZhCN, NTag } = naive;
createApp({
components: {
NCard,
NInput,
NButton,
NDataTable,
NSpace,
NConfigProvider,
NModalProvider,
NMessageProvider,
NTag
},
setup() {
const loading = ref(false);
const cmpts = ref([]);
// 查询参数
const query = reactive({
keyword: '',
page: 1,
pageSize: 10,
partnerId: ''
});
// 表格列配置
const columns = [
{
title: '赛事名称',
key: 'cmptName',
}, },
mounted() { {
this.fetchData() title: '创建时间',
key: 'createDate',
width: 180,
align: 'center',
},
{
title: '开始时间',
key: 'startTimeStr',
width: 180,
align: 'center',
},
{
title: '上架状态',
key: 'cmptStatus',
align: 'center',
width: 100,
render: (row) => {
const status = CmptStatusEnum.valueOf(row.cmptStatus);
return h(NTag, {
type: status?.color || 'default',
bordered: false
}, () => status?.name || row.cmptStatus);
}
},
{
title: '审核状态',
key: 'auditStatus',
width: 100,
align: 'center',
render: (row) => {
const status = AuditStatusEnum.valueOf(row.auditStatus);
return h(NTag, {
type: status?.color || 'default',
bordered: false
}, () => status?.name || row.cmptStatus);
}
},
{
title: '操作',
key: 'operation',
width: 120,
fixed: 'right',
align: 'center',
render: (row) => {
return h(NButton, {
type: 'primary',
onClick: () => toDetail(row)
}, () => '查看赛事');
}
}
];
// 分页配置
const pagination = reactive({
page: 1,
pageSize: 10,
pageSizes: [10, 20, 50, 100],
showSizePicker: true,
itemCount: 0,
prefix: (info) => "共" + info.itemCount + "条数据",
onChange: (page) => {
pagination.page = page
fetchData()
}, },
methods: { onUpdatePageSize: (pageSize) => {
getQueryParams() { pagination.pageSize = pageSize
pagination.page = 1
fetchData()
}
});
// 获取URL参数
const getQueryParams = () => {
const params = {}; const params = {};
window.location.search.substring(1).split('&').forEach(pair => { window.location.search.substring(1).split('&').forEach(pair => {
const [key, value] = pair.split('='); const [key, value] = pair.split('=');
if (key) params[key] = decodeURIComponent(value || ''); if (key) params[key] = decodeURIComponent(value || '');
}); });
return params; return params;
}, };
toDetail(item) {
// 获取当前URL中的参数部分 // 跳转到赛事详情
const currentUrl = window.location.href; const toDetail = (item) => {
const params = this.getQueryParams() const params = getQueryParams();
// 构建目标URL
const targetUrl = window.location.origin + "/gk/cmpt/" + item.cmptId + "?" + new URLSearchParams({ ...params, cId: item.cmptId }); const targetUrl = window.location.origin + "/gk/cmpt/" + item.cmptId + "?" + new URLSearchParams({ ...params, cId: item.cmptId });
window.location.href = targetUrl; window.location.href = targetUrl;
};
// 获取数据
const fetchData = async (isRest = false) => {
loading.value = true;
const urlParams = getQueryParams();
query.partnerId = urlParams.partnerId || "EDA6760246C92BF6";
// 确保query.page与pagination.page保持一致
query.page = isRest ? 1 : pagination.page;
pagination.page = query.page;
query.pageSize = pagination.pageSize;
const [err, res] = await to(fetch("/gk/guide/cmpt", {
method: 'POST',
headers: {
'Content-Type': 'application/json; charset=UTF-8'
}, },
fetchData() { body: JSON.stringify(query)
const params = this.getQueryParams() }));
const url = "/gk/guide/cmpt"
$.ajax({ if (err) {
type: 'POST', console.error('请求失败:', err);
contentType: 'application/json', loading.value = false;
data: JSON.stringify({ return;
page: 1,
pageSize: 100,
partnerId: params?.partnerId
}),
url,
beforeSend: () => {
layer.load()
},
complete: () => {
layer.closeAll()
},
success: (res) => {
this.cmpts = res.content
} }
})
},
}, try {
const result = await res.json();
if (result.code === 200) {
cmpts.value = result.content || [];
pagination.itemCount = result.total;
}
} catch (jsonErr) {
console.error('解析JSON失败:', jsonErr);
} finally {
loading.value = false;
}
};
// 初始化
onMounted(() => {
fetchData();
$(".header-second-titile").text("赛事列表");
}); });
// 国际化配置
const locale = zhCN;
const dateLocale = dateZhCN;
return {
cmpts,
query,
columns,
pagination,
loading,
locale,
dateLocale,
fetchData,
toDetail
};
}
}).mount('#app');
</script> </script>
</body> </body>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment