feat: 前端UI优化和后台管理功能修复

前端优化:
- 修复分享图片配置,使用API返回的share_image
- 新增设置页面,包含用户协议、隐私政策、退出登录、注销账号
- 调整用户中心菜单,将协议相关功能移至设置页面
- 隐藏首页和详情页的参与次数显示
- 商城页面价格显示改为哈尼券(价格x100)

后台管理修复:
- 修复用户列表货币字段映射错误(Diamond字段)
- 修复资金变动对话框,动态加载货币名称
- 修复ChangeDiamondAsync方法使用正确的Money2字段
This commit is contained in:
zpc 2026-02-27 20:37:00 +08:00
parent b619d4ce3a
commit aa11230116
14 changed files with 346 additions and 117 deletions

View File

@ -45,8 +45,8 @@
},
"mysql": {
"command": "node",
"args": [
"D:/CodeManage/HaniBlindBox/server/scripts/mssql-mcp-server/index.js"
"args": [
"D:/outsource/HaniBlindBox/server/scripts/mssql-mcp-server/index.js"
],
"env": {
"MYSQL_HOST": "192.168.195.16",
@ -62,7 +62,7 @@
"sqlserver": {
"command": "node",
"args": [
"D:/CodeManage/HaniBlindBox/server/scripts/mssql-mcp-server/index.js"
"D:/outsource/HaniBlindBox/server/scripts/mssql-mcp-server/index.js"
],
"env": {
"MSSQL_SERVER": "1.13.21.84",
@ -78,7 +78,7 @@
"admin-sqlserver": {
"command": "node",
"args": [
"D:/CodeManage/HaniBlindBox/server/scripts/mssql-mcp-server/index.js"
"D:/outsource/HaniBlindBox/server/scripts/mssql-mcp-server/index.js"
],
"env": {
"MSSQL_SERVER": "1.13.21.84",

View File

@ -221,7 +221,21 @@ const defaultConfig = {
// 配置类
class ConfigManager {
static getShareImageUrl() {
return "https://image.zfunbox.cn/icon/fenxiang.jpg"; //this.getAppSetting('share_image_url');
// 从 base_config 中获取 share_image
const shareImage = this.getBaseConfigKey('share_image');
// 如果没有配置,返回默认图片
return shareImage || "https://image.zfunbox.cn/icon/fenxiang.jpg";
}
/**
* 获取分享标题
* @returns {String} 分享标题
*/
static getShareTitle() {
// 从 base_config 中获取 share_title
const shareTitle = this.getBaseConfigKey('share_title');
// 如果没有配置,返回默认标题
return shareTitle || (this.getAppSetting("app_name") + ",正版潮玩手办一番赏");
}
/**
* 初始化并加载配置

View File

@ -12,9 +12,9 @@
// 测试环境配置 - .NET 10 后端
const testing = {
// baseUrl: 'https://app.zpc-xy.com/honey/api',
baseUrl: 'https://api.hanimanghe.top',
// baseUrl: 'https://api.hanimanghe.top',
// baseUrl: 'http://192.168.1.24:5238',
// baseUrl: 'http://192.168.195.15:2822',
baseUrl: 'http://192.168.195.15:2822',
imageUrl: 'https://youdas-1308826010.cos.ap-shanghai.myqcloud.com',
loginPage: '',
wxAppId: ''

View File

@ -126,94 +126,69 @@ class BasePlatform {
* @returns {Array} 菜单项数组每项包含id, show, title, icon, path和handler
*/
getUserMenuList() {
let m = [{
id: 1,
show: true,
title: '消费记录',
icon: 'my/s1.png',
path: '/pages/other/order_list',
handler: this.navigateToPath.bind(this)
},
{
id: 3,
show: true,
title: '我的收藏',
icon: 'my/s3.png',
path: '/package/mine/collect',
handler: this.navigateToPath.bind(this)
},
{
id: 4,
show: true,
title: '优惠券',
icon: 'my/s4.png',
path: '/pages/user/coupon',
handler: this.navigateToPath.bind(this)
},
{
id: 5,
show: true,
title: '邀请好友',
icon: 'my/s5.png',
path: '/pages/user/tui-guang',
handler: this.navigateToPath.bind(this)
},
{
id: 6,
show: true,
title: '加入福利群',
icon: 'my/s6.png',
path: '',
handler: this.handleJoinGroup.bind(this)
},
{
id: 7,
show: true,
title: '用户协议',
icon: 'my/s7.png',
// path: '/pages/guize/guize?type=4',
handler: this.getUserAgreement.bind(this)
},
{
id: 9,
show: true,
title: '隐私协议',
icon: 'my/s7.png',
handler: this.getPrivacyAgreement.bind(this)
}
];
if (uni.getStorageSync('token') != null && uni.getStorageSync('token') != "") {
m.push({
id: 8,
let m = [{
id: 1,
show: true,
title: '注销账号',
icon: 'my/s10.png',
path: '/pages/user/cancel-account-page',
title: '消费记录',
icon: 'my/s1.png',
path: '/pages/other/order_list',
handler: this.navigateToPath.bind(this)
}),
m.push({
id: 8,
show: true,
title: '退出登录',
icon: 'my/exit.png',
path: '',
handler: this.handleLogout.bind(this)
})
},
{
id: 3,
show: true,
title: '我的收藏',
icon: 'my/s3.png',
path: '/package/mine/collect',
handler: this.navigateToPath.bind(this)
},
{
id: 4,
show: true,
title: '优惠券',
icon: 'my/s4.png',
path: '/pages/user/coupon',
handler: this.navigateToPath.bind(this)
},
{
id: 5,
show: true,
title: '邀请好友',
icon: 'my/s5.png',
path: '/pages/user/tui-guang',
handler: this.navigateToPath.bind(this)
},
{
id: 6,
show: true,
title: '加入福利群',
icon: 'my/s6.png',
path: '',
handler: this.handleJoinGroup.bind(this)
},
{
id: 11,
show: true,
title: '设置',
icon: 'my/settings.png',
path: '/pages/user/settings',
handler: this.navigateToPath.bind(this)
}
];
// "关于"菜单:如需显示可将 show 改为 true
const customServiceMenu = {
id: 10,
show: false,
title: '关于',
icon: 'my/about.png',
path: '/pages/other/about',
handler: this.navigateToPath.bind(this)
};
m.push(customServiceMenu);
return m;
}
// “关于”菜单:如需显示可将 show 改为 true
const customServiceMenu = {
id: 10,
show: false,
title: '关于',
icon: 'my/about.png',
path: '/pages/other/about',
handler: this.navigateToPath.bind(this)
};
m.push(customServiceMenu);
return m;
}
/**
* 导航到指定页面

View File

@ -27,7 +27,8 @@
<image :src="item" lazy-load></image>
</view>
</view>
{{ pageData.goods.join_count }}次参与
<!-- 隐藏参与次数显示 -->
<!-- {{ pageData.goods.join_count }}次参与 -->
</view>
</view>

View File

@ -201,7 +201,15 @@
{
"path": "pages/user/coupon",
"style": {
"navigationBarTitleText": "",
"navigationBarTitleText": "优惠券",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}
},
{
"path": "pages/user/settings",
"style": {
"navigationBarTitleText": "设置",
"enablePullDownRefresh": false,
"navigationStyle": "custom"
}

View File

@ -21,7 +21,7 @@
</view>
<view class="price-box">
<view class="price">
<text><text style="font-size: 24rpx;">{{ item.price }}</text></text>
<text>{{ item.price * 100 }}<text style="font-size: 20rpx;">{{ $config.getAppSetting('currency2_name') }}</text></text>
</view>
</view>
<view class="num-box">
@ -54,7 +54,7 @@
<text>类型明信片</text>
<text class="qty">×{{ orderData.goods.prize_num }}</text>
</view>
<view class="goods-price">¥{{ orderData.goods.price }}</view>
<view class="goods-price">{{ orderData.goods.price * 100 }}{{ $config.getAppSetting('currency2_name') }}</view>
</view>
</view>

View File

@ -31,7 +31,8 @@
<image v-for="(item, i) in pageData.goods.join_user.slice(0, 3)" :key="i"
:style="{ 'z-index': i }" :src="item" mode="aspectFill"></image>
</view> -->
<view class="header-canyu-num">已有{{ pageData.goods.join_count }}次参与</view>
<!-- 隐藏参与次数显示 -->
<!-- <view class="header-canyu-num">已有{{ pageData.goods.join_count }}次参与</view> -->
</view>
<view class="header-shoucang justify-center" style=" flex-direction: column; margin-right: 48rpx;"
@tap="toggleCollect">

View File

@ -88,13 +88,11 @@
<image :src="$img1('common/new1.png')" class="image1"
style="animation: zoom 1.2s ease-in-out infinite" mode="widthFix"
v-if="item.new_is == 1 && tabList[tabCur].id != 2"></image>
<view class="image3 common_bg"
<!-- 隐藏参与次数标签 -->
<!-- <view class="image3 common_bg"
style="padding: 0 10rpx;line-height: 32rpx; background-color: #424242; border-radius: 10rpx;">
<!-- <text>
{{ item.type_text }}
</text> -->
<text>{{ item.join_count }}次参与</text>
</view>
</view> -->
<view v-if="item.is_shou_zhe == 1" class="half-tag">
<image class="img100" :src="$img1('common/chouBanjia.png')" mode="aspectFit"></image>
</view>
@ -279,6 +277,7 @@
onShareAppMessage() {
let imageUrl = this.$config.getShareImageUrl();
console.log('imageUrlimageUrlimageUrl:', imageUrl);
// 使 id .NET API
const userinfo = uni.getStorageSync("userinfo") || {};
const pid = userinfo.id || userinfo.ID || '';

View File

@ -0,0 +1,202 @@
<template>
<view class="settings-page">
<uni-nav-bar
title="设置"
left-icon="left"
color="#333"
backgroundColor="#FFFFFF"
:border="false"
:statusBar="true"
:fixed="true"
@clickLeft="goBack"
></uni-nav-bar>
<view class="nav-placeholder"></view>
<view class="settings-card">
<!-- 用户协议 -->
<view class="settings-item" @click="handleUserAgreement">
<view class="item-left">
<image class="item-icon" :src="$img1('my/s7.png')" mode=""></image>
<text class="item-title">用户协议</text>
</view>
<view class="item-right">
<uni-icons type="right" size="16" color="#CCCCCC"></uni-icons>
</view>
</view>
<view class="divider-line"></view>
<!-- 隐私政策 -->
<view class="settings-item" @click="handlePrivacyPolicy">
<view class="item-left">
<image class="item-icon" :src="$img1('my/s7.png')" mode=""></image>
<text class="item-title">隐私政策</text>
</view>
<view class="item-right">
<uni-icons type="right" size="16" color="#CCCCCC"></uni-icons>
</view>
</view>
<!-- 退出登录 -->
<view v-if="isLoggedIn" class="divider-line"></view>
<view v-if="isLoggedIn" class="settings-item" @click="handleLogout">
<view class="item-left">
<image class="item-icon" :src="$img1('my/exit.png')" mode=""></image>
<text class="item-title">退出登录</text>
</view>
<view class="item-right">
<uni-icons type="right" size="16" color="#CCCCCC"></uni-icons>
</view>
</view>
<!-- 注销账号 -->
<view v-if="isLoggedIn" class="divider-line"></view>
<view v-if="isLoggedIn" class="settings-item" @click="handleCancelAccount">
<view class="item-left">
<image class="item-icon" :src="$img1('my/s10.png')" mode=""></image>
<text class="item-title">注销账号</text>
</view>
<view class="item-right">
<uni-icons type="right" size="16" color="#CCCCCC"></uni-icons>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
isLoggedIn: false
}
},
onLoad() {
this.checkLoginStatus();
},
onShow() {
this.checkLoginStatus();
},
methods: {
/**
* 返回上一页
*/
goBack() {
uni.navigateBack({
delta: 1
});
},
/**
* 检查登录状态
*/
checkLoginStatus() {
const token = uni.getStorageSync('token');
this.isLoggedIn = token != null && token != "";
},
/**
* 用户协议
*/
handleUserAgreement() {
this.$platform.getUserAgreement();
},
/**
* 隐私政策
*/
handlePrivacyPolicy() {
this.$platform.getPrivacyAgreement();
},
/**
* 退出登录
*/
handleLogout() {
uni.showModal({
title: '提示',
content: '确定要退出登录吗?',
success: (res) => {
if (res.confirm) {
this.$platform.handleLogout();
}
}
});
},
/**
* 注销账号
*/
handleCancelAccount() {
this.$c.to({ url: '/pages/user/cancel-account-page' });
}
}
}
</script>
<style lang="scss" scoped>
.settings-page {
min-height: 100vh;
background-color: #F5F5F5;
&::before {
content: "";
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: -1;
background-image: url('/static/main_bg.jpg');
background-size: cover;
background-position: center;
background-repeat: no-repeat;
}
}
.nav-placeholder {
height: 88rpx;
}
.settings-card {
margin: 30rpx;
background-color: #FFFFFF;
border-radius: 16rpx;
overflow: hidden;
}
.settings-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 28rpx 32rpx;
background-color: #FFFFFF;
.item-left {
display: flex;
align-items: center;
flex: 1;
.item-icon {
width: 48rpx;
height: 48rpx;
margin-right: 24rpx;
}
.item-title {
font-size: 28rpx;
color: #333333;
}
}
.item-right {
flex-shrink: 0;
}
}
.divider-line {
height: 1rpx;
background-color: #E0E0E0;
margin: 0 32rpx;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -77,17 +77,17 @@ public class UserListResponse
public string? Mobile { get; set; }
/// <summary>
/// 余额
/// 余额/钻石 (对应 balance_name)
/// </summary>
public decimal Balance { get; set; }
/// <summary>
/// 积分
/// 积分/HH币 (对应 currency1_name)
/// </summary>
public decimal Integral { get; set; }
/// <summary>
/// 钻石/评分
/// 哈尼券 (对应 currency2_name)
/// </summary>
public decimal Diamond { get; set; }

View File

@ -221,17 +221,17 @@ public class UserBusinessService : IUserBusinessService
private async Task ChangeDiamondAsync(User user, decimal changeAmount, string remark, int operatorId)
{
var newDiamond = user.Score + changeAmount;
var newDiamond = (user.Money2 ?? 0) + changeAmount;
if (newDiamond < 0)
{
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "钻石不足");
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "哈尼券不足");
}
user.Score = newDiamond;
user.Money2 = newDiamond;
user.UpdatedAt = DateTime.Now;
// 记录变动日志
var profitScore = new ProfitScore
// 记录变动日志到 ProfitMoney2 表
var profitMoney2 = new ProfitMoney2
{
UserId = user.Id,
ChangeMoney = changeAmount,
@ -241,7 +241,7 @@ public class UserBusinessService : IUserBusinessService
ShareUid = operatorId,
CreatedAt = DateTime.Now
};
_dbContext.ProfitScores.Add(profitScore);
_dbContext.ProfitMoney2s.Add(profitMoney2);
}
#endregion
@ -1289,9 +1289,9 @@ public class UserBusinessService : IUserBusinessService
Nickname = user.Nickname,
Avatar = user.HeadImg,
Mobile = user.Mobile,
Balance = user.Money,
Integral = user.Integral,
Diamond = user.Score,
Balance = user.Money, // 钻石 (balance_name)
Integral = user.Integral, // HH币 (currency1_name)
Diamond = user.Money2 ?? 0, // 哈尼券 (currency2_name)
EquityLevel = user.Vip,
CreatedAt = user.CreatedAt,
LastLoginIp = loginIps.GetValueOrDefault(user.Id),

View File

@ -65,9 +65,10 @@
</template>
<script setup lang="ts">
import { ref, reactive, computed, watch } from 'vue'
import { ref, reactive, computed, watch, onMounted } from 'vue'
import { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { changeUserMoney, type UserMoneyChangeRequest, type UserListItem } from '@/api/business/user'
import { getAppSetting, type AppSetting } from '@/api/business/config'
interface Props {
modelValue: boolean
@ -92,6 +93,7 @@ const visible = computed({
const formRef = ref<FormInstance>()
const submitLoading = ref(false)
const appSetting = ref<AppSetting>()
const formData = reactive<UserMoneyChangeRequest>({
type: 1,
@ -100,6 +102,23 @@ const formData = reactive<UserMoneyChangeRequest>({
remark: ''
})
//
const loadAppSetting = async () => {
try {
const res = await getAppSetting()
if (res.data?.value) {
appSetting.value = res.data.value
}
} catch (error) {
console.error('获取应用设置失败:', error)
}
}
//
onMounted(() => {
loadAppSetting()
})
//
const formRules = computed<FormRules>(() => ({
operation: [{ required: true, message: '请选择操作类型', trigger: 'change' }],
@ -119,9 +138,19 @@ const formRules = computed<FormRules>(() => ({
]
}))
//
// -
const typeLabel = computed(() => {
const labels: Record<number, string> = { 1: '钻石', 2: 'H币', 3: '赊尼券' }
if (!appSetting.value) {
//
const labels: Record<number, string> = { 1: '钻石', 2: 'HH币', 3: '哈尼券' }
return labels[props.type] || '钻石'
}
const labels: Record<number, string> = {
1: appSetting.value.balance_name || '钻石', // /
2: appSetting.value.currency1_name || 'HH币', // /HH
3: appSetting.value.currency2_name || '哈尼券' // /
}
return labels[props.type] || '钻石'
})