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": { "mysql": {
"command": "node", "command": "node",
"args": [ "args": [
"D:/CodeManage/HaniBlindBox/server/scripts/mssql-mcp-server/index.js" "D:/outsource/HaniBlindBox/server/scripts/mssql-mcp-server/index.js"
], ],
"env": { "env": {
"MYSQL_HOST": "192.168.195.16", "MYSQL_HOST": "192.168.195.16",
@ -62,7 +62,7 @@
"sqlserver": { "sqlserver": {
"command": "node", "command": "node",
"args": [ "args": [
"D:/CodeManage/HaniBlindBox/server/scripts/mssql-mcp-server/index.js" "D:/outsource/HaniBlindBox/server/scripts/mssql-mcp-server/index.js"
], ],
"env": { "env": {
"MSSQL_SERVER": "1.13.21.84", "MSSQL_SERVER": "1.13.21.84",
@ -78,7 +78,7 @@
"admin-sqlserver": { "admin-sqlserver": {
"command": "node", "command": "node",
"args": [ "args": [
"D:/CodeManage/HaniBlindBox/server/scripts/mssql-mcp-server/index.js" "D:/outsource/HaniBlindBox/server/scripts/mssql-mcp-server/index.js"
], ],
"env": { "env": {
"MSSQL_SERVER": "1.13.21.84", "MSSQL_SERVER": "1.13.21.84",

View File

@ -221,7 +221,21 @@ const defaultConfig = {
// 配置类 // 配置类
class ConfigManager { class ConfigManager {
static getShareImageUrl() { 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 后端 // 测试环境配置 - .NET 10 后端
const testing = { const testing = {
// baseUrl: 'https://app.zpc-xy.com/honey/api', // 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.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', imageUrl: 'https://youdas-1308826010.cos.ap-shanghai.myqcloud.com',
loginPage: '', loginPage: '',
wxAppId: '' wxAppId: ''

View File

@ -126,94 +126,69 @@ class BasePlatform {
* @returns {Array} 菜单项数组每项包含id, show, title, icon, path和handler * @returns {Array} 菜单项数组每项包含id, show, title, icon, path和handler
*/ */
getUserMenuList() { getUserMenuList() {
let m = [{ let m = [{
id: 1, 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,
show: true, show: true,
title: '注销账号', title: '消费记录',
icon: 'my/s10.png', icon: 'my/s1.png',
path: '/pages/user/cancel-account-page', path: '/pages/other/order_list',
handler: this.navigateToPath.bind(this) handler: this.navigateToPath.bind(this)
}), },
m.push({ {
id: 8, id: 3,
show: true, show: true,
title: '退出登录', title: '我的收藏',
icon: 'my/exit.png', icon: 'my/s3.png',
path: '', path: '/package/mine/collect',
handler: this.handleLogout.bind(this) 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> <image :src="item" lazy-load></image>
</view> </view>
</view> </view>
{{ pageData.goods.join_count }}次参与 <!-- 隐藏参与次数显示 -->
<!-- {{ pageData.goods.join_count }}次参与 -->
</view> </view>
</view> </view>

View File

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

View File

@ -21,7 +21,7 @@
</view> </view>
<view class="price-box"> <view class="price-box">
<view class="price"> <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> </view>
<view class="num-box"> <view class="num-box">
@ -54,7 +54,7 @@
<text>类型明信片</text> <text>类型明信片</text>
<text class="qty">×{{ orderData.goods.prize_num }}</text> <text class="qty">×{{ orderData.goods.prize_num }}</text>
</view> </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> </view>

View File

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

View File

@ -88,13 +88,11 @@
<image :src="$img1('common/new1.png')" class="image1" <image :src="$img1('common/new1.png')" class="image1"
style="animation: zoom 1.2s ease-in-out infinite" mode="widthFix" style="animation: zoom 1.2s ease-in-out infinite" mode="widthFix"
v-if="item.new_is == 1 && tabList[tabCur].id != 2"></image> 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;"> style="padding: 0 10rpx;line-height: 32rpx; background-color: #424242; border-radius: 10rpx;">
<!-- <text>
{{ item.type_text }}
</text> -->
<text>{{ item.join_count }}次参与</text> <text>{{ item.join_count }}次参与</text>
</view> </view> -->
<view v-if="item.is_shou_zhe == 1" class="half-tag"> <view v-if="item.is_shou_zhe == 1" class="half-tag">
<image class="img100" :src="$img1('common/chouBanjia.png')" mode="aspectFit"></image> <image class="img100" :src="$img1('common/chouBanjia.png')" mode="aspectFit"></image>
</view> </view>
@ -279,6 +277,7 @@
onShareAppMessage() { onShareAppMessage() {
let imageUrl = this.$config.getShareImageUrl(); let imageUrl = this.$config.getShareImageUrl();
console.log('imageUrlimageUrlimageUrl:', imageUrl);
// 使 id .NET API // 使 id .NET API
const userinfo = uni.getStorageSync("userinfo") || {}; const userinfo = uni.getStorageSync("userinfo") || {};
const pid = userinfo.id || userinfo.ID || ''; 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; } public string? Mobile { get; set; }
/// <summary> /// <summary>
/// 余额 /// 余额/钻石 (对应 balance_name)
/// </summary> /// </summary>
public decimal Balance { get; set; } public decimal Balance { get; set; }
/// <summary> /// <summary>
/// 积分 /// 积分/HH币 (对应 currency1_name)
/// </summary> /// </summary>
public decimal Integral { get; set; } public decimal Integral { get; set; }
/// <summary> /// <summary>
/// 钻石/评分 /// 哈尼券 (对应 currency2_name)
/// </summary> /// </summary>
public decimal Diamond { get; set; } 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) 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) if (newDiamond < 0)
{ {
throw new BusinessException(BusinessErrorCodes.ValidationFailed, "钻石不足"); throw new BusinessException(BusinessErrorCodes.ValidationFailed, "哈尼券不足");
} }
user.Score = newDiamond; user.Money2 = newDiamond;
user.UpdatedAt = DateTime.Now; user.UpdatedAt = DateTime.Now;
// 记录变动日志 // 记录变动日志到 ProfitMoney2 表
var profitScore = new ProfitScore var profitMoney2 = new ProfitMoney2
{ {
UserId = user.Id, UserId = user.Id,
ChangeMoney = changeAmount, ChangeMoney = changeAmount,
@ -241,7 +241,7 @@ public class UserBusinessService : IUserBusinessService
ShareUid = operatorId, ShareUid = operatorId,
CreatedAt = DateTime.Now CreatedAt = DateTime.Now
}; };
_dbContext.ProfitScores.Add(profitScore); _dbContext.ProfitMoney2s.Add(profitMoney2);
} }
#endregion #endregion
@ -1289,9 +1289,9 @@ public class UserBusinessService : IUserBusinessService
Nickname = user.Nickname, Nickname = user.Nickname,
Avatar = user.HeadImg, Avatar = user.HeadImg,
Mobile = user.Mobile, Mobile = user.Mobile,
Balance = user.Money, Balance = user.Money, // 钻石 (balance_name)
Integral = user.Integral, Integral = user.Integral, // HH币 (currency1_name)
Diamond = user.Score, Diamond = user.Money2 ?? 0, // 哈尼券 (currency2_name)
EquityLevel = user.Vip, EquityLevel = user.Vip,
CreatedAt = user.CreatedAt, CreatedAt = user.CreatedAt,
LastLoginIp = loginIps.GetValueOrDefault(user.Id), LastLoginIp = loginIps.GetValueOrDefault(user.Id),

View File

@ -65,9 +65,10 @@
</template> </template>
<script setup lang="ts"> <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 { ElMessage, type FormInstance, type FormRules } from 'element-plus'
import { changeUserMoney, type UserMoneyChangeRequest, type UserListItem } from '@/api/business/user' import { changeUserMoney, type UserMoneyChangeRequest, type UserListItem } from '@/api/business/user'
import { getAppSetting, type AppSetting } from '@/api/business/config'
interface Props { interface Props {
modelValue: boolean modelValue: boolean
@ -92,6 +93,7 @@ const visible = computed({
const formRef = ref<FormInstance>() const formRef = ref<FormInstance>()
const submitLoading = ref(false) const submitLoading = ref(false)
const appSetting = ref<AppSetting>()
const formData = reactive<UserMoneyChangeRequest>({ const formData = reactive<UserMoneyChangeRequest>({
type: 1, type: 1,
@ -100,6 +102,23 @@ const formData = reactive<UserMoneyChangeRequest>({
remark: '' 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>(() => ({ const formRules = computed<FormRules>(() => ({
operation: [{ required: true, message: '请选择操作类型', trigger: 'change' }], operation: [{ required: true, message: '请选择操作类型', trigger: 'change' }],
@ -119,9 +138,19 @@ const formRules = computed<FormRules>(() => ({
] ]
})) }))
// // -
const typeLabel = computed(() => { 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] || '钻石' return labels[props.type] || '钻石'
}) })