32
This commit is contained in:
parent
86584798a3
commit
2f75cc611b
|
|
@ -1249,7 +1249,7 @@ interface ConfigPageState {
|
|||
| order_type | 订单类型 | 1-测评订单, 2-规划订单 |
|
||||
| order_status | 订单状态 | 1-待支付, 2-已支付, 3-已完成, 4-退款中, 5-已退款, 6-已取消 |
|
||||
| pay_type | 支付方式 | 1-微信支付 |
|
||||
| booking_status | 预约状态 | 1-待确认, 2-已确认, 3-已完成, 4-已取消 |
|
||||
| booking_status | 预约状态 | 1-待联系, 2-已联系, 3-已完成, 4-已取消 |
|
||||
| invite_code_status | 邀请码状态 | 1-未分配, 2-已分配, 3-已使用 |
|
||||
| commission_level | 佣金层级 | 1-直接下级, 2-间接下级 |
|
||||
| commission_status | 佣金状态 | 1-待结算, 2-已结算 |
|
||||
|
|
|
|||
|
|
@ -229,7 +229,7 @@
|
|||
- 实现搜索表单(规划师、用户、日期范围、状态)
|
||||
- 实现分页表格(预约ID、用户信息、规划师信息、预约日期、学生信息、状态、创建时间、操作)
|
||||
- 实现预约详情抽屉(完整预约信息、用户详情、规划师详情、学生信息)
|
||||
- 实现状态修改对话框(待确认/已确认/已完成/已取消)
|
||||
- 实现状态修改对话框(待联系/已联系/已完成/已取消)
|
||||
- 实现导出功能
|
||||
- 使用 DictSelect 组件选择状态
|
||||
- _Requirements: 13.1, 13.2, 13.3, 13.4, 13.5, 13.6_
|
||||
|
|
|
|||
|
|
@ -368,7 +368,7 @@
|
|||
| ScoreBiology | int | 否 | - | 生物成绩 |
|
||||
| ScoreGeography | int | 否 | - | 地理成绩 |
|
||||
| ScorePolitics | int | 否 | - | 政治成绩 |
|
||||
| Status | int | 是 | 1 | 状态:1待确认 2已确认 3已完成 4已取消 |
|
||||
| Status | int | 是 | 1 | 状态:1待联系 2已联系 3已完成 4已取消 |
|
||||
| CreateTime | datetime2 | 是 | GETDATE() | 创建时间 |
|
||||
| UpdateTime | datetime2 | 是 | GETDATE() | 更新时间 |
|
||||
| IsDeleted | bit | 是 | 0 | 软删除 |
|
||||
|
|
|
|||
|
|
@ -472,7 +472,7 @@ BEGIN
|
|||
[ScoreBiology] INT NULL, -- 生物成绩
|
||||
[ScoreGeography] INT NULL, -- 地理成绩
|
||||
[ScorePolitics] INT NULL, -- 政治成绩
|
||||
[Status] INT NOT NULL DEFAULT 1, -- 状态:1待确认 2已确认 3已完成 4已取消
|
||||
[Status] INT NOT NULL DEFAULT 1, -- 状态:1待联系 2已联系 3已完成 4已取消
|
||||
[CreateTime] DATETIME2 NOT NULL DEFAULT GETDATE(),
|
||||
[UpdateTime] DATETIME2 NOT NULL DEFAULT GETDATE(),
|
||||
[IsDeleted] BIT NOT NULL DEFAULT 0,
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ public class PlannerBooking
|
|||
public string? Expectation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态:1待确认 2已确认 3已完成 4已取消
|
||||
/// 状态:1待联系 2已联系 3已完成 4已取消
|
||||
/// </summary>
|
||||
public int Status { get; set; } = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ public class BookingDto
|
|||
public string GradeName { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 状态:1待确认 2已确认 3已完成 4已取消
|
||||
/// 状态:1待联系 2已联系 3已完成 4已取消
|
||||
/// </summary>
|
||||
public int Status { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ public class BookingQueryRequest : PagedRequest
|
|||
public DateTime? BookingDateEnd { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态:1待确认 2已确认 3已完成 4已取消
|
||||
/// 状态:1待联系 2已联系 3已完成 4已取消
|
||||
/// </summary>
|
||||
public int? Status { get; set; }
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ public class UpdateBookingStatusRequest
|
|||
public long Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态:1待确认 2已确认 3已完成 4已取消
|
||||
/// 状态:1待联系 2已联系 3已完成 4已取消
|
||||
/// </summary>
|
||||
[Range(1, 4, ErrorMessage = "状态值无效")]
|
||||
public int Status { get; set; }
|
||||
|
|
|
|||
|
|
@ -329,8 +329,8 @@ public class AssessmentRecordService : IAssessmentRecordService
|
|||
return (parent.Id, parent.Name);
|
||||
}
|
||||
|
||||
// 否则使用分类类型名称
|
||||
return (category.Id, GetCategoryTypeName(category.CategoryType));
|
||||
// 顶级分类按 CategoryType 分组,使用负数避免与真实ID冲突
|
||||
return ((long)-category.CategoryType, GetCategoryTypeName(category.CategoryType));
|
||||
})
|
||||
.Select(g => new ReportCategoryGroup
|
||||
{
|
||||
|
|
|
|||
|
|
@ -31,8 +31,8 @@ public class PlannerService : IPlannerService
|
|||
/// </summary>
|
||||
private static readonly Dictionary<int, string> BookingStatusNames = new()
|
||||
{
|
||||
{ 1, "待确认" },
|
||||
{ 2, "已确认" },
|
||||
{ 1, "待联系" },
|
||||
{ 2, "已联系" },
|
||||
{ 3, "已完成" },
|
||||
{ 4, "已取消" }
|
||||
};
|
||||
|
|
|
|||
|
|
@ -39,8 +39,19 @@
|
|||
|
||||
<el-table-column label="配置值" min-width="250">
|
||||
<template #default="{ row }">
|
||||
<!-- 图片类型显示预览 -->
|
||||
<template v-if="isImageConfig(row.configKey)">
|
||||
<el-image
|
||||
v-if="row.configValue"
|
||||
:src="row.configValue"
|
||||
:preview-src-list="[row.configValue]"
|
||||
fit="contain"
|
||||
style="width: 60px; height: 60px;"
|
||||
/>
|
||||
<span v-else class="config-value" style="color: #909399; font-style: italic;">点击编辑上传图片</span>
|
||||
</template>
|
||||
<!-- 富文本类型显示预览 -->
|
||||
<template v-if="isRichTextConfig(row.configKey)">
|
||||
<template v-else-if="isRichTextConfig(row.configKey)">
|
||||
<span class="config-value rich-text-preview">
|
||||
{{ getPlainText(row.configValue) || '点击编辑设置内容' }}
|
||||
</span>
|
||||
|
|
@ -124,6 +135,29 @@
|
|||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<!-- 图片配置编辑对话框 -->
|
||||
<el-dialog
|
||||
v-model="state.imageDialogVisible"
|
||||
:title="'编辑 - ' + state.editingItem?.description"
|
||||
width="500px"
|
||||
:close-on-click-modal="false"
|
||||
>
|
||||
<el-form label-width="80px">
|
||||
<el-form-item label="配置键">
|
||||
<el-input :model-value="state.editingItem?.configKey" disabled />
|
||||
</el-form-item>
|
||||
<el-form-item label="图片">
|
||||
<ImageUpload v-model="state.imageValue" placeholder="上传客服二维码图片" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button @click="handleImageCancel">取消</el-button>
|
||||
<el-button type="primary" :loading="state.saving" @click="handleImageSave">
|
||||
保存
|
||||
</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
|
@ -136,6 +170,7 @@ import { reactive, onMounted } from 'vue'
|
|||
import { Setting, Edit } from '@element-plus/icons-vue'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { RichTextEditor } from '@/components'
|
||||
import ImageUpload from '@/components/ImageUpload/index.vue'
|
||||
import {
|
||||
getConfigList,
|
||||
updateConfig,
|
||||
|
|
@ -154,6 +189,8 @@ interface ConfigPageState {
|
|||
richTextValue: string
|
||||
dialogVisible: boolean
|
||||
richTextDialogVisible: boolean
|
||||
imageDialogVisible: boolean
|
||||
imageValue: string
|
||||
saving: boolean
|
||||
validationError: string
|
||||
}
|
||||
|
|
@ -161,6 +198,9 @@ interface ConfigPageState {
|
|||
// 富文本配置键列表
|
||||
const RICH_TEXT_CONFIG_KEYS = ['user_agreement', 'privacy_policy', 'about_us_content']
|
||||
|
||||
// 图片配置键列表
|
||||
const IMAGE_CONFIG_KEYS = ['service_qrcode']
|
||||
|
||||
// ============ State ============
|
||||
|
||||
const state = reactive<ConfigPageState>({
|
||||
|
|
@ -172,6 +212,8 @@ const state = reactive<ConfigPageState>({
|
|||
richTextValue: '',
|
||||
dialogVisible: false,
|
||||
richTextDialogVisible: false,
|
||||
imageDialogVisible: false,
|
||||
imageValue: '',
|
||||
saving: false,
|
||||
validationError: ''
|
||||
})
|
||||
|
|
@ -185,6 +227,13 @@ function isRichTextConfig(configKey: string): boolean {
|
|||
return RICH_TEXT_CONFIG_KEYS.includes(configKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断是否为图片配置
|
||||
*/
|
||||
function isImageConfig(configKey: string): boolean {
|
||||
return IMAGE_CONFIG_KEYS.includes(configKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* 从 HTML 中提取纯文本用于预览
|
||||
*/
|
||||
|
|
@ -300,6 +349,10 @@ function handleEdit(item: ConfigItem) {
|
|||
// 富文本编辑
|
||||
state.richTextValue = item.configValue || ''
|
||||
state.richTextDialogVisible = true
|
||||
} else if (isImageConfig(item.configKey)) {
|
||||
// 图片编辑
|
||||
state.imageValue = item.configValue || ''
|
||||
state.imageDialogVisible = true
|
||||
} else {
|
||||
// 普通编辑
|
||||
state.editValue = item.configValue
|
||||
|
|
@ -379,6 +432,38 @@ async function handleRichTextSave() {
|
|||
}
|
||||
}
|
||||
|
||||
function handleImageCancel() {
|
||||
state.imageDialogVisible = false
|
||||
state.editingItem = null
|
||||
state.imageValue = ''
|
||||
}
|
||||
|
||||
async function handleImageSave() {
|
||||
if (!state.editingItem) return
|
||||
|
||||
state.saving = true
|
||||
|
||||
try {
|
||||
const res = await updateConfig({
|
||||
configKey: state.editingItem.configKey,
|
||||
configValue: state.imageValue
|
||||
})
|
||||
|
||||
if (res.code === 0) {
|
||||
ElMessage.success('配置更新成功')
|
||||
handleImageCancel()
|
||||
await loadConfigList()
|
||||
} else {
|
||||
throw new Error(res.message || '更新配置失败')
|
||||
}
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : '更新配置失败'
|
||||
ElMessage.error(message)
|
||||
} finally {
|
||||
state.saving = false
|
||||
}
|
||||
}
|
||||
|
||||
// ============ Lifecycle ============
|
||||
|
||||
onMounted(() => {
|
||||
|
|
|
|||
|
|
@ -191,8 +191,8 @@ const statusForm = reactive({
|
|||
// 状态类型映射
|
||||
const getStatusType = (status: number): '' | 'success' | 'warning' | 'info' | 'danger' => {
|
||||
const map: Record<number, '' | 'success' | 'warning' | 'info' | 'danger'> = {
|
||||
1: 'warning', // 待确认
|
||||
2: '', // 已确认
|
||||
1: 'warning', // 待联系
|
||||
2: '', // 已联系
|
||||
3: 'success', // 已完成
|
||||
4: 'info' // 已取消
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,4 +111,30 @@ public class SystemController : ControllerBase
|
|||
return ApiResponse<AboutDto>.Fail("获取关于我们失败");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取联系我们信息
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// GET /api/system/getContactInfo
|
||||
///
|
||||
/// 从配置表读取客服二维码图片URL
|
||||
/// 不需要用户登录认证
|
||||
/// </remarks>
|
||||
/// <returns>联系我们信息(二维码图片URL)</returns>
|
||||
[HttpGet("getContactInfo")]
|
||||
[ProducesResponseType(typeof(ApiResponse<ContactDto>), StatusCodes.Status200OK)]
|
||||
public async Task<ApiResponse<ContactDto>> GetContactInfo()
|
||||
{
|
||||
try
|
||||
{
|
||||
var contact = await _systemService.GetContactInfoAsync();
|
||||
return ApiResponse<ContactDto>.Success(contact);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Failed to get contact info");
|
||||
return ApiResponse<ContactDto>.Fail("获取联系我们信息失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,4 +33,13 @@ public interface ISystemService
|
|||
/// </remarks>
|
||||
/// <returns>关于我们数据</returns>
|
||||
Task<AboutDto> GetAboutAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 获取联系我们信息
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// 从配置表读取客服二维码图片(config_key: service_qrcode)
|
||||
/// </remarks>
|
||||
/// <returns>联系我们数据</returns>
|
||||
Task<ContactDto> GetContactInfoAsync();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ public class SystemService : ISystemService
|
|||
private const string PrivacyPolicyKey = "privacy_policy";
|
||||
private const string AboutUsKey = "about_us_content";
|
||||
private const string AppVersionKey = "app_version";
|
||||
private const string ServiceQrcodeKey = "service_qrcode";
|
||||
|
||||
// 默认版本号
|
||||
private const string DefaultVersion = "1.0.0";
|
||||
|
|
@ -87,4 +88,19 @@ public class SystemService : ISystemService
|
|||
Version = version ?? DefaultVersion
|
||||
};
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<ContactDto> GetContactInfoAsync()
|
||||
{
|
||||
_logger.LogDebug("获取联系我们信息");
|
||||
|
||||
var qrcodeUrl = await _configService.GetConfigValueAsync(ServiceQrcodeKey);
|
||||
|
||||
_logger.LogDebug("获取联系我们完成,二维码URL: {Url}", qrcodeUrl ?? "未配置");
|
||||
|
||||
return new ContactDto
|
||||
{
|
||||
QrcodeUrl = qrcodeUrl ?? string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -544,7 +544,7 @@ public partial class MiAssessmentDbContext : DbContext
|
|||
.HasComment("政治成绩");
|
||||
entity.Property(e => e.Status)
|
||||
.HasDefaultValue(1)
|
||||
.HasComment("状态:1待确认 2已确认 3已完成 4已取消");
|
||||
.HasComment("状态:1待联系 2已联系 3已完成 4已取消");
|
||||
entity.Property(e => e.CreateTime)
|
||||
.HasDefaultValueSql("(getdate())")
|
||||
.HasComment("创建时间");
|
||||
|
|
|
|||
|
|
@ -127,7 +127,7 @@ public class PlannerBooking
|
|||
public string? Expectation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 状态:1待确认 2已确认 3已完成 4已取消
|
||||
/// 状态:1待联系 2已联系 3已完成 4已取消
|
||||
/// </summary>
|
||||
public int Status { get; set; } = 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
namespace MiAssessment.Model.Models.System;
|
||||
|
||||
/// <summary>
|
||||
/// 联系我们数据传输对象
|
||||
/// </summary>
|
||||
public class ContactDto
|
||||
{
|
||||
/// <summary>
|
||||
/// 客服二维码图片URL
|
||||
/// </summary>
|
||||
public string QrcodeUrl { get; set; } = string.Empty;
|
||||
}
|
||||
|
|
@ -28,8 +28,17 @@ export function getAbout() {
|
|||
return get('/system/getAbout')
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取联系我们信息(客服二维码)
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
export function getContactInfo() {
|
||||
return get('/system/getContactInfo')
|
||||
}
|
||||
|
||||
export default {
|
||||
getAgreement,
|
||||
getPrivacy,
|
||||
getAbout
|
||||
getAbout,
|
||||
getContactInfo
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,6 +97,25 @@
|
|||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 联系我们弹窗 -->
|
||||
<view v-if="contactPopupVisible" class="popup-mask" @click="hideContactPopup">
|
||||
<view class="contact-popup" @click.stop>
|
||||
<view class="contact-popup-header">
|
||||
<text class="contact-popup-title">联系我们</text>
|
||||
<text class="contact-popup-close" @click="hideContactPopup">✕</text>
|
||||
</view>
|
||||
<view class="contact-popup-body">
|
||||
<image
|
||||
class="contact-qrcode"
|
||||
:src="contactQrcodeUrl"
|
||||
mode="aspectFit"
|
||||
@click="handleSaveQrcode"
|
||||
/>
|
||||
<text class="contact-tip">长按识别或点击预览保存二维码</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
|
@ -109,6 +128,7 @@ import { ref, computed, onMounted } from 'vue'
|
|||
import { onShow, onPullDownRefresh } from '@dcloudio/uni-app'
|
||||
import { useUserStore } from '@/store/user.js'
|
||||
import { useNavbar } from '@/composables/useNavbar.js'
|
||||
import { getContactInfo } from '@/api/system.js'
|
||||
|
||||
const userStore = useUserStore()
|
||||
const { totalNavbarHeight } = useNavbar()
|
||||
|
|
@ -116,6 +136,10 @@ const { totalNavbarHeight } = useNavbar()
|
|||
// 退出登录弹窗状态
|
||||
const logoutPopupVisible = ref(false)
|
||||
|
||||
// 联系我们弹窗状态
|
||||
const contactPopupVisible = ref(false)
|
||||
const contactQrcodeUrl = ref('')
|
||||
|
||||
// 计算属性
|
||||
const isLoggedIn = computed(() => userStore.isLoggedIn)
|
||||
const isPartner = computed(() => userStore.isPartner)
|
||||
|
|
@ -175,14 +199,38 @@ function goAssessmentHistory() {
|
|||
}
|
||||
|
||||
/**
|
||||
* 联系我们
|
||||
* 联系我们 - 弹出客服二维码弹窗
|
||||
*/
|
||||
function handleContactUs() {
|
||||
uni.showModal({
|
||||
title: '联系我们',
|
||||
content: '如有问题,请联系客服微信:xxxxxx',
|
||||
showCancel: false,
|
||||
confirmText: '我知道了'
|
||||
async function handleContactUs() {
|
||||
try {
|
||||
const res = await getContactInfo()
|
||||
if (res && res.code === 0 && res.data && res.data.qrcodeUrl) {
|
||||
contactQrcodeUrl.value = res.data.qrcodeUrl
|
||||
contactPopupVisible.value = true
|
||||
} else {
|
||||
uni.showToast({ title: '暂未配置客服信息', icon: 'none' })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取联系信息失败:', error)
|
||||
uni.showToast({ title: '获取联系信息失败', icon: 'none' })
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭联系我们弹窗
|
||||
*/
|
||||
function hideContactPopup() {
|
||||
contactPopupVisible.value = false
|
||||
}
|
||||
|
||||
/**
|
||||
* 长按保存二维码图片
|
||||
*/
|
||||
function handleSaveQrcode() {
|
||||
if (!contactQrcodeUrl.value) return
|
||||
uni.previewImage({
|
||||
urls: [contactQrcodeUrl.value],
|
||||
current: contactQrcodeUrl.value
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -472,4 +520,50 @@ onMounted(() => {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 联系我们弹窗
|
||||
.contact-popup {
|
||||
width: 560rpx;
|
||||
background-color: $bg-white;
|
||||
border-radius: $border-radius-xl;
|
||||
overflow: hidden;
|
||||
|
||||
.contact-popup-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: $spacing-xl $spacing-xl 0;
|
||||
|
||||
.contact-popup-title {
|
||||
font-size: $font-size-lg;
|
||||
font-weight: $font-weight-bold;
|
||||
color: $text-color;
|
||||
}
|
||||
|
||||
.contact-popup-close {
|
||||
font-size: $font-size-xl;
|
||||
color: $text-placeholder;
|
||||
padding: $spacing-xs;
|
||||
}
|
||||
}
|
||||
|
||||
.contact-popup-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: $spacing-xl;
|
||||
|
||||
.contact-qrcode {
|
||||
width: 400rpx;
|
||||
height: 400rpx;
|
||||
border-radius: $border-radius-md;
|
||||
}
|
||||
|
||||
.contact-tip {
|
||||
margin-top: $spacing-lg;
|
||||
font-size: $font-size-sm;
|
||||
color: $text-placeholder;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -612,6 +612,7 @@ onLoad(async (options) => {
|
|||
padding: $spacing-lg;
|
||||
padding-bottom: calc(#{$spacing-lg} + env(safe-area-inset-bottom));
|
||||
background-color: transparent;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user