feat(region-select): 添加收益区域查看页面

This commit is contained in:
woody 2025-07-25 09:07:39 +08:00
parent edcc03d9a6
commit cb69eea60d
7 changed files with 520 additions and 72 deletions

View File

@ -1,12 +1,8 @@
<template>
<div
v-if="popupVisible"
class="region-select-overlay"
@click.self="handleClose"
>
<div v-if="popupVisible" class="region-select-overlay">
<div class="region-select-popup">
<div class="popup-header">
<h3 class="popup-title">选择收益区域</h3>
<h3 class="popup-title">选择收益区域({{ typeEnumMap[area] }})</h3>
</div>
<div class="popup-content">
<picker-view
@ -17,26 +13,25 @@
@change="handlePickerChange"
>
<picker-view-column>
<div v-for="p in provinces" :key="p.id" class="picker-item">
<div v-for="p in provinceList" :key="p.id" class="picker-item">
{{ p.name }}
</div>
</picker-view-column>
<picker-view-column>
<div v-for="c in cities" :key="c.id" class="picker-item">
<picker-view-column
v-if="cityList.length && ['city', 'county'].includes(this.area)"
>
<div v-for="c in cityList" :key="c.id" class="picker-item">
{{ c.name }}
</div>
</picker-view-column>
<picker-view-column>
<div v-for="d in districts" :key="d.id" class="picker-item">
<picker-view-column v-if="countyList.length && this.area == 'county'">
<div v-for="d in countyList" :key="d.id" class="picker-item">
{{ d.name }}
</div>
</picker-view-column>
</picker-view>
</div>
<div class="popup-footer">
<button class="popup-btn popup-cancel" @click="handleClose">
取消
</button>
<button class="popup-btn popup-confirm" @click="handleConfirm">
确认
</button>
@ -49,66 +44,104 @@
import { getRegionSelect, setRegion, getRegionAreaTree } from '@/config/mine.js'
export default {
name: 'region-select',
name: 'RegionSelect',
props: {
autoTrigger: {
type: Boolean,
default: true,
},
},
data() {
return {
popupVisible: false,
areaTree: [],
provinces: [],
cities: [],
districts: [],
pickerValue: [0, 0, 0],
indicatorStyle: `height: 50px;`,
selectedList: [],
regionList: [],
provinceList: [],
cityList: [],
countyList: [],
area: '',
typeMap: {
province: 1,
city: 2,
county: 3,
},
typeEnumMap: {
province: '省',
city: '市',
county: '区',
},
}
},
async created() {
if (!this.autoTrigger) {
return
}
uni.showLoading({
title: '加载中...',
})
try {
const res = await getRegionSelect()
if (
res.code === 200 &&
res.data &&
res.data.regionStatus === 0 &&
!res.data.province
) {
this.open()
}
} catch (error) {
console.error('Failed to get region select info:', error)
} finally {
uni.hideLoading()
}
this.getRegionSelect()
.then(selectedList => {
this.selectedList = selectedList
const area = selectedList.shift()
this.open(area)
})
.catch(() => {
uni.hideLoading()
})
},
methods: {
async open() {
async getRegionSelect() {
return new Promise(async (resolve, reject) => {
try {
const res = await getRegionSelect()
if (res?.code === 200) {
const needSelected = Object.keys(res?.data || {})
.filter(key => key !== 'data')
.filter(key => res.data[key])
.reverse()
if (needSelected?.length) {
resolve(needSelected)
} else {
reject(false)
}
}
} catch (error) {
reject(false)
}
})
},
async open(area) {
this.area = area
uni.showLoading({
title: '加载中...',
})
return new Promise(async (resolve, reject) => {
if (this.areaTree?.length === 0) {
await this.loadAreaTree()
}
this.popupVisible = true
await this.loadAreaTree(area)
resolve()
uni.hideLoading()
})
},
async loadAreaTree() {
async loadAreaTree(selectedArea) {
try {
const res = await getRegionAreaTree()
const res = await getRegionAreaTree({
type: this.typeMap[selectedArea],
})
if (res.code === 200 && res.data) {
this.areaTree = res.data
this.provinces = this.areaTree
if (this.provinces.length > 0) {
this.cities = this.provinces[0].children || []
if (this.cities.length > 0) {
this.districts = this.cities[0].children || []
} else {
this.districts = []
}
}
this.provinceList = res.data.filter(item => item.parent === 0)
this.cityList = res.data.filter(item =>
this.provinceList.find(province => province.pkId === item.parent)
)
this.countyList = res.data.filter(item =>
this.cityList.find(city => city.pkId === item.parent)
)
this.getProvinceFilterList()
this.getCityFilterList()
this.generateRegionTree()
return true
}
return false
@ -116,42 +149,95 @@ export default {
console.error('Failed to load area tree:', error)
}
},
generateRegionTree() {
this.countyList.forEach(item => {
const city = this.cityList.find(city => city.pkId === item.parent)
if (city) {
if (!city.children) {
city.children = [item]
} else {
city.children.push(item)
}
}
})
this.cityList.forEach(item => {
const province = this.provinceList.find(
province => province.pkId === item.parent
)
if (province) {
if (!province.children) {
province.children = [item]
} else {
province.children.push(item)
}
}
})
this.cityList = this.provinceList[this.pickerValue[0]].children
this.countyList = this.cityList[this.pickerValue[1]].children
},
getProvinceFilterList() {
if (!this.cityList.length) {
this.provinceList = []
}
this.provinceList = this.provinceList.filter(
item => !!this.cityList.find(city => city.parent === item.pkId)
)
},
getCityFilterList() {
if (!this.countyList.length) {
this.cityList = []
}
this.cityList = this.cityList.filter(
item => !!this.countyList.find(county => county.parent === item.pkId)
)
},
handlePickerChange(e) {
const [pIndex, cIndex] = e.detail.value
const oldPIndex = this.pickerValue[0]
const oldCIndex = this.pickerValue[1]
this.pickerValue = e.detail.value
if (pIndex !== oldPIndex) {
this.cities = this.provinces[pIndex]?.children || []
this.districts = this.cities[0]?.children || []
} else if (cIndex !== oldCIndex) {
this.districts = this.cities[cIndex]?.children || []
if (oldPIndex !== pIndex) {
this.cityList = this.provinceList[pIndex].children
this.countyList = this.cityList[0].children
this.$set(this.pickerValue, 1, 0)
this.$set(this.pickerValue, 2, 0)
} else if (oldCIndex !== cIndex) {
this.countyList = this.cityList[cIndex].children
this.$set(this.pickerValue, 2, 0)
}
this.pickerValue = e.detail.value
},
handleClose() {
this.popupVisible = false
},
async handleConfirm() {
const [pIndex, cIndex, dIndex] = this.pickerValue
const province = this.provinces[pIndex]
const city = this.cities[cIndex]
const district = this.districts[dIndex]
const province = this.provinceList[pIndex]
const city = this.cityList[cIndex]
const county = this.countyList[dIndex]
if (!province || !city || !district) {
return
}
try {
const params = {
province: province.id,
city: city.id,
county: district.id,
type: this.typeMap[this.area],
province: province.pkId,
city: 0,
county: 0,
}
if (this.area == 'city') {
params.city = city.pkId
} else if (this.area == 'county') {
params.city = city.pkId
params.county = county.pkId
}
const res = await setRegion(params)
if (res.code === 200) {
this.handleClose()
this.$emit('success')
if (this.selectedList.length) {
this.area = this.selectedList.shift()
this.pickerValue = [0, 0, 0]
this.open(this.area)
} else {
this.$emit('success')
}
} else {
throw new Error(res.message || 'Failed to set region')
}
@ -180,7 +266,7 @@ export default {
.region-select-popup {
width: 90%;
max-width: 680rpx;
/* max-width: 680rpx; */
background-color: white;
border-radius: 24rpx;
animation: scale-up 0.3s ease-out;
@ -228,7 +314,7 @@ export default {
align-items: center;
justify-content: center;
height: 50px;
font-size: 30rpx;
font-size: 24rpx;
color: #666;
white-space: nowrap;
overflow: hidden;

View File

@ -30,3 +30,16 @@ export const getRefundList = params =>
// 退款列表接口(别名)
export const refundList = params =>
http.get('/sale/api/my-order/refund-list', { params })
// 获取是否能区域选择的资格
export const getRegionSelect = () => {
return http.get('/member/api/member/get-region')
}
// 设置区域
export const setRegion = data =>
http.post('/member/api/member/set-region', data)
// 获取可选择区域
export const getRegionAreaTree = params =>
http.get('/member/api/member/get-region-list', { params })

View File

@ -19,7 +19,7 @@ module.exports = vm => {
//#ifdef DEV_SERVER
console.log('DEV_SERVER')
config.baseURL = 'https://t-app.beida666.com/prod-api'
config.baseURL = 'http://192.168.0.86:8080'
//#endif
//#ifdef QA_SERVER

View File

@ -492,6 +492,13 @@
"navigationBarBackgroundColor": "#fff"
}
},
{
"path": "pages/mine/region/index",
"style": {
"navigationBarTitleText": "收益区域",
"navigationBarBackgroundColor": "#fff"
}
},
{
"path": "pages/mine/balance/transDetail",
"style": {

View File

@ -341,6 +341,9 @@
</u-button>
</view>
</u-popup>
<RegionSelect
v-if="Object.keys(userInfo).length && userInfo.memberCode != 'BD68880628'"
/>
<!-- <talentList :drShow="drShow" @closeShow="closeShow"></talentList> -->
</view>
</template>
@ -350,10 +353,12 @@ import clTabbar from '@/components/cl-tabbar.vue'
import * as min from '@/config/balance.js'
import * as api from '@/config/login.js'
import { getMemberBalance } from '@/config/mine.js'
// import talentList from "@/components/talentList.vue";
import RegionSelect from '@/components/region-select/index.vue'
export default {
components: {
'cl-tabbar': clTabbar,
RegionSelect,
// talentList,
},
data() {
@ -479,6 +484,13 @@ export default {
menuKey: '',
ifshow: false,
},
{
url: '/pages/mine/region/index',
name: '收益区域',
imgurl: '../../static/images/region-area.svg',
menuKey: '',
ifshow: false,
},
// {
// url: "2",
// name: "",
@ -712,7 +724,7 @@ export default {
getInfo() {
api.getInfo().then(res => {
if (res) {
this.userInfo = res.data
this.userInfo = res.data || {}
if (this.userInfo.memberCode == 'BD68880628') {
this.ifSpecial = true
this.otherMenuList = [
@ -861,6 +873,7 @@ export default {
//
this.drShow = true
} else {
console.log(url, 'url?')
uni.navigateTo({
url: url,
})

328
pages/mine/region/index.vue Normal file
View File

@ -0,0 +1,328 @@
<template>
<view class="region-container">
<!-- 收益区域列表 -->
<view class="region-list">
<!-- 省级区域 -->
<view v-if="shouldShowProvince" class="region-item">
<view class="region-card">
<view class="region-header">
<text class="region-level">省级区域</text>
</view>
<view v-if="selectedData.provinceData" class="region-content">
<text class="region-name">{{
selectedData.provinceData.provinceName
}}</text>
</view>
<view v-if="unSelected.includes('province')" class="region-action">
<button class="select-btn" @click="selectProvinceRegion">
选择省级收益区域
</button>
</view>
</view>
</view>
<!-- 市级区域 -->
<view v-if="shouldShowCity" class="region-item">
<view class="region-card">
<view class="region-header">
<text class="region-level">市级区域</text>
</view>
<view v-if="selectedData.cityData" class="region-content">
<text class="region-name">{{
selectedData.cityData.provinceName
}}</text>
<text class="region-separator">·</text>
<text class="region-name">{{
selectedData.cityData.cityName
}}</text>
</view>
<view v-if="unSelected.includes('city')" class="region-action">
<button class="select-btn" @click="selectCityRegion">
选择市级收益区域
</button>
</view>
</view>
</view>
<!-- 区县级区域 -->
<view v-if="shouldShowCounty" class="region-item">
<view class="region-card">
<view class="region-header">
<text class="region-level">区县级区域</text>
</view>
<view v-if="selectedData.countyData" class="region-content">
<text class="region-name">{{
selectedData.countyData.provinceName
}}</text>
<text class="region-separator">·</text>
<text class="region-name">{{
selectedData.countyData.cityName
}}</text>
<text class="region-separator">·</text>
<text class="region-name">{{
selectedData.countyData.countyName
}}</text>
</view>
<view v-if="unSelected.includes('county')" class="region-action">
<button class="select-btn" @click="selectCountyRegion">
选择区县收益区域
</button>
</view>
</view>
</view>
</view>
<!-- 空状态 -->
<view
v-if="!shouldShowProvince && !shouldShowCity && !shouldShowCounty"
class="empty-state"
>
<text class="empty-text">暂无收益区域数据</text>
</view>
<RegionSelect
@success="successHandle"
:autoTrigger="false"
ref="regionSelect"
/>
</view>
</template>
<script>
import { getRegionSelect } from '@/config/mine.js'
import RegionSelect from '@/components/region-select/index.vue'
export default {
name: 'RegionPage',
components: {
RegionSelect,
},
data() {
return {
unSelected: [],
selectedData: {},
rawData: {}, //
}
},
computed: {
//
shouldShowProvince() {
return (
this.selectedData.provinceData || this.unSelected.includes('province')
)
},
//
shouldShowCity() {
return this.selectedData.cityData || this.unSelected.includes('city')
},
//
shouldShowCounty() {
return this.selectedData.countyData || this.unSelected.includes('county')
},
},
onShow() {
this.getRegionSelect()
},
methods: {
async getRegionSelect() {
uni.showLoading({
title: '加载中...',
})
return new Promise(async (resolve, reject) => {
try {
const res = await getRegionSelect()
if (res?.code === 200) {
this.rawData = res.data || {}
// true
const unSelected = Object.keys(res?.data || {})
.filter(key => key !== 'data')
.filter(key => res.data[key] === true)
this.unSelected = unSelected
this.selectedData = res.data?.data || {}
resolve()
}
} catch (error) {
reject(false)
} finally {
uni.hideLoading()
}
})
},
//
selectProvinceRegion() {
this.$refs.regionSelect.open('province')
},
//
selectCityRegion() {
this.$refs.regionSelect.open('city')
},
//
selectCountyRegion() {
this.$refs.regionSelect.open('county')
},
successHandle(data) {
this.getRegionSelect()
},
},
}
</script>
<style lang="scss" scoped>
.region-container {
min-height: 100vh;
background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
padding: 15rpx;
}
.region-list {
padding: 0 15rpx;
}
.region-item {
margin-bottom: 15rpx;
.region-card {
background: #ffffff;
border-radius: 20rpx;
padding: 20rpx;
box-shadow: 0 8rpx 30rpx rgba(0, 91, 172, 0.1);
border: 2rpx solid rgba(0, 91, 172, 0.1);
transition: all 0.3s ease;
&:hover {
box-shadow: 0 12rpx 40rpx rgba(0, 91, 172, 0.15);
transform: translateY(-4rpx);
}
}
.region-header {
text-align: center;
padding: 15rpx 0 20rpx 0;
position: relative;
&::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 100rpx;
height: 4rpx;
background: linear-gradient(135deg, #005bac 0%, #0071d9 100%);
border-radius: 2rpx;
}
.region-level {
font-size: 32rpx;
color: #005bac;
font-weight: bold;
letter-spacing: 1rpx;
}
}
.region-content {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
margin-bottom: 15rpx;
padding: 10rpx 0;
.region-name {
font-size: 28rpx;
color: #333333;
font-weight: 500;
}
.region-separator {
margin: 0 10rpx;
font-size: 24rpx;
color: #999999;
}
}
.region-action {
.select-btn {
width: 100%;
height: 70rpx;
background: linear-gradient(135deg, #005bac 0%, #0071d9 100%);
color: #ffffff;
border: none;
border-radius: 35rpx;
font-size: 26rpx;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 6rpx 20rpx rgba(0, 91, 172, 0.3);
transition: all 0.3s ease;
&:active {
transform: scale(0.98);
box-shadow: 0 4rpx 15rpx rgba(0, 91, 172, 0.4);
}
&::after {
border: none;
}
}
}
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60rpx 40rpx;
.empty-text {
font-size: 26rpx;
color: #999999;
}
}
/* 响应式设计 */
@media screen and (max-width: 750rpx) {
.region-container {
padding: 10rpx;
}
.region-list {
padding: 0 10rpx;
}
.region-item {
margin-bottom: 12rpx;
.region-card {
padding: 18rpx;
}
.region-header {
padding: 12rpx 0 15rpx 0;
.region-level {
font-size: 30rpx;
}
}
.region-content {
padding: 8rpx 0;
margin-bottom: 12rpx;
.region-name {
font-size: 26rpx;
}
.region-separator {
font-size: 22rpx;
}
}
.region-action .select-btn {
height: 65rpx;
font-size: 24rpx;
}
}
}
</style>

View File

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1753348164758" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4496" xmlns:xlink="http://www.w3.org/1999/xlink" width="256" height="256"><path d="M331.585353 377.917166H139.330795a27.99979 27.99979 0 0 0-27.487794 23.039827l-43.199676 236.798224 438.71671 55.039587c-73.27945-93.823296-151.262866-211.838411-175.774682-314.877638zM864.157359 737.562468l104.319217 13.087902-12.351907-67.679492-9.503929-52.159609a1838.994208 1838.994208 0 0 1-82.463381 106.751199zM814.173734 795.770032a2044.304668 2044.304668 0 0 1-40.671695 44.479666l-23.263826 24.543816-46.463651 49.087632-46.495652-49.087632-23.263825-24.575816a2105.904206 2105.904206 0 0 1-67.903491-75.551433l-246.782149-30.975768L400.416837 1023.99232h584.411617a28.127789 28.127789 0 0 0 27.455794-33.27975l-31.743762-174.078695zM251.649953 725.178561L57.123412 700.794744 4.227808 990.71257A28.159789 28.159789 0 0 0 31.683602 1023.99232h302.269733l-81.919385-293.213801a29.887776 29.887776 0 0 1-0.383997-5.599958zM703.774562 438.396712a127.99904 127.99904 0 1 0-127.99904-127.99904 128.159039 128.159039 0 0 0 127.99904 127.99904z m0-191.99856a63.99952 63.99952 0 1 1-63.99952 63.99952 64.06352 64.06352 0 0 1 63.99952-63.99952z" fill="#fb3123" p-id="4497"></path><path d="M703.774562 820.793844l23.231825-24.575816c30.015775-31.679762 293.245801-314.65364 293.245801-479.740402a316.477626 316.477626 0 1 0-632.955253 0c0 165.086762 263.230026 448.02864 293.213801 479.740402z m0-756.794324a252.798104 252.798104 0 0 1 252.478106 252.478106c0 107.775192-165.086762 313.405649-252.478106 410.524921-87.423344-97.119272-252.478106-302.749729-252.478107-410.524921A252.766104 252.766104 0 0 1 703.774562 63.99952z" fill="#fb3123" p-id="4498"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB