细节修改

This commit is contained in:
18631081161 2026-01-06 19:00:44 +08:00
parent 181ffdae66
commit 74de21b28f
25 changed files with 891 additions and 57 deletions

View File

@ -4,7 +4,7 @@
VITE_APP_TITLE=相宜相亲后台管理系统(开发)
# API基础地址
VITE_API_BASE_URL=http://localhost:5000/api
VITE_API_BASE_URL=http://localhost:5001/api
# 静态资源服务器地址(AppApi用于图片等静态资源
VITE_STATIC_BASE_URL=http://localhost:5001
# 静态资源服务器地址(所有图片统一从AppApi访问
VITE_STATIC_BASE_URL=http://localhost:5000

View File

@ -20,3 +20,31 @@ export function setDefaultAvatar(avatarUrl: string) {
export function getAllConfigs() {
return request.get('/admin/config/all')
}
/**
*
*/
export function getUserAgreement() {
return request.get('/admin/config/userAgreement')
}
/**
*
*/
export function setUserAgreement(content: string) {
return request.post('/admin/config/userAgreement', { content })
}
/**
*
*/
export function getPrivacyPolicy() {
return request.get('/admin/config/privacyPolicy')
}
/**
*
*/
export function setPrivacyPolicy(content: string) {
return request.post('/admin/config/privacyPolicy', { content })
}

View File

@ -60,8 +60,8 @@ import { ElMessage } from 'element-plus'
import type { UploadFile, UploadFiles, UploadRawFile, UploadUserFile } from 'element-plus'
import { getToken } from '@/utils/auth'
// AppApi
const STATIC_BASE = import.meta.env.VITE_STATIC_BASE_URL || 'http://localhost:5001'
// AppApi访
const STATIC_BASE = import.meta.env.VITE_STATIC_BASE_URL || 'http://localhost:5000'
// URLURL
function getFullImageUrl(url: string): string {

View File

@ -2,8 +2,8 @@
* URL处理工具
*/
// 获取静态资源服务器地址AppApi用于图片等静态资源
const STATIC_BASE = import.meta.env.VITE_STATIC_BASE_URL || 'http://localhost:5001'
// 静态资源服务器地址所有图片统一从AppApi访问
const STATIC_BASE = import.meta.env.VITE_STATIC_BASE_URL || 'http://localhost:5000'
/**
* URLURL

View File

@ -7,36 +7,79 @@
</div>
</template>
<el-form :model="configForm" label-width="120px" class="config-form">
<!-- 默认头像设置 -->
<el-form-item label="默认头像">
<div class="avatar-upload">
<el-upload
class="avatar-uploader"
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
accept="image/*"
>
<img v-if="configForm.defaultAvatar" :src="getFullUrl(configForm.defaultAvatar)" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
<div class="avatar-tip">
<p>建议尺寸200x200像素</p>
<p>支持格式JPGPNG</p>
<p>新用户注册时将使用此头像作为默认头像</p>
</div>
</div>
</el-form-item>
<el-tabs v-model="activeTab" class="config-tabs">
<!-- 基础配置 -->
<el-tab-pane label="基础配置" name="basic">
<el-form :model="configForm" label-width="120px" class="config-form">
<!-- 默认头像设置 -->
<el-form-item label="默认头像">
<div class="avatar-upload">
<el-upload
class="avatar-uploader"
:action="uploadUrl"
:headers="uploadHeaders"
:show-file-list="false"
:on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload"
accept="image/*"
>
<img v-if="configForm.defaultAvatar" :src="getFullUrl(configForm.defaultAvatar)" class="avatar" />
<el-icon v-else class="avatar-uploader-icon"><Plus /></el-icon>
</el-upload>
<div class="avatar-tip">
<p>建议尺寸200x200像素</p>
<p>支持格式JPGPNG</p>
<p>新用户注册时将使用此头像作为默认头像</p>
</div>
</div>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="saveConfig" :loading="saving">
保存配置
</el-button>
</el-form-item>
</el-form>
<el-form-item>
<el-button type="primary" @click="saveBasicConfig" :loading="saving">
保存配置
</el-button>
</el-form-item>
</el-form>
</el-tab-pane>
<!-- 用户协议 -->
<el-tab-pane label="用户协议" name="userAgreement">
<div class="agreement-editor">
<div class="editor-toolbar">
<span class="toolbar-title">用户协议内容</span>
<el-button type="primary" @click="saveUserAgreement" :loading="savingAgreement">
保存协议
</el-button>
</div>
<el-input
v-model="agreementForm.userAgreement"
type="textarea"
:rows="20"
placeholder="请输入用户协议内容支持HTML格式"
class="agreement-textarea"
/>
</div>
</el-tab-pane>
<!-- 隐私协议 -->
<el-tab-pane label="隐私协议" name="privacyPolicy">
<div class="agreement-editor">
<div class="editor-toolbar">
<span class="toolbar-title">隐私协议内容</span>
<el-button type="primary" @click="savePrivacyPolicy" :loading="savingPolicy">
保存协议
</el-button>
</div>
<el-input
v-model="agreementForm.privacyPolicy"
type="textarea"
:rows="20"
placeholder="请输入隐私协议内容支持HTML格式"
class="agreement-textarea"
/>
</div>
</el-tab-pane>
</el-tabs>
</el-card>
</div>
</template>
@ -45,7 +88,14 @@
import { ref, onMounted, computed } from 'vue'
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import { getDefaultAvatar, setDefaultAvatar } from '@/api/config'
import {
getDefaultAvatar,
setDefaultAvatar,
getUserAgreement,
setUserAgreement,
getPrivacyPolicy,
setPrivacyPolicy
} from '@/api/config'
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
@ -53,11 +103,20 @@ const apiBaseUrl = import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/a
// /api
const serverUrl = apiBaseUrl.replace(/\/api$/, '')
const activeTab = ref('basic')
const configForm = ref({
defaultAvatar: ''
})
const agreementForm = ref({
userAgreement: '',
privacyPolicy: ''
})
const saving = ref(false)
const savingAgreement = ref(false)
const savingPolicy = ref(false)
const uploadUrl = computed(() => `${apiBaseUrl}/admin/upload`)
@ -83,6 +142,25 @@ const loadConfig = async () => {
}
}
const loadAgreements = async () => {
try {
const [userAgreementRes, privacyPolicyRes] = await Promise.all([
getUserAgreement(),
getPrivacyPolicy()
])
if (userAgreementRes) {
agreementForm.value.userAgreement = userAgreementRes.content || ''
}
if (privacyPolicyRes) {
agreementForm.value.privacyPolicy = privacyPolicyRes.content || ''
}
} catch (error) {
console.error('加载协议失败:', error)
}
}
const handleAvatarSuccess = (response) => {
if (response.code === 0 && response.data) {
configForm.value.defaultAvatar = response.data.url
@ -107,7 +185,7 @@ const beforeAvatarUpload = (file) => {
return true
}
const saveConfig = async () => {
const saveBasicConfig = async () => {
if (!configForm.value.defaultAvatar) {
ElMessage.warning('请先上传默认头像')
return
@ -126,8 +204,43 @@ const saveConfig = async () => {
}
}
const saveUserAgreement = async () => {
if (!agreementForm.value.userAgreement.trim()) {
ElMessage.warning('请输入用户协议内容')
return
}
savingAgreement.value = true
try {
await setUserAgreement(agreementForm.value.userAgreement)
ElMessage.success('用户协议保存成功')
} catch (error) {
console.error('保存用户协议失败:', error)
} finally {
savingAgreement.value = false
}
}
const savePrivacyPolicy = async () => {
if (!agreementForm.value.privacyPolicy.trim()) {
ElMessage.warning('请输入隐私协议内容')
return
}
savingPolicy.value = true
try {
await setPrivacyPolicy(agreementForm.value.privacyPolicy)
ElMessage.success('隐私协议保存成功')
} catch (error) {
console.error('保存隐私协议失败:', error)
} finally {
savingPolicy.value = false
}
}
onMounted(() => {
loadConfig()
loadAgreements()
})
</script>
@ -137,7 +250,7 @@ onMounted(() => {
}
.config-card {
max-width: 800px;
max-width: 1200px;
}
.card-header {
@ -145,6 +258,10 @@ onMounted(() => {
font-weight: 600;
}
.config-tabs {
margin-top: 20px;
}
.config-form {
padding: 20px 0;
}
@ -199,4 +316,29 @@ onMounted(() => {
.avatar-tip p {
margin: 0;
}
.agreement-editor {
padding: 20px 0;
}
.editor-toolbar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid var(--el-border-color-light);
}
.toolbar-title {
font-size: 16px;
font-weight: 600;
color: var(--el-text-color-primary);
}
.agreement-textarea :deep(.el-textarea__inner) {
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
font-size: 14px;
line-height: 1.6;
}
</style>

56
docs/用户协议.txt Normal file
View File

@ -0,0 +1,56 @@
<h2>相宜相亲用户服务协议</h2>
<p><strong>更新日期2026年1月6日</strong></p>
<p><strong>生效日期2026年1月6日</strong></p>
<p>欢迎您使用相宜相亲小程序(以下简称"本平台")!在使用本平台服务之前,请您仔细阅读并充分理解本协议的全部内容。</p>
<h3>一、服务说明</h3>
<p>1. 本平台是一个为用户提供相亲交友服务的社交平台,旨在帮助用户寻找合适的另一半。</p>
<p>2. 本平台提供的服务包括但不限于:资料填写、信息浏览、用户沟通、会员服务、消息推送等功能。</p>
<p>3. 用户需年满18周岁方可使用本平台服务。</p>
<h3>二、用户注册与账号管理</h3>
<p>1. 用户在注册时应提供真实、准确、完整的个人信息,并在信息发生变化时及时更新。</p>
<p>2. 用户应妥善保管账号和密码,因用户原因导致的账号安全问题由用户自行承担。</p>
<p>3. 用户不得将账号转让、出借给他人使用。</p>
<h3>三、用户行为规范</h3>
<p>1. 用户应遵守国家法律法规,不得利用本平台从事违法违规活动。</p>
<p>2. 用户不得发布虚假、欺诈、骚扰、侮辱、诽谤等不良信息。</p>
<p>3. 用户不得上传含有色情、暴力、恐怖等违法违规内容的图片或信息。</p>
<p>4. 用户应尊重其他用户的隐私和合法权益。</p>
<h3>四、相亲资料管理</h3>
<p>1. 用户填写的相亲资料应真实、准确,不得提供虚假信息。</p>
<p>2. 用户上传的照片应为本人或其子女的真实照片,不得使用他人照片。</p>
<p>3. 用户可选择是否公开照片,平台将根据用户选择进行展示。</p>
<h3>五、会员服务</h3>
<p>1. 本平台提供多种会员服务包括限时会员1299元、诚意会员1999元、诚意会员家庭版2999元。</p>
<p>2. 会员服务的具体权益以平台公示为准。</p>
<p>3. 会员费用一经支付,除法律规定的情形外,不予退还。</p>
<h3>六、实名认证</h3>
<p>1. 用户可选择进行实名认证,认证通过后将获得"已实名"标识。</p>
<p>2. 实名认证需支付88元认证费用并提供真实有效的身份证信息。</p>
<p>3. 平台将对用户提交的身份信息进行严格保密。</p>
<h3>七、联系方式解锁</h3>
<p>1. 用户解锁其他用户的联系方式需消耗联系次数。</p>
<p>2. 新用户注册时赠送2次免费联系次数。</p>
<p>3. 联系次数用完后,需开通会员获取更多次数。</p>
<h3>八、免责声明</h3>
<p>1. 本平台仅提供信息展示和沟通渠道,不对用户之间的交往结果承担责任。</p>
<p>2. 用户应自行判断其他用户信息的真实性,谨慎交往。</p>
<p>3. 因不可抗力导致的服务中断,本平台不承担责任。</p>
<h3>九、协议修改</h3>
<p>1. 本平台有权根据需要修改本协议内容。</p>
<p>2. 协议修改后,将在平台公示,用户继续使用即视为同意修改后的协议。</p>
<h3>十、联系方式</h3>
<p>如您对本协议有任何疑问,请通过平台内的"管家指导"功能联系我们。</p>
<p><strong>相宜相亲运营团队</strong></p>

68
docs/隐私协议.txt Normal file
View File

@ -0,0 +1,68 @@
<h2>相宜相亲隐私政策</h2>
<p><strong>更新日期2026年1月6日</strong></p>
<p><strong>生效日期2026年1月6日</strong></p>
<p>相宜相亲(以下简称"我们")深知个人信息对您的重要性,我们将按照法律法规的规定,保护您的个人信息及隐私安全。</p>
<h3>一、我们收集的信息</h3>
<p>为了向您提供相亲交友服务,我们可能会收集以下信息:</p>
<p><strong>1. 基本信息</strong></p>
<p>• 微信授权信息(昵称、头像)</p>
<p>• 手机号码(用于账号验证和联系)</p>
<p>• 微信号(用于用户间交换联系方式)</p>
<p><strong>2. 相亲资料信息</strong></p>
<p>• 个人基本信息:姓名、性别、出生年份、身高、体重、学历、职业、月收入等</p>
<p>• 家庭信息:与孩子的关系、家乡城市、现居城市等</p>
<p>• 婚姻状况:婚姻状态、房产情况、车辆情况等</p>
<p>• 照片:用户上传的个人或子女照片</p>
<p>• 相亲要求:期望的另一半条件</p>
<p><strong>3. 实名认证信息</strong></p>
<p>• 身份证照片(正反面)</p>
<p>• 身份证号码</p>
<p>• 真实姓名</p>
<p><strong>4. 使用信息</strong></p>
<p>• 浏览记录、收藏记录、解锁记录</p>
<p>• 聊天记录</p>
<p>• 设备信息、IP地址、位置信息</p>
<h3>二、信息的使用</h3>
<p>我们收集的信息将用于以下目的:</p>
<p>1. 提供相亲交友服务,包括资料展示、用户匹配、消息通知等</p>
<p>2. 验证用户身份,保障账号安全</p>
<p>3. 进行实名认证,提升平台可信度</p>
<p>4. 改进和优化我们的服务</p>
<p>5. 向您推送相关通知和消息</p>
<h3>三、信息的保护</h3>
<p>1. 我们采用业界标准的安全技术措施保护您的个人信息。</p>
<p>2. 您的身份证信息仅用于实名认证,认证完成后将进行加密存储。</p>
<p>3. 您的照片可选择是否公开,未公开的照片将进行模糊处理展示。</p>
<p>4. 我们不会将您的个人信息出售给第三方。</p>
<h3>四、信息的共享</h3>
<p>1. 在您同意的情况下,您的部分信息将展示给其他用户(如相亲资料)。</p>
<p>2. 当您与其他用户交换联系方式时,相关信息将被共享。</p>
<p>3. 为完成实名认证,我们可能会将您的身份信息提交给第三方认证服务商。</p>
<p>4. 根据法律法规要求,我们可能需要向有关部门提供您的信息。</p>
<h3>五、您的权利</h3>
<p>1. 您可以随时查看和修改您的个人资料。</p>
<p>2. 您可以选择是否公开您的照片。</p>
<p>3. 您可以申请删除您的账号和相关数据。</p>
<p>4. 您可以拒绝接收推送消息。</p>
<h3>六、未成年人保护</h3>
<p>本平台仅面向18周岁及以上用户我们不会故意收集未成年人的个人信息。</p>
<h3>七、隐私政策的更新</h3>
<p>我们可能会不时更新本隐私政策,更新后的政策将在平台公示。</p>
<h3>八、联系我们</h3>
<p>如您对本隐私政策有任何疑问,请通过平台内的"管家指导"功能联系我们。</p>
<p><strong>相宜相亲运营团队</strong></p>

15
miniapp/api/agreement.js Normal file
View File

@ -0,0 +1,15 @@
import { get } from './request.js'
/**
* 获取用户协议
*/
export const getUserAgreement = () => {
return get('/config/userAgreement')
}
/**
* 获取隐私协议
*/
export const getPrivacyPolicy = () => {
return get('/config/privacyPolicy')
}

View File

@ -6,10 +6,10 @@
import { get, post, del } from './request'
import { getToken } from '../utils/storage'
// API 基础地址
const BASE_URL = 'http://localhost:5001/api/app'
// 静态资源服务器地址
const STATIC_URL = 'http://localhost:5001'
// API 基础地址AppApi
const BASE_URL = 'http://localhost:5000/api/app'
// 静态资源服务器地址AppApi
const STATIC_URL = 'http://localhost:5000'
/**
* 提交/更新用户资料

View File

@ -9,7 +9,7 @@
import { getToken, removeToken, removeUserInfo } from '../utils/storage'
// API 基础地址
const BASE_URL = 'http://localhost:5001/api/app'
const BASE_URL = 'http://localhost:5000/api/app'
// 请求状态
let isRefreshing = false

View File

@ -126,6 +126,13 @@
"navigationStyle": "custom",
"navigationBarTitleText": "登录"
}
},
{
"path": "pages/agreement/index",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "协议"
}
}
],
"globalStyle": {

View File

@ -0,0 +1,327 @@
<template>
<view class="agreement-page">
<!-- 页面加载状态 -->
<Loading type="page" :loading="pageLoading" />
<!-- 顶部背景图 -->
<view class="top-bg">
<image src="/static/title_bg.png" mode="aspectFill" class="bg-img" />
</view>
<!-- 自定义导航栏 -->
<view class="custom-navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="navbar-content">
<view class="navbar-back" @click="handleBack">
<text class="back-icon"></text>
</view>
<text class="navbar-title">{{ pageTitle }}</text>
<view class="navbar-placeholder"></view>
</view>
</view>
<!-- 可滚动内容区域 -->
<scroll-view
class="content-scroll"
scroll-y
:style="{
top: (statusBarHeight + 44) + 'px',
height: 'calc(100vh - ' + (statusBarHeight + 44) + 'px)'
}"
>
<view class="agreement-content">
<view class="content-wrapper">
<rich-text
v-if="agreementContent && agreementContent.trim()"
:nodes="formatContent(agreementContent)"
class="rich-content"
/>
<view v-else-if="!pageLoading" class="empty-content">
<image src="/static/ic_empty.png" mode="aspectFit" class="empty-icon" />
<text class="empty-text">暂无{{ agreementType === 'user' ? '用户协议' : '隐私协议' }}内容</text>
</view>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import { ref, computed, onMounted } from 'vue'
import { getUserAgreement, getPrivacyPolicy } from '@/api/agreement.js'
import Loading from '@/components/Loading/index.vue'
export default {
name: 'AgreementPage',
components: {
Loading
},
setup() {
//
const statusBarHeight = ref(20)
//
const pageLoading = ref(true)
const agreementType = ref('user') // user: , privacy:
const agreementContent = ref('')
//
const getSystemInfo = () => {
uni.getSystemInfo({
success: (res) => {
statusBarHeight.value = res.statusBarHeight || 20
}
})
}
//
const handleBack = () => {
uni.navigateBack()
}
//
const pageTitle = computed(() => {
return agreementType.value === 'user' ? '用户协议' : '隐私协议'
})
/**
* 格式化协议内容
*/
const formatContent = (content) => {
if (!content) return ''
// HTML
if (content.includes('<')) {
return content
}
// HTML
return content.replace(/\n/g, '<br/>')
}
/**
* 获取协议内容
*/
const getAgreementContent = async () => {
try {
let res
if (agreementType.value === 'user') {
res = await getUserAgreement()
} else {
res = await getPrivacyPolicy()
}
console.log('协议接口返回:', res)
//
if (res) {
if (res.data && res.data.content) {
// : { code: 0, data: { content: "..." } }
agreementContent.value = res.data.content
} else if (res.content) {
// : { content: "..." }
agreementContent.value = res.content
} else if (typeof res === 'string') {
//
agreementContent.value = res
}
}
} catch (error) {
console.error('获取协议内容失败:', error)
uni.showToast({
title: '获取协议内容失败',
icon: 'none'
})
}
}
/**
* 初始化页面
*/
const initPage = async () => {
pageLoading.value = true
try {
getSystemInfo()
//
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = currentPage.options || {}
agreementType.value = options.type || 'user'
await getAgreementContent()
} catch (error) {
console.error('初始化页面失败:', error)
} finally {
pageLoading.value = false
}
}
onMounted(() => {
initPage()
})
return {
statusBarHeight,
pageLoading,
agreementType,
agreementContent,
pageTitle,
handleBack,
formatContent
}
}
}
</script>
<style lang="scss" scoped>
.agreement-page {
height: 100vh;
background-color: #f8f8f8;
overflow: hidden;
}
//
.top-bg {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 400rpx;
z-index: 0;
.bg-img {
width: 100%;
height: 100%;
}
}
//
.custom-navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 200;
.navbar-content {
position: relative;
height: 44px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
.navbar-back {
width: 80rpx;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
.back-icon {
font-size: 56rpx;
color: #333;
font-weight: 300;
}
}
.navbar-title {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.navbar-placeholder {
width: 80rpx;
}
}
}
//
.content-scroll {
position: fixed;
left: 0;
right: 0;
z-index: 1;
}
//
.agreement-content {
padding: 20rpx 30rpx 40rpx;
.content-wrapper {
background: #fff;
border-radius: 16rpx;
padding: 40rpx 30rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
.rich-content {
line-height: 1.8;
font-size: 28rpx;
color: #333;
:deep(p) {
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
}
:deep(h1), :deep(h2), :deep(h3) {
font-weight: 600;
margin: 30rpx 0 20rpx;
color: #333;
}
:deep(h1) {
font-size: 36rpx;
}
:deep(h2) {
font-size: 32rpx;
}
:deep(h3) {
font-size: 30rpx;
}
:deep(ul), :deep(ol) {
padding-left: 40rpx;
margin-bottom: 20rpx;
}
:deep(li) {
margin-bottom: 10rpx;
}
:deep(strong) {
font-weight: 600;
color: #333;
}
:deep(em) {
font-style: italic;
color: #666;
}
}
.empty-content {
text-align: center;
padding: 100rpx 0;
.empty-icon {
width: 200rpx;
height: 200rpx;
margin-bottom: 30rpx;
}
.empty-text {
font-size: 28rpx;
color: #999;
}
}
}
}
</style>

View File

@ -579,7 +579,7 @@ const handleCopyWeChat = (wechatNo) => {
const previewPhotos = (photos, index) => {
uni.previewImage({
urls: photos,
current: index
current: photos[index] || photos[0]
})
}

View File

@ -257,12 +257,12 @@ export default {
//
const handleUserAgreement = () => {
uni.showToast({ title: '功能开发中', icon: 'none' })
uni.navigateTo({ url: '/pages/agreement/index?type=user' })
}
//
const handlePrivacyPolicy = () => {
uni.showToast({ title: '功能开发中', icon: 'none' })
uni.navigateTo({ url: '/pages/agreement/index?type=privacy' })
}
// 退

View File

@ -486,7 +486,7 @@ const previewPhoto = (index) => {
const urls = displayPhotos.value.map(p => p.photoUrl)
uni.previewImage({
urls,
current: index
current: urls[index] || urls[0]
})
}

View File

@ -1276,8 +1276,8 @@ const loadProfile = async () => {
formData.introduction = profile.introduction || ''
formData.weChatNo = profile.weChatNo || ''
// - URL
const STATIC_URL = 'http://localhost:5001'
// - URL使AppApi
const STATIC_URL = 'http://localhost:5000'
if (profile.photos && profile.photos.length > 0) {
formData.photos = profile.photos.map(p => ({
id: p.id,

View File

@ -114,10 +114,10 @@ export default {
uni.showLoading({ title: '上传中...' })
try {
//
// 使AppApi
const uploadRes = await new Promise((resolve, reject) => {
uni.uploadFile({
url: `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:5001/api'}/app/upload`,
url: `${import.meta.env.VITE_API_BASE_URL || 'http://localhost:5000/api'}/app/upload`,
filePath: tempFilePath,
name: 'file',
header: {

View File

@ -4,7 +4,7 @@
*/
// 后端服务器基础地址(图片资源 - 使用AppApi因为图片存储在AppApi的wwwroot下
const IMAGE_BASE_URL = 'http://localhost:5001'
const IMAGE_BASE_URL = 'http://localhost:5000'
/**
* 获取完整的图片URL

View File

@ -61,6 +61,62 @@ public class AdminConfigController : ControllerBase
var configs = await _configService.GetAllConfigsAsync();
return ApiResponse<Dictionary<string, string>>.Success(configs);
}
/// <summary>
/// 获取用户协议
/// </summary>
[HttpGet("userAgreement")]
public async Task<ApiResponse<AgreementResponse>> GetUserAgreement()
{
var content = await _configService.GetUserAgreementAsync();
return ApiResponse<AgreementResponse>.Success(new AgreementResponse
{
Content = content ?? ""
});
}
/// <summary>
/// 设置用户协议
/// </summary>
[HttpPost("userAgreement")]
public async Task<ApiResponse> SetUserAgreement([FromBody] SetAgreementRequest request)
{
if (string.IsNullOrWhiteSpace(request.Content))
{
return ApiResponse.Error(40001, "协议内容不能为空");
}
var result = await _configService.SetUserAgreementAsync(request.Content);
return result ? ApiResponse.Success("设置成功") : ApiResponse.Error(40001, "设置失败");
}
/// <summary>
/// 获取隐私协议
/// </summary>
[HttpGet("privacyPolicy")]
public async Task<ApiResponse<AgreementResponse>> GetPrivacyPolicy()
{
var content = await _configService.GetPrivacyPolicyAsync();
return ApiResponse<AgreementResponse>.Success(new AgreementResponse
{
Content = content ?? ""
});
}
/// <summary>
/// 设置隐私协议
/// </summary>
[HttpPost("privacyPolicy")]
public async Task<ApiResponse> SetPrivacyPolicy([FromBody] SetAgreementRequest request)
{
if (string.IsNullOrWhiteSpace(request.Content))
{
return ApiResponse.Error(40001, "协议内容不能为空");
}
var result = await _configService.SetPrivacyPolicyAsync(request.Content);
return result ? ApiResponse.Success("设置成功") : ApiResponse.Error(40001, "设置失败");
}
}
/// <summary>
@ -84,3 +140,25 @@ public class SetDefaultAvatarRequest
/// </summary>
public string AvatarUrl { get; set; } = string.Empty;
}
/// <summary>
/// 协议响应
/// </summary>
public class AgreementResponse
{
/// <summary>
/// 协议内容
/// </summary>
public string Content { get; set; } = string.Empty;
}
/// <summary>
/// 设置协议请求
/// </summary>
public class SetAgreementRequest
{
/// <summary>
/// 协议内容
/// </summary>
public string Content { get; set; } = string.Empty;
}

View File

@ -0,0 +1,13 @@
{
"profiles": {
"XiangYi.AdminApi": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://localhost:5001",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -37,7 +37,7 @@
"Storage": {
"Provider": "Local",
"Local": {
"BasePath": "wwwroot/uploads",
"BasePath": "../XiangYi.AppApi/wwwroot/uploads",
"BaseUrl": "/uploads"
},
"TencentCos": {

View File

@ -14,11 +14,16 @@ namespace XiangYi.AppApi.Controllers;
public class ConfigController : ControllerBase
{
private readonly IConfigService _configService;
private readonly ISystemConfigService _systemConfigService;
private readonly ILogger<ConfigController> _logger;
public ConfigController(IConfigService configService, ILogger<ConfigController> logger)
public ConfigController(
IConfigService configService,
ISystemConfigService systemConfigService,
ILogger<ConfigController> logger)
{
_configService = configService;
_systemConfigService = systemConfigService;
_logger = logger;
}
@ -57,4 +62,45 @@ public class ConfigController : ControllerBase
var result = await _configService.GetAllPopupConfigsAsync();
return ApiResponse<List<PopupConfigResponse>>.Success(result);
}
/// <summary>
/// 获取用户协议
/// </summary>
/// <returns>用户协议内容</returns>
[HttpGet("userAgreement")]
[AllowAnonymous]
public async Task<ApiResponse<AgreementContentResponse>> GetUserAgreement()
{
var content = await _systemConfigService.GetUserAgreementAsync();
return ApiResponse<AgreementContentResponse>.Success(new AgreementContentResponse
{
Content = content ?? "暂无用户协议内容"
});
}
/// <summary>
/// 获取隐私协议
/// </summary>
/// <returns>隐私协议内容</returns>
[HttpGet("privacyPolicy")]
[AllowAnonymous]
public async Task<ApiResponse<AgreementContentResponse>> GetPrivacyPolicy()
{
var content = await _systemConfigService.GetPrivacyPolicyAsync();
return ApiResponse<AgreementContentResponse>.Success(new AgreementContentResponse
{
Content = content ?? "暂无隐私协议内容"
});
}
}
/// <summary>
/// 协议内容响应
/// </summary>
public class AgreementContentResponse
{
/// <summary>
/// 协议内容
/// </summary>
public string Content { get; set; } = string.Empty;
}

View File

@ -5,7 +5,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5001",
"applicationUrl": "http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@ -15,7 +15,7 @@
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7001;http://localhost:5001",
"applicationUrl": "https://localhost:7000;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@ -29,4 +29,24 @@ public interface ISystemConfigService
/// 获取所有配置
/// </summary>
Task<Dictionary<string, string>> GetAllConfigsAsync();
/// <summary>
/// 获取用户协议内容
/// </summary>
Task<string?> GetUserAgreementAsync();
/// <summary>
/// 设置用户协议内容
/// </summary>
Task<bool> SetUserAgreementAsync(string content);
/// <summary>
/// 获取隐私协议内容
/// </summary>
Task<string?> GetPrivacyPolicyAsync();
/// <summary>
/// 设置隐私协议内容
/// </summary>
Task<bool> SetPrivacyPolicyAsync(string content);
}

View File

@ -18,6 +18,16 @@ public class SystemConfigService : ISystemConfigService
/// </summary>
public const string DefaultAvatarKey = "default_avatar";
/// <summary>
/// 用户协议配置键
/// </summary>
public const string UserAgreementKey = "user_agreement";
/// <summary>
/// 隐私协议配置键
/// </summary>
public const string PrivacyPolicyKey = "privacy_policy";
public SystemConfigService(
IRepository<SystemConfig> configRepository,
ILogger<SystemConfigService> logger)
@ -92,4 +102,28 @@ public class SystemConfigService : ISystemConfigService
var configs = await _configRepository.GetListAsync(c => true);
return configs.ToDictionary(c => c.ConfigKey, c => c.ConfigValue);
}
/// <inheritdoc />
public async Task<string?> GetUserAgreementAsync()
{
return await GetConfigValueAsync(UserAgreementKey);
}
/// <inheritdoc />
public async Task<bool> SetUserAgreementAsync(string content)
{
return await SetConfigValueAsync(UserAgreementKey, content, "用户协议内容");
}
/// <inheritdoc />
public async Task<string?> GetPrivacyPolicyAsync()
{
return await GetConfigValueAsync(PrivacyPolicyKey);
}
/// <inheritdoc />
public async Task<bool> SetPrivacyPolicyAsync(string content)
{
return await SetConfigValueAsync(PrivacyPolicyKey, content, "隐私协议内容");
}
}