diff --git a/.kiro/specs/admin-frontend/design.md b/.kiro/specs/admin-frontend/design.md
index 40f5277..3600c44 100644
--- a/.kiro/specs/admin-frontend/design.md
+++ b/.kiro/specs/admin-frontend/design.md
@@ -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-已结算 |
diff --git a/.kiro/specs/admin-frontend/tasks.md b/.kiro/specs/admin-frontend/tasks.md
index ecfc6f5..b426bc0 100644
--- a/.kiro/specs/admin-frontend/tasks.md
+++ b/.kiro/specs/admin-frontend/tasks.md
@@ -229,7 +229,7 @@
- 实现搜索表单(规划师、用户、日期范围、状态)
- 实现分页表格(预约ID、用户信息、规划师信息、预约日期、学生信息、状态、创建时间、操作)
- 实现预约详情抽屉(完整预约信息、用户详情、规划师详情、学生信息)
- - 实现状态修改对话框(待确认/已确认/已完成/已取消)
+ - 实现状态修改对话框(待联系/已联系/已完成/已取消)
- 实现导出功能
- 使用 DictSelect 组件选择状态
- _Requirements: 13.1, 13.2, 13.3, 13.4, 13.5, 13.6_
diff --git a/docs/数据库设计文档.md b/docs/数据库设计文档.md
index 3daab4a..7840f91 100644
--- a/docs/数据库设计文档.md
+++ b/docs/数据库设计文档.md
@@ -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 | 软删除 |
diff --git a/server/MiAssessment/scripts/init_business_db.sql b/server/MiAssessment/scripts/init_business_db.sql
index 386a3fe..9e60231 100644
--- a/server/MiAssessment/scripts/init_business_db.sql
+++ b/server/MiAssessment/scripts/init_business_db.sql
@@ -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,
diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Entities/PlannerBooking.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Entities/PlannerBooking.cs
index 04c8c67..b8ad106 100644
--- a/server/MiAssessment/src/MiAssessment.Admin.Business/Entities/PlannerBooking.cs
+++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Entities/PlannerBooking.cs
@@ -127,7 +127,7 @@ public class PlannerBooking
public string? Expectation { get; set; }
///
- /// 状态:1待确认 2已确认 3已完成 4已取消
+ /// 状态:1待联系 2已联系 3已完成 4已取消
///
public int Status { get; set; } = 1;
diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/BookingDto.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/BookingDto.cs
index 51eca69..94cdc8b 100644
--- a/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/BookingDto.cs
+++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/BookingDto.cs
@@ -86,7 +86,7 @@ public class BookingDto
public string GradeName { get; set; } = null!;
///
- /// 状态:1待确认 2已确认 3已完成 4已取消
+ /// 状态:1待联系 2已联系 3已完成 4已取消
///
public int Status { get; set; }
diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/BookingQueryRequest.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/BookingQueryRequest.cs
index cec9727..39373e9 100644
--- a/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/BookingQueryRequest.cs
+++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/BookingQueryRequest.cs
@@ -26,7 +26,7 @@ public class BookingQueryRequest : PagedRequest
public DateTime? BookingDateEnd { get; set; }
///
- /// 状态:1待确认 2已确认 3已完成 4已取消
+ /// 状态:1待联系 2已联系 3已完成 4已取消
///
public int? Status { get; set; }
diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/UpdateBookingStatusRequest.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/UpdateBookingStatusRequest.cs
index e13d9ca..40249d9 100644
--- a/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/UpdateBookingStatusRequest.cs
+++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Models/Planner/UpdateBookingStatusRequest.cs
@@ -14,7 +14,7 @@ public class UpdateBookingStatusRequest
public long Id { get; set; }
///
- /// 状态:1待确认 2已确认 3已完成 4已取消
+ /// 状态:1待联系 2已联系 3已完成 4已取消
///
[Range(1, 4, ErrorMessage = "状态值无效")]
public int Status { get; set; }
diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs
index fbeeb8b..4aadb3f 100644
--- a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs
+++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/AssessmentRecordService.cs
@@ -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
{
diff --git a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/PlannerService.cs b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/PlannerService.cs
index c96c720..e721956 100644
--- a/server/MiAssessment/src/MiAssessment.Admin.Business/Services/PlannerService.cs
+++ b/server/MiAssessment/src/MiAssessment.Admin.Business/Services/PlannerService.cs
@@ -31,8 +31,8 @@ public class PlannerService : IPlannerService
///
private static readonly Dictionary BookingStatusNames = new()
{
- { 1, "待确认" },
- { 2, "已确认" },
+ { 1, "待联系" },
+ { 2, "已联系" },
{ 3, "已完成" },
{ 4, "已取消" }
};
diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/config/index.vue b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/config/index.vue
index c95060b..d5addd8 100644
--- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/config/index.vue
+++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/config/index.vue
@@ -39,8 +39,19 @@
+
+
+
+ 点击编辑上传图片
+
-
+
{{ getPlainText(row.configValue) || '点击编辑设置内容' }}
@@ -124,6 +135,29 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ 取消
+
+ 保存
+
+
+
@@ -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({
@@ -172,6 +212,8 @@ const state = reactive({
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(() => {
diff --git a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/planner/booking/index.vue b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/planner/booking/index.vue
index 7220d00..0bd58c1 100644
--- a/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/planner/booking/index.vue
+++ b/server/MiAssessment/src/MiAssessment.Admin/admin-web/src/views/business/planner/booking/index.vue
@@ -191,8 +191,8 @@ const statusForm = reactive({
// 状态类型映射
const getStatusType = (status: number): '' | 'success' | 'warning' | 'info' | 'danger' => {
const map: Record = {
- 1: 'warning', // 待确认
- 2: '', // 已确认
+ 1: 'warning', // 待联系
+ 2: '', // 已联系
3: 'success', // 已完成
4: 'info' // 已取消
}
diff --git a/server/MiAssessment/src/MiAssessment.Api/Controllers/SystemController.cs b/server/MiAssessment/src/MiAssessment.Api/Controllers/SystemController.cs
index 0d95e77..d3a36a7 100644
--- a/server/MiAssessment/src/MiAssessment.Api/Controllers/SystemController.cs
+++ b/server/MiAssessment/src/MiAssessment.Api/Controllers/SystemController.cs
@@ -111,4 +111,30 @@ public class SystemController : ControllerBase
return ApiResponse.Fail("获取关于我们失败");
}
}
+
+ ///
+ /// 获取联系我们信息
+ ///
+ ///
+ /// GET /api/system/getContactInfo
+ ///
+ /// 从配置表读取客服二维码图片URL
+ /// 不需要用户登录认证
+ ///
+ /// 联系我们信息(二维码图片URL)
+ [HttpGet("getContactInfo")]
+ [ProducesResponseType(typeof(ApiResponse), StatusCodes.Status200OK)]
+ public async Task> GetContactInfo()
+ {
+ try
+ {
+ var contact = await _systemService.GetContactInfoAsync();
+ return ApiResponse.Success(contact);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to get contact info");
+ return ApiResponse.Fail("获取联系我们信息失败");
+ }
+ }
}
diff --git a/server/MiAssessment/src/MiAssessment.Core/Interfaces/ISystemService.cs b/server/MiAssessment/src/MiAssessment.Core/Interfaces/ISystemService.cs
index 93dbfac..8641415 100644
--- a/server/MiAssessment/src/MiAssessment.Core/Interfaces/ISystemService.cs
+++ b/server/MiAssessment/src/MiAssessment.Core/Interfaces/ISystemService.cs
@@ -33,4 +33,13 @@ public interface ISystemService
///
/// 关于我们数据
Task GetAboutAsync();
+
+ ///
+ /// 获取联系我们信息
+ ///
+ ///
+ /// 从配置表读取客服二维码图片(config_key: service_qrcode)
+ ///
+ /// 联系我们数据
+ Task GetContactInfoAsync();
}
diff --git a/server/MiAssessment/src/MiAssessment.Core/Services/SystemService.cs b/server/MiAssessment/src/MiAssessment.Core/Services/SystemService.cs
index 20e5262..81576fb 100644
--- a/server/MiAssessment/src/MiAssessment.Core/Services/SystemService.cs
+++ b/server/MiAssessment/src/MiAssessment.Core/Services/SystemService.cs
@@ -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
};
}
+
+ ///
+ public async Task GetContactInfoAsync()
+ {
+ _logger.LogDebug("获取联系我们信息");
+
+ var qrcodeUrl = await _configService.GetConfigValueAsync(ServiceQrcodeKey);
+
+ _logger.LogDebug("获取联系我们完成,二维码URL: {Url}", qrcodeUrl ?? "未配置");
+
+ return new ContactDto
+ {
+ QrcodeUrl = qrcodeUrl ?? string.Empty
+ };
+ }
}
diff --git a/server/MiAssessment/src/MiAssessment.Model/Data/MiAssessmentDbContext.cs b/server/MiAssessment/src/MiAssessment.Model/Data/MiAssessmentDbContext.cs
index 0fedcd1..b9afa35 100644
--- a/server/MiAssessment/src/MiAssessment.Model/Data/MiAssessmentDbContext.cs
+++ b/server/MiAssessment/src/MiAssessment.Model/Data/MiAssessmentDbContext.cs
@@ -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("创建时间");
diff --git a/server/MiAssessment/src/MiAssessment.Model/Entities/PlannerBooking.cs b/server/MiAssessment/src/MiAssessment.Model/Entities/PlannerBooking.cs
index c6492fa..730eeed 100644
--- a/server/MiAssessment/src/MiAssessment.Model/Entities/PlannerBooking.cs
+++ b/server/MiAssessment/src/MiAssessment.Model/Entities/PlannerBooking.cs
@@ -127,7 +127,7 @@ public class PlannerBooking
public string? Expectation { get; set; }
///
- /// 状态:1待确认 2已确认 3已完成 4已取消
+ /// 状态:1待联系 2已联系 3已完成 4已取消
///
public int Status { get; set; } = 1;
diff --git a/server/MiAssessment/src/MiAssessment.Model/Models/System/ContactDto.cs b/server/MiAssessment/src/MiAssessment.Model/Models/System/ContactDto.cs
new file mode 100644
index 0000000..79b6a35
--- /dev/null
+++ b/server/MiAssessment/src/MiAssessment.Model/Models/System/ContactDto.cs
@@ -0,0 +1,12 @@
+namespace MiAssessment.Model.Models.System;
+
+///
+/// 联系我们数据传输对象
+///
+public class ContactDto
+{
+ ///
+ /// 客服二维码图片URL
+ ///
+ public string QrcodeUrl { get; set; } = string.Empty;
+}
diff --git a/uniapp/api/system.js b/uniapp/api/system.js
index 42e8879..4cb4340 100644
--- a/uniapp/api/system.js
+++ b/uniapp/api/system.js
@@ -28,8 +28,17 @@ export function getAbout() {
return get('/system/getAbout')
}
+/**
+ * 获取联系我们信息(客服二维码)
+ * @returns {Promise
@@ -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;
+ }
+ }
+}
diff --git a/uniapp/pages/planner/book/index.vue b/uniapp/pages/planner/book/index.vue
index 84dc57f..d89f81d 100644
--- a/uniapp/pages/planner/book/index.vue
+++ b/uniapp/pages/planner/book/index.vue
@@ -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 {