Files
integral-shop/backend-adminend/src/views/integral-external/order/index.vue
scott ee0886b800 feat: 新增积分外部页面(免认证三页 + 配套基础设施)
前端:
- 新增 EmptyLayout 空壳布局(无侧边栏/导航)
- 新增 requestNoAuth Axios 实例(不注入 token)
- 新增 integralExternal 路由模块(/integral-external/*)
- permission.js 加入 whiteListPrefixes 前缀白名单跳过登录
- 新增 phoneDesensitize 手机号脱敏过滤器
- 新增三个免认证页面:
  · 积分订单页(/integral-external/order)
  · 用户积分页(/integral-external/user,手机号脱敏)
  · 用户积分明细子页(/integral-external/user/integral-detail)

后端:
- 新增 ExternalIntegralController(无 @PreAuthorize)
  · GET  /api/external/integral/order/list
  · GET  /api/external/integral/user/list
  · POST /api/external/integral/log/list
- WebSecurityConfig 加入 /api/external/integral/** permitAll

文档与工具:
- 新增 coding plan、schedule、测试报告
- 新增 start-backend.sh / start-frontend.sh 本地启动脚本
- 新增 .mvn/wrapper/maven-wrapper.properties

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 10:41:30 +08:00

255 lines
8.2 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="divBox relative">
<el-card class="box-card">
<div class="clearfix">
<div class="container">
<el-form size="small" label-width="100px">
<el-form-item label="订单类型:">
<el-radio-group v-model="tableFrom.type" type="button" class="mr20" size="small" @change="seachList">
<el-radio-button v-for="(item, i) in typeOptions" :key="i" :label="item.value">
{{ item.label }}
</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="订单状态:">
<el-radio-group v-model="tableFrom.status" type="button" @change="seachList">
<el-radio-button label="all">全部</el-radio-button>
<el-radio-button label="unPaid">未支付</el-radio-button>
<el-radio-button label="notShipped">未发货</el-radio-button>
<el-radio-button label="spike">待收货</el-radio-button>
<el-radio-button label="bargain">待评价</el-radio-button>
<el-radio-button label="complete">交易完成</el-radio-button>
<el-radio-button label="refunding">退款中</el-radio-button>
<el-radio-button label="refunded">已退款</el-radio-button>
</el-radio-group>
</el-form-item>
<el-form-item label="时间选择:" class="width100">
<el-radio-group
v-model="tableFrom.dateLimit"
type="button"
class="mr20"
size="small"
@change="selectChange(tableFrom.dateLimit)"
>
<el-radio-button v-for="(item, i) in fromList.fromTxt" :key="i" :label="item.val">
{{ item.text }}
</el-radio-button>
</el-radio-group>
<el-date-picker
v-model="timeVal"
value-format="yyyy-MM-dd"
format="yyyy-MM-dd"
size="small"
type="daterange"
placement="bottom-end"
placeholder="自定义时间"
style="width: 220px"
@change="onchangeTime"
/>
</el-form-item>
<el-form-item label="订单号:" class="width100">
<el-input
v-model="tableFrom.orderNo"
placeholder="请输入订单号"
class="selWidth"
size="small"
clearable
>
<el-button slot="append" icon="el-icon-search" size="small" @click="seachList" />
</el-input>
</el-form-item>
</el-form>
</div>
</div>
</el-card>
<el-card class="box-card mt10">
<el-table
v-loading="listLoading"
:data="tableData.data"
size="mini"
class="table"
highlight-current-row
:header-cell-style="{ fontWeight: 'bold' }"
>
<el-table-column label="订单号" min-width="210">
<template slot-scope="scope">
<span style="display: block">{{ scope.row.orderId }}</span>
<span v-if="scope.row.isDel" style="color: #ed4014; display: block">用户已删除</span>
</template>
</el-table-column>
<el-table-column prop="orderType" label="订单类型" min-width="110" />
<el-table-column prop="realName" label="收货人" min-width="100" />
<el-table-column label="商品信息" min-width="280">
<template slot-scope="scope">
<div v-if="scope.row.productList && scope.row.productList.length">
<div
v-for="(val, i) in scope.row.productList"
:key="i"
class="tabBox acea-row row-middle"
style="flex-wrap: inherit; margin-bottom: 4px"
>
<div class="demo-image__preview mr10" style="width: 40px; height: 40px; flex-shrink: 0">
<el-image :src="val.info.image" :preview-src-list="[val.info.image]" style="width: 40px; height: 40px" />
</div>
<div class="text_overflow">
<span class="tabBox_tit mr10">{{ val.info.productName }}</span>
<span class="tabBox_pice">{{ val.info.price }} × {{ val.info.payNum }}</span>
</div>
</div>
</div>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column prop="payPrice" label="实际支付" min-width="90">
<template slot-scope="scope">
<span>{{ scope.row.payPrice }}</span>
</template>
</el-table-column>
<el-table-column label="支付方式" min-width="90">
<template slot-scope="scope">
<span>{{ scope.row.payTypeStr || '-' }}</span>
</template>
</el-table-column>
<el-table-column label="订单状态" min-width="100">
<template slot-scope="scope">
<span :class="scope.row.refundStatus === 1 || scope.row.refundStatus === 2 ? 'refund-tag' : ''">
{{ scope.row.statusStr ? scope.row.statusStr.value : '-' }}
</span>
</template>
</el-table-column>
<el-table-column prop="createTime" label="下单时间" min-width="150" />
</el-table>
<div class="block mt20">
<el-pagination
:page-sizes="[15, 30, 45, 60]"
:page-size="tableFrom.limit"
:current-page="tableFrom.page"
layout="total, sizes, prev, pager, next, jumper"
:total="tableData.total"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</el-card>
</div>
</template>
<script>
import { getExternalOrderList } from '@/api/integralExternal';
export default {
name: 'IntegralExternalOrder',
data() {
return {
listLoading: false,
tableData: {
data: [],
total: 0,
},
tableFrom: {
type: '',
status: 'all',
dateLimit: '',
orderNo: '',
page: 1,
limit: 15,
},
timeVal: [],
fromList: {
fromTxt: [
{ text: '全部', val: '' },
{ text: '今天', val: 'today' },
{ text: '昨天', val: 'yesterday' },
{ text: '最近7天', val: 'lately7' },
{ text: '最近30天', val: 'lately30' },
{ text: '本月', val: 'month' },
{ text: '本年', val: 'year' },
],
},
typeOptions: [
{ label: '全部', value: '' },
{ label: '普通订单', value: '0' },
{ label: '秒杀订单', value: '1' },
{ label: '砍价订单', value: '2' },
{ label: '拼团订单', value: '3' },
{ label: '视频号订单', value: '4' },
],
};
},
mounted() {
this.getList();
},
methods: {
getList() {
this.listLoading = true;
const params = { ...this.tableFrom };
if (!params.type) delete params.type;
if (!params.dateLimit) delete params.dateLimit;
if (!params.orderNo) delete params.orderNo;
getExternalOrderList(params)
.then((res) => {
this.tableData.data = res.list || [];
this.tableData.total = res.total || 0;
})
.catch(() => {})
.finally(() => {
this.listLoading = false;
});
},
seachList() {
this.tableFrom.page = 1;
this.getList();
},
selectChange(val) {
if (val) this.timeVal = [];
this.tableFrom.dateLimit = val;
this.seachList();
},
onchangeTime(e) {
this.timeVal = e;
this.tableFrom.dateLimit = e ? e.join(',') : '';
this.seachList();
},
handleSizeChange(val) {
this.tableFrom.limit = val;
this.getList();
},
handleCurrentChange(val) {
this.tableFrom.page = val;
this.getList();
},
},
};
</script>
<style scoped lang="scss">
.mt10 {
margin-top: 10px;
}
.mt20 {
margin-top: 20px;
}
.refund-tag {
color: #f124c7;
font-weight: bold;
}
.tabBox {
display: flex;
align-items: center;
}
.tabBox_tit {
font-size: 12px;
color: #333;
}
.tabBox_pice {
font-size: 12px;
color: #999;
}
.block {
text-align: right;
}
</style>