-
品牌标识
-
-
-
-
-
-
-
-
-
移除
-
- 侧栏展开时显示,建议 32×32 或等比透明图
-
-
-
-
-
-
-
-
-
-
移除
-
- 可选;侧栏折叠时显示,不传则使用系统 Logo
-
-
-
-
-
基础信息
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 保
- 存
-
+
+
-
-
-
- 登录设置
-
-
-
-
-
-
- 分钟
-
-
-
-
-
-
-
- 上次登录信息提示
-
-
-
-
时间:2018-10-17 12:40
-
地点:上海市
-
IP:255.255.0.0
-
-
-
- 访问设置
-
-
-
-
-
- 开启后管理后台与员工手机端均显示「部门、姓名、电话、时间」水印(请点保存持久化)
-
-
-
-
- 仅影响管理后台,数值越大水印越深(默认 10)
-
-
-
-
-
- 仅影响员工手机端,数值越大水印越深(默认 14)
-
-
- 隐私设置
-
-
- 关闭时发送日志等展示明文手机号;开启后全站列表/详情/发送日志按脱敏规则展示(请点保存持久化)
-
-
-
-
-
- 保
- 存
-
-
-
+
+
+
-
+
+
+
-
-
-
- 语音识别
-
- Paraformer异步转写(录音文件识别)专用;留空则使用官方:{{ defaultParaformerSubmitUrl }}。
- 禁止填写下方「对话」用的 compatible-mode/v1,否则会提交失败。
-
-
-
-
-
-
-
-
- 与百炼「录音文件识别」里的 model 一致(Paraformer 系列);留空则默认 paraformer-v2。
- 禁止填 qwen*、实时/口译类模型名(如
- qwen3-livetranslate-flash-realtime-2025-09-22),那些属于另一条产品线,异步文件识别用不了。
-
-
-
-
- 对话与文本总结
- OpenAI 兼容模式:用于文案总结等,需与下方模型名搭配(如兼容模式接入千问)。
-
-
-
-
-
-
-
-
-
- 备注归纳提示词
- 发给千问的 system 角色,用来约束「删掉废话、保留关键」。留空则用系统内置默认(美业健康师备注风格)。
-
-
-
-
- 保
- 存
-
-
-
+
-
+
+
+
-
-
-
- 微信支付配置
-
-
- 关闭时小程序下单使用模拟支付
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- 保 存
-
-
-
+
+
+
@@ -273,6 +73,10 @@ import {
} from '@/api/system/sysConfig'
import { getPayConfig, savePayConfig } from '@/api/extend/mallConfig'
import BusinessConfigPanel from './business-config-panel'
+import BasicConfigPanel from './basic-config-panel'
+import SecurityConfigPanel from './security-config-panel'
+import AiConfigPanel from './ai-config-panel'
+import MallPayConfigPanel from './mall-pay-config-panel'
import SmsConfigPanel from './sms-config-panel'
import { normalizeBusinessConfigFields } from '@/utils/consume-admin-policy'
import { ossUploadFile } from '@/utils/oss-upload'
@@ -281,7 +85,14 @@ import getPageTitle from '@/utils/get-page-title'
export default {
name: 'system-sysConfig',
- components: { BusinessConfigPanel, SmsConfigPanel },
+ components: {
+ BusinessConfigPanel,
+ BasicConfigPanel,
+ SecurityConfigPanel,
+ AiConfigPanel,
+ MallPayConfigPanel,
+ SmsConfigPanel
+ },
data() {
return {
defaultParaformerSubmitUrl: 'https://dashscope.aliyuncs.com/api/v1/services/audio/asr/transcription',
@@ -291,6 +102,7 @@ export default {
logoUploading: false,
logoIconUploading: false,
mallPayLoading: false,
+ mallPayReady: false,
mallPayForm: {
MchId: '',
ApiV3Key: '',
@@ -354,6 +166,7 @@ export default {
aiChatApiKey: '',
aiSummarizeSystemPrompt: '',
consumeEditableDays: 3,
+ consumeAllowCrossMonthEdit: 0,
adminConsumeVoidEditableDays: 0,
adminConsumeDeleteEditableDays: 0,
abnormalConsumeAmountTolerance: 0.01,
@@ -377,6 +190,7 @@ export default {
methods: {
// 加载会员商城支付配置(Category=MallPayment)
initMallPayConfig() {
+ this.mallPayLoading = true
getPayConfig().then(res => {
const data = (res && res.data) || {}
this.mallPayForm = {
@@ -387,7 +201,10 @@ export default {
NotifyUrl: data.notifyUrl || '',
PayEnabled: data.payEnabled === '1' || data.payEnabled === 1 ? '1' : '0'
}
- }).catch(() => { })
+ }).catch(() => { }).finally(() => {
+ this.mallPayLoading = false
+ this.mallPayReady = true
+ })
},
// 保存会员商城支付配置
saveMallPay() {
@@ -421,6 +238,9 @@ export default {
this.baseForm.commissionTierHelpSwitch = this.baseForm.commissionTierHelpSwitch == null
? 1
: (Number(this.baseForm.commissionTierHelpSwitch) === 1 ? 1 : 0)
+ this.baseForm.consumeAllowCrossMonthEdit = this.baseForm.consumeAllowCrossMonthEdit == null
+ ? 0
+ : (Number(this.baseForm.consumeAllowCrossMonthEdit) === 1 ? 1 : 0)
this.baseForm = normalizeBusinessConfigFields(this.baseForm)
this.syncSysBrand()
this.listLoading = false
@@ -493,6 +313,9 @@ export default {
payload.commissionTierHelpSwitch = payload.commissionTierHelpSwitch == null
? 1
: (Number(payload.commissionTierHelpSwitch) === 1 ? 1 : 0)
+ payload.consumeAllowCrossMonthEdit = payload.consumeAllowCrossMonthEdit == null
+ ? 0
+ : (Number(payload.consumeAllowCrossMonthEdit) === 1 ? 1 : 0)
updateSystemConfig(payload).then(res => {
this.syncSysBrand()
this.$message({
@@ -525,115 +348,23 @@ export default {
}
>>>.el-tabs--border-card>.el-tabs__content {
- padding: 30px 36px 10px 36px;
- }
-
- .saveBtn {
- width: 100px;
- }
-
- .basic-settings {
- max-width: 1100px;
- }
-
- .basic-settings>>>.el-divider {
- margin: 8px 0 20px;
- }
-
- .basic-settings>>>.el-divider:first-child {
- margin-top: 0;
- }
-
- .brand-row {
- margin-bottom: 4px;
- }
-
- .brand-row .el-form-item {
- margin-bottom: 8px;
- }
-
- .brand-row .form-tip {
- margin-top: 8px;
- margin-bottom: 0;
- min-height: 40px;
- }
-
- .form-actions {
- margin-top: 8px;
- padding-left: 120px;
- }
-
- .form-tip {
- color: #909399;
- font-size: 13px;
- line-height: 1.6;
- margin: 0 0 12px 0;
- padding-left: 0;
- }
-
- .opacity-row {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
- gap: 12px;
- }
-
- .opacity-row .form-tip {
- margin: 0;
- }
-
- .img-uploader {
- display: flex;
- align-items: center;
- gap: 12px;
- }
-
- .single-uploader ::v-deep .el-upload {
- border: 1px dashed #d9d9d9;
- border-radius: 6px;
- cursor: pointer;
- width: 80px;
- height: 80px;
- display: flex;
- align-items: center;
- justify-content: center;
+ padding: 16px 20px 10px;
+ height: calc(100% - 40px);
overflow: hidden;
-
- &:hover {
- border-color: #409EFF;
- }
}
- .single-icon {
- font-size: 22px;
- color: #8c939d;
+ >>>.el-tab-pane {
+ height: 100%;
}
- .single-img {
- width: 80px;
- height: 80px;
- object-fit: contain;
- display: block;
- }
-}
-
-/* 暗黑:覆盖本页写死的浅色上传/提示(祖先 .ncc-content-dark) */
-.ncc-content-dark .systemConfig {
- .form-tip {
- color: rgba(255, 255, 255, 0.55);
- }
-
- .single-uploader ::v-deep .el-upload {
- background-color: #262626;
- border-color: rgba(255, 255, 255, 0.18);
-
- &:hover {
- border-color: #1890ff;
- }
+ .config-tab-panel {
+ margin: 0;
+ height: 100%;
+ min-height: calc(100vh - 200px);
}
- .single-icon {
- color: rgba(255, 255, 255, 0.55);
+ .saveBtn {
+ width: 100px;
}
}
diff --git a/antis-ncc-admin/src/views/system/sysConfig/mall-pay-config-panel.vue b/antis-ncc-admin/src/views/system/sysConfig/mall-pay-config-panel.vue
new file mode 100644
index 0000000..7fd9abb
--- /dev/null
+++ b/antis-ncc-admin/src/views/system/sysConfig/mall-pay-config-panel.vue
@@ -0,0 +1,139 @@
+
+
+
+
+
+
+ 正式收款需填写商户参数并开启;关闭时小程序下单使用模拟支付。
+
+
+
+
+ {{ form.PayEnabled === '1' ? '已开启' : '已关闭(模拟支付)' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/antis-ncc-admin/src/views/system/sysConfig/security-config-panel.vue b/antis-ncc-admin/src/views/system/sysConfig/security-config-panel.vue
new file mode 100644
index 0000000..15dff46
--- /dev/null
+++ b/antis-ncc-admin/src/views/system/sysConfig/security-config-panel.vue
@@ -0,0 +1,326 @@
+
+
+
+
+
+
+ 控制账号登录互踢、会话超时与登录成功后的提示信息。
+
+
+
+
+
+
+
+
+
+
+
+
+ 分钟
+
+
+
+
+
+
+
+
+ 开启后,用户登录成功时将展示上一次登录的时间、地点与 IP 信息,便于发现异常登录。
+
+
+
+
+ {{ Number(form.lastLoginTimeSwitch) === 1 ? '已开启' : '已关闭' }}
+
+
+
+
效果预览
+
+
上次登录信息提示
+
时间:2018-10-17 12:40
+
地点:上海市
+
IP:255.255.0.0
+
+
+
+
+
+
+
+
+
+ 开启后管理后台与员工手机端均显示「部门、姓名、电话、时间」水印;调整后请点击顶部保存持久化。
+
+
+
+
+ {{ Number(form.watermarkSwitch) === 1 ? '已开启' : '已关闭' }}
+
+
+
+
+
+
+ 仅影响管理后台,数值越大越深(默认 10)
+
+
+
+
+
+ 仅影响员工手机端,数值越大越深(默认 14)
+
+
+
+
+
+
+
+
+ 关闭时发送日志等展示明文手机号;开启后全站列表、详情与发送日志按脱敏规则展示。
+
+
+
+
+ {{ Number(form.privacyMobileMask) === 1 ? '已开启' : '已关闭' }}
+
+
+
+
+
+
+
+
+
+ 开启验证后,仅允许白名单内的 IP 访问系统;每行一个 IP,支持多行配置。
+
+
+
+
+ {{ Number(form.whitelistSwitch) === 1 ? '已开启' : '已关闭' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/antis-ncc-admin/src/views/system/sysConfig/sms-config-panel.vue b/antis-ncc-admin/src/views/system/sysConfig/sms-config-panel.vue
index 19e43a4..0b1083e 100644
--- a/antis-ncc-admin/src/views/system/sysConfig/sms-config-panel.vue
+++ b/antis-ncc-admin/src/views/system/sysConfig/sms-config-panel.vue
@@ -1,151 +1,152 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 同会员同日同场景不重复发送
+
+
+
+
+ 保存短信配置
+
+
+
+
+
+
+
+
-
-
-
-
-
- {{ row.name || '无' }}
-
-
-
-
- {{ row.templateCode || '无' }}
-
-
- {{ row.signName || '无' }}
-
-
-
-
- {{ row.templateTypeName || templateTypeName(row.templateType) }}
-
-
-
-
- {{ row.sceneCode || '无' }}
-
-
-
-
- {{ row.enabled === 1 ? '启用' : '停用' }}
-
-
-
-
- {{ row.remark || '无' }}
-
-
-
- 编辑
-
- {{ row.enabled === 1 ? '停用' : '启用' }}
-
- 删除
-
-
-
-
-
+
+
+
+
+
+ {{ row.name || '无' }}
+
+
+
+
+ {{ row.templateCode || '无' }}
+
+
+ {{ row.signName || '无' }}
+
+
+
+
+ {{ row.templateTypeName || templateTypeName(row.templateType) }}
+
+
+
+
+ {{ row.sceneCode || '无' }}
+
+
+
+
+ {{ row.enabled === 1 ? '启用' : '停用' }}
+
+
+
+
+ {{ row.remark || '无' }}
+
+
+
+ 编辑
+
+ {{ row.enabled === 1 ? '停用' : '启用' }}
+
+ 删除
+
+
+
+
+
+
+
@@ -154,6 +155,7 @@
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSms/LqSmsSendLogListOutput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSms/LqSmsSendLogListOutput.cs
index aae8a58..6865d4d 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSms/LqSmsSendLogListOutput.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqSms/LqSmsSendLogListOutput.cs
@@ -15,11 +15,20 @@ namespace NCC.Extend.Entitys.Dto.LqSms
public string memberId { get; set; }
public string memberName { get; set; }
public string templateParam { get; set; }
+ ///
+ /// 发送内容摘要
+ ///
+ public string sendContent { get; set; }
public int status { get; set; }
public string statusName { get; set; }
public string providerResult { get; set; }
public DateTime? bizDate { get; set; }
public int isTest { get; set; }
public DateTime? creatorTime { get; set; }
+ public string creatorUserId { get; set; }
+ ///
+ /// 发送人姓名(BASE_USER.F_RealName,无则显示账号或系统)
+ ///
+ public string creatorUserName { get; set; }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_sms_send_log/LqSmsSendLogEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_sms_send_log/LqSmsSendLogEntity.cs
index 25776ec..0910979 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_sms_send_log/LqSmsSendLogEntity.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_sms_send_log/LqSmsSendLogEntity.cs
@@ -73,6 +73,12 @@ namespace NCC.Extend.Entitys.lq_sms_send_log
public string TemplateParam { get; set; }
///
+ /// 发送内容摘要(签名 + 模板 + 参数,便于日志查看)
+ ///
+ [SugarColumn(ColumnName = "F_SendContent", IsNullable = true)]
+ public string SendContent { get; set; }
+
+ ///
/// 发送状态(见 )
///
[SugarColumn(ColumnName = "F_Status")]
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/Helpers/BillingCustomerTypeHelper.cs b/netcore/src/Modularity/Extend/NCC.Extend/Helpers/BillingCustomerTypeHelper.cs
new file mode 100644
index 0000000..085ed66
--- /dev/null
+++ b/netcore/src/Modularity/Extend/NCC.Extend/Helpers/BillingCustomerTypeHelper.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Threading.Tasks;
+using NCC.Code;
+using NCC.Extend.Entitys.Enum;
+using NCC.Extend.Entitys.lq_khxx;
+using NCC.Extend.Entitys.lq_kd_kdjlb;
+using SqlSugar;
+
+namespace NCC.Extend.Helpers
+{
+ ///
+ /// 开单顾客类型(gjlx)解析与补全:开单时应快照会员 khlx,历史/漏写字段可从会员档案兜底。
+ ///
+ public static class BillingCustomerTypeHelper
+ {
+ ///
+ /// 取有效顾客类型编码:优先开单 gjlx,为空时用会员 khlx。
+ ///
+ public static string ResolveEffectiveGjlx(string gjlx, string memberKhlx)
+ {
+ if (!string.IsNullOrWhiteSpace(gjlx))
+ return gjlx.Trim();
+ if (!string.IsNullOrWhiteSpace(memberKhlx))
+ return memberKhlx.Trim();
+ return null;
+ }
+
+ ///
+ /// 解析为顾客类型展示名(线索/新客/散客/会员/归档);均无则返回「无」。
+ ///
+ public static string ResolveCustomerTypeName(string gjlx, string memberKhlx = null)
+ {
+ var effective = ResolveEffectiveGjlx(gjlx, memberKhlx);
+ if (string.IsNullOrEmpty(effective))
+ return "无";
+
+ if (int.TryParse(effective, out var typeValue))
+ {
+ var desc = EnumHelper.GetEnumDesc
(typeValue);
+ return string.IsNullOrEmpty(desc) ? effective : desc;
+ }
+
+ return effective;
+ }
+
+ ///
+ /// 新建/更新开单前:gjlx 为空时从会员档案 khlx 写入快照。
+ ///
+ public static async Task EnsureGjlxFromMemberAsync(ISqlSugarClient db, LqKdKdjlbEntity entity)
+ {
+ if (entity == null || !string.IsNullOrWhiteSpace(entity.Gjlx) || string.IsNullOrWhiteSpace(entity.Kdhy))
+ return;
+
+ var khlx = await db.Queryable()
+ .Where(x => x.Id == entity.Kdhy)
+ .Select(x => x.Khlx)
+ .FirstAsync();
+ if (!string.IsNullOrWhiteSpace(khlx))
+ entity.Gjlx = khlx.Trim();
+ }
+ }
+}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/Helpers/ConsumeEditableDaysHelper.cs b/netcore/src/Modularity/Extend/NCC.Extend/Helpers/ConsumeEditableDaysHelper.cs
index 2166ee4..bf294c2 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/Helpers/ConsumeEditableDaysHelper.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/Helpers/ConsumeEditableDaysHelper.cs
@@ -23,6 +23,11 @@ namespace NCC.Extend.Helpers
public const string KeyConsumeEditableDays = "consumeEditableDays";
///
+ /// 配置键:小程序端是否允许跨月修改/作废/重开单(1=允许,0=不允许)
+ ///
+ public const string KeyConsumeAllowCrossMonthEdit = "consumeAllowCrossMonthEdit";
+
+ ///
/// 配置键:管理后台作废耗卡允许天数
///
public const string KeyAdminConsumeVoidEditableDays = "adminConsumeVoidEditableDays";
@@ -63,6 +68,11 @@ namespace NCC.Extend.Helpers
public const int DefaultDays = 3;
///
+ /// 小程序端缺省:不允许跨月修改
+ ///
+ public const int DefaultConsumeAllowCrossMonthEdit = 0;
+
+ ///
/// 管理后台缺省天数:0 表示不限制
///
public const int DefaultAdminOperationEditableDays = 0;
@@ -81,12 +91,23 @@ namespace NCC.Extend.Helpers
}
///
+ /// 小程序端是否允许跨月修改/作废/重开单
+ ///
+ public static async Task GetConsumeAllowCrossMonthEditAsync(ISqlSugarClient db)
+ {
+ return await GetBoolConfigAsync(db, KeyConsumeAllowCrossMonthEdit, DefaultConsumeAllowCrossMonthEdit);
+ }
+
+ ///
/// 校验小程序端耗卡是否在允许修改/作废的时间窗口内
///
public static async Task EnsureConsumeWithinEditableWindowAsync(ISqlSugarClient db, LqXhHyhkEntity card)
{
var days = await GetConsumeEditableDaysAsync(db);
EnsureWithinEditableWindow(card, days, "耗卡发生已超过 {0} 天,不允许修改/作废");
+ var allowCrossMonth = await GetConsumeAllowCrossMonthEditAsync(db);
+ if (!allowCrossMonth)
+ EnsureSameCalendarMonth(card, "耗卡已跨月,不允许修改/作废");
}
///
@@ -178,6 +199,16 @@ namespace NCC.Extend.Helpers
throw NCCException.Oh(string.Format(messageTemplate, days));
}
+ private static void EnsureSameCalendarMonth(LqXhHyhkEntity card, string message)
+ {
+ var baseTime = card.Hksj ?? card.CreateTime;
+ if (!baseTime.HasValue)
+ return;
+ var now = DateTime.Now;
+ if (baseTime.Value.Year != now.Year || baseTime.Value.Month != now.Month)
+ throw NCCException.Oh(message);
+ }
+
private static async Task GetIntConfigWithLegacyAsync(
ISqlSugarClient db,
string key,
@@ -199,5 +230,20 @@ namespace NCC.Extend.Helpers
return defaultValue;
return int.TryParse(row.Value.Trim(), out var n) && n >= 0 ? n : defaultValue;
}
+
+ private static async Task GetBoolConfigAsync(ISqlSugarClient db, string key, int defaultValue)
+ {
+ var row = await db.Queryable()
+ .Where(x => x.Category == CategorySysConfig && SqlFunc.ToLower(x.Key) == key.ToLower())
+ .FirstAsync();
+ if (row == null || string.IsNullOrWhiteSpace(row.Value))
+ return defaultValue == 1;
+ var value = row.Value.Trim();
+ if (value == "1" || value.Equals("true", StringComparison.OrdinalIgnoreCase))
+ return true;
+ if (value == "0" || value.Equals("false", StringComparison.OrdinalIgnoreCase))
+ return false;
+ return int.TryParse(value, out var n) ? n == 1 : defaultValue == 1;
+ }
}
}
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
index 5d983fe..9b76517 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKdKdjlbService.cs
@@ -197,6 +197,7 @@ namespace NCC.Extend.LqKdKdjlb
}
var memberArchiveNoDict = await BuildMemberArchiveNoDictAsync(billingRecords.Select(x => x.kdhy));
+ var memberKhlxDict = await BuildMemberKhlxDictAsync(billingRecords.Select(x => x.kdhy));
_logger.LogInformation($"查询到 {billingRecords.Count} 条开单记录");
@@ -387,7 +388,7 @@ namespace NCC.Extend.LqKdKdjlb
CustomerArchiveNo = ResolveMemberArchiveNo(billing.kdhy, memberArchiveNoDict) ?? "无",
CustomerChannel = string.IsNullOrEmpty(billing.kdhyjdqd) ? "无" : billing.kdhyjdqd,
GoldTriangle = GetGoldTriangleName(billing.jsj),
- CustomerType = ResolveCustomerTypeName(billing.gjlx),
+ CustomerType = BillingCustomerTypeHelper.ResolveCustomerTypeName(billing.gjlx, ResolveMemberKhlx(billing.kdhy, memberKhlxDict)),
HealthCoach = "无",
PerformanceAmount = "¥0.00",
RelatedItem = ItemDisplayNameHelper.Display(exportItemNameMap, itemDetail.px, itemDetail.pxmc) ?? "无",
@@ -422,7 +423,7 @@ namespace NCC.Extend.LqKdKdjlb
CustomerArchiveNo = ResolveMemberArchiveNo(billing.kdhy, memberArchiveNoDict) ?? "无",
CustomerChannel = string.IsNullOrEmpty(billing.kdhyjdqd) ? "无" : billing.kdhyjdqd,
GoldTriangle = GetGoldTriangleName(billing.jsj),
- CustomerType = ResolveCustomerTypeName(billing.gjlx),
+ CustomerType = BillingCustomerTypeHelper.ResolveCustomerTypeName(billing.gjlx, ResolveMemberKhlx(billing.kdhy, memberKhlxDict)),
HealthCoach = healthTeacher.jksxm ?? "无",
PerformanceAmount = FormatPerformanceAmount(healthTeacher.jksyj),
RelatedItem = ItemDisplayNameHelper.Display(exportItemNameMap, itemDetail.px, itemDetail.pxmc) ?? "无",
@@ -604,6 +605,7 @@ namespace NCC.Extend.LqKdKdjlb
var MemberInfo = await _db.Queryable().FirstAsync(p => p.Id == entity.Kdhy);
output.kdhyc = MemberInfo.Khmc;
output.kdhysjh = MemberInfo.Sjh;
+ output.gjlx = BillingCustomerTypeHelper.ResolveEffectiveGjlx(entity.Gjlx, MemberInfo?.Khlx) ?? output.gjlx;
if (!string.IsNullOrEmpty(entity.ActivityId))
{
@@ -816,7 +818,7 @@ namespace NCC.Extend.LqKdKdjlb
{
var khxxMap = await _db.Queryable()
.Where(x => kdhyIds.Contains(x.Id))
- .Select(x => new { x.Id, x.Khmc, x.Sjh })
+ .Select(x => new { x.Id, x.Khmc, x.Sjh, x.Khlx })
.ToListAsync();
var khxxDict = khxxMap.ToDictionary(x => x.Id);
foreach (var item in data.list)
@@ -825,6 +827,8 @@ namespace NCC.Extend.LqKdKdjlb
{
item.kdhyc = kh.Khmc;
item.kdhysjh = kh.Sjh;
+ if (string.IsNullOrWhiteSpace(item.gjlx) && !string.IsNullOrWhiteSpace(kh.Khlx))
+ item.gjlx = kh.Khlx.Trim();
}
}
}
@@ -1348,6 +1352,7 @@ namespace NCC.Extend.LqKdKdjlb
entity.CreateTime = DateTime.Now;
entity.UpdateTime = DateTime.Now;
entity.IsEffective = StatusEnum.有效.GetHashCode();
+ await BillingCustomerTypeHelper.EnsureGjlxFromMemberAsync(_db, entity);
try
{
//开启事务
@@ -1739,6 +1744,7 @@ namespace NCC.Extend.LqKdKdjlb
var entity = input.Adapt();
entity.Id = id; // 确保ID正确设置
+ await BillingCustomerTypeHelper.EnsureGjlxFromMemberAsync(_db, entity);
try
{
//检查开单记录是否可以操作
@@ -2477,6 +2483,13 @@ namespace NCC.Extend.LqKdKdjlb
{
var batchData = historyData.Skip(batchIndex * batchSize).Take(batchSize).ToList();
+ var batchMemberIds = batchData
+ .Select(x => (string)x.F_MemberId)
+ .Where(x => !string.IsNullOrEmpty(x))
+ .Distinct()
+ .ToList();
+ var batchMemberKhlxDict = await BuildMemberKhlxDictAsync(batchMemberIds);
+
var kdjlbEntities = new List();
var pxmxEntities = new List();
@@ -2496,6 +2509,7 @@ namespace NCC.Extend.LqKdKdjlb
Kdhyc = (string)item.会员名称,
Jsj = "暂无",
Kdhysjh = (string)item.会员电话,
+ Gjlx = ResolveMemberKhlx((string)item.F_MemberId, batchMemberKhlxDict),
Kdrq = fixedDateTime,
Zdyj = Convert.ToDecimal(item.整单业绩 ?? 0),
Sfyj = Convert.ToDecimal(item.实付业绩 ?? 0),
@@ -3127,6 +3141,7 @@ namespace NCC.Extend.LqKdKdjlb
var billingIds = billingRecords.Select(x => x.id).ToList();
var memberArchiveNoDict = await BuildMemberArchiveNoDictAsync(billingRecords.Select(x => x.kdhy));
+ var memberKhlxDict = await BuildMemberKhlxDictAsync(billingRecords.Select(x => x.kdhy));
// 构建品项明细查询条件
var itemDetailsQuery = _db.Queryable()
@@ -3274,7 +3289,7 @@ namespace NCC.Extend.LqKdKdjlb
customerChannel = billing.kdhyjdqd, // 客户进店渠道
storeId = billing.djmd,
goldTriangle = billing.jsj,
- customerType = ResolveCustomerTypeName(billing.gjlx),
+ customerType = BillingCustomerTypeHelper.ResolveCustomerTypeName(billing.gjlx, ResolveMemberKhlx(billing.kdhy, memberKhlxDict)),
cooperationInstitution = billing.hgjg,
cooperationInstitutionName = billing.hgjgName,
paymentHospital = billing.fkyy, // 付款医院ID
@@ -3408,23 +3423,12 @@ namespace NCC.Extend.LqKdKdjlb
#endregion
#region 私有方法
- ///
- /// 解析开单顾客类型名称
- ///
- private static string ResolveCustomerTypeName(string gjlx)
- {
- if (string.IsNullOrEmpty(gjlx))
- {
- return "无";
- }
-
- if (int.TryParse(gjlx, out var typeValue))
- {
- var desc = EnumHelper.GetEnumDesc(typeValue);
- return string.IsNullOrEmpty(desc) ? gjlx : desc;
- }
- return gjlx;
+ private static string ResolveMemberKhlx(string memberId, Dictionary memberKhlxDict)
+ {
+ if (string.IsNullOrEmpty(memberId) || memberKhlxDict == null)
+ return null;
+ return memberKhlxDict.TryGetValue(memberId, out var khlx) ? khlx : null;
}
///
@@ -3521,6 +3525,22 @@ namespace NCC.Extend.LqKdKdjlb
return dict;
}
+ private async Task> BuildMemberKhlxDictAsync(IEnumerable memberIds)
+ {
+ var ids = memberIds.Where(x => !string.IsNullOrEmpty(x)).Distinct().ToList();
+ if (!ids.Any())
+ return new Dictionary();
+
+ var members = await _db.Queryable()
+ .Where(x => ids.Contains(x.Id))
+ .Select(x => new { x.Id, x.Khlx })
+ .ToListAsync();
+
+ return members
+ .Where(x => !string.IsNullOrEmpty(x.Id) && !string.IsNullOrWhiteSpace(x.Khlx))
+ .ToDictionary(x => x.Id, x => x.Khlx.Trim());
+ }
+
///
/// 根据开单会员字段解析客户档案号
///
diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqSmsService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqSmsService.cs
index 173ee7b..02d6335 100644
--- a/netcore/src/Modularity/Extend/NCC.Extend/LqSmsService.cs
+++ b/netcore/src/Modularity/Extend/NCC.Extend/LqSmsService.cs
@@ -21,9 +21,11 @@ using NCC.Extend.Entitys.Enum;
using NCC.Extend.Entitys.lq_khxx;
using NCC.Extend.Entitys.lq_sms_send_log;
using NCC.Extend.Entitys.lq_sms_template;
+using NCC.Extend.Entitys.lq_sms_template;
using NCC.Extend.Interfaces.BusinessOperationLog;
using NCC.Extend.Interfaces.LqSms;
using NCC.FriendlyException;
+using NCC.System.Entitys.Permission;
using NCC.System.Entitys.System;
using Newtonsoft.Json;
using SqlSugar;
@@ -683,18 +685,37 @@ namespace NCC.Extend
memberId = it.MemberId,
memberName = it.MemberName,
templateParam = it.TemplateParam,
+ sendContent = it.SendContent,
status = it.Status,
providerResult = it.ProviderResult,
bizDate = it.BizDate,
isTest = it.IsTest,
- creatorTime = it.CreatorTime
+ creatorTime = it.CreatorTime,
+ creatorUserId = it.CreatorUserId
})
.ToPagedListAsync(input.currentPage, input.pageSize);
var mask = await IsPrivacyMobileMaskEnabledAsync();
+ var userIds = data.list
+ .Select(x => x.creatorUserId)
+ .Where(x => !string.IsNullOrWhiteSpace(x))
+ .Distinct()
+ .ToList();
+ var userMap = userIds.Count == 0
+ ? new Dictionary()
+ : (await _db.Queryable()
+ .Where(u => userIds.Contains(u.Id))
+ .ToListAsync())
+ .ToDictionary(u => u.Id, u => u);
+
foreach (var item in data.list)
{
item.statusName = GetEnumDescription((SmsSendStatusEnum)item.status);
+ if (string.IsNullOrWhiteSpace(item.sendContent))
+ {
+ item.sendContent = FormatSendContent(item.signName, null, item.templateCode, item.templateParam);
+ }
+ item.creatorUserName = ResolveSenderDisplayName(item.creatorUserId, userMap);
// 默认存明文;开启隐私开关时再脱敏。历史已脱敏数据保持原样。
if (mask)
{
@@ -723,6 +744,10 @@ namespace NCC.Extend
var configMap = await LoadConfigMapAsync();
var prevent = ParseSwitch(GetMapValue(configMap, KeyPreventDuplicate), 1) == 1;
var memberId = string.IsNullOrWhiteSpace(input.memberId) ? null : input.memberId.Trim();
+ var defaultSign = GetMapValue(configMap, KeySignName);
+ var signName = string.IsNullOrWhiteSpace(template.SignName)
+ ? (string.IsNullOrWhiteSpace(defaultSign) ? DefaultSignName : defaultSign)
+ : template.SignName;
if (!skipDuplicate && prevent && !string.IsNullOrWhiteSpace(memberId))
{
@@ -735,7 +760,7 @@ namespace NCC.Extend
if (exists)
{
var skipLog = await InsertSendLogAsync(template, input, bizDate,
- (int)SmsSendStatusEnum.跳过, "同日同场景已成功发送,跳过", null);
+ (int)SmsSendStatusEnum.跳过, "同日同场景已成功发送,跳过", null, signName);
return new
{
logId = skipLog,
@@ -758,11 +783,6 @@ namespace NCC.Extend
throw NCCException.Oh("当前生日/场景发送仅支持阿里云(厂商=1),请检查短信配置");
}
- var defaultSign = GetMapValue(configMap, KeySignName);
- var signName = string.IsNullOrWhiteSpace(template.SignName)
- ? (string.IsNullOrWhiteSpace(defaultSign) ? DefaultSignName : defaultSign)
- : template.SignName;
-
var variables = input.variables ?? new Dictionary();
if (!variables.ContainsKey("name") && !string.IsNullOrWhiteSpace(input.memberName))
{
@@ -793,7 +813,7 @@ namespace NCC.Extend
var ok = string.Equals(providerResult, "OK", StringComparison.OrdinalIgnoreCase);
var status = ok ? (int)SmsSendStatusEnum.成功 : (int)SmsSendStatusEnum.失败;
- var logId = await InsertSendLogAsync(template, input, bizDate, status, providerResult, templateParam);
+ var logId = await InsertSendLogAsync(template, input, bizDate, status, providerResult, templateParam, signName);
return new
{
@@ -812,20 +832,23 @@ namespace NCC.Extend
DateTime bizDate,
int status,
string providerResult,
- string templateParam)
+ string templateParam,
+ string signName)
{
+ var resolvedSign = string.IsNullOrWhiteSpace(signName) ? template.SignName : signName;
var entity = new LqSmsSendLogEntity
{
Id = YitIdHelper.NextId().ToString(),
SceneCode = template.SceneCode,
TemplateId = template.Id,
TemplateCode = template.TemplateCode,
- SignName = template.SignName,
+ SignName = resolvedSign,
MobileMasked = string.IsNullOrWhiteSpace(input.mobile) ? string.Empty : input.mobile.Trim(),
MobileHash = HashMobile(input.mobile),
MemberId = string.IsNullOrWhiteSpace(input.memberId) ? null : input.memberId.Trim(),
MemberName = input.memberName,
TemplateParam = templateParam,
+ SendContent = FormatSendContent(resolvedSign, template.Name, template.TemplateCode, templateParam),
Status = status,
ProviderResult = providerResult,
BizDate = bizDate.Date,
@@ -1106,6 +1129,51 @@ namespace NCC.Extend
return text;
}
+ private static string FormatSendContent(string signName, string templateName, string templateCode, string templateParam)
+ {
+ var sb = new StringBuilder();
+ if (!string.IsNullOrWhiteSpace(signName))
+ {
+ sb.Append('【').Append(signName.Trim()).Append('】');
+ }
+ if (!string.IsNullOrWhiteSpace(templateName))
+ {
+ if (sb.Length > 0) sb.Append(' ');
+ sb.Append(templateName.Trim());
+ }
+ if (!string.IsNullOrWhiteSpace(templateCode))
+ {
+ if (sb.Length > 0) sb.Append(' ');
+ sb.Append('(').Append(templateCode.Trim()).Append(')');
+ }
+ if (!string.IsNullOrWhiteSpace(templateParam))
+ {
+ if (sb.Length > 0) sb.Append(':');
+ sb.Append(templateParam.Trim());
+ }
+ return sb.Length > 0 ? sb.ToString() : "无";
+ }
+
+ private static string ResolveSenderDisplayName(string creatorUserId, IDictionary userMap)
+ {
+ if (string.IsNullOrWhiteSpace(creatorUserId))
+ {
+ return "系统";
+ }
+ if (userMap != null && userMap.TryGetValue(creatorUserId, out var user) && user != null)
+ {
+ if (!string.IsNullOrWhiteSpace(user.RealName))
+ {
+ return user.RealName.Trim();
+ }
+ if (!string.IsNullOrWhiteSpace(user.Account))
+ {
+ return user.Account.Trim();
+ }
+ }
+ return "无";
+ }
+
private static string GetMapValue(Dictionary map, string key)
{
if (map == null || string.IsNullOrWhiteSpace(key))
diff --git a/netcore/src/Modularity/System/NCC.System.Entitys/Dto/System/SysConfig/SysConfigOutput.cs b/netcore/src/Modularity/System/NCC.System.Entitys/Dto/System/SysConfig/SysConfigOutput.cs
index f0635b1..80cdc81 100644
--- a/netcore/src/Modularity/System/NCC.System.Entitys/Dto/System/SysConfig/SysConfigOutput.cs
+++ b/netcore/src/Modularity/System/NCC.System.Entitys/Dto/System/SysConfig/SysConfigOutput.cs
@@ -309,6 +309,11 @@ namespace NCC.System.Entitys.Dto.System.SysConfig
public string consumeEditableDays { get; set; }
///
+ /// 小程序端是否允许跨月修改/作废/重开单(1=允许,0=不允许);关闭时即使仍在可修改天数内,耗卡月份与当前月不同也禁止操作
+ ///
+ public int? consumeAllowCrossMonthEdit { get; set; }
+
+ ///
/// 管理后台作废耗卡允许天数;0 表示不限制天数(是否显示按钮由角色按钮权限控制)
///
public string adminConsumeVoidEditableDays { get; set; }
diff --git a/绿纤uni-app/common/auth-nav.js b/绿纤uni-app/common/auth-nav.js
new file mode 100644
index 0000000..b60b0e7
--- /dev/null
+++ b/绿纤uni-app/common/auth-nav.js
@@ -0,0 +1,33 @@
+import auth from '@/common/auth.js'
+
+let redirectingToLogin = false
+
+/**
+ * 统一跳转登录页,避免多个接口同时 600 时连续 reLaunch 导致页面闪烁。
+ */
+export function redirectToLogin() {
+ if (redirectingToLogin) return
+
+ const pages = getCurrentPages()
+ const current = pages.length ? pages[pages.length - 1] : null
+ const route = current && (current.route || (current.$page && current.$page.fullPath))
+ if (route && String(route).indexOf('pages/login/login') !== -1) {
+ auth.clearLoginState()
+ return
+ }
+
+ redirectingToLogin = true
+ auth.clearLoginState()
+ uni.reLaunch({
+ url: '/pages/login/login',
+ complete: () => {
+ setTimeout(() => {
+ redirectingToLogin = false
+ }, 800)
+ }
+ })
+}
+
+export default {
+ redirectToLogin
+}
diff --git a/绿纤uni-app/pages.json b/绿纤uni-app/pages.json
index 1becfcd..8efc98c 100644
--- a/绿纤uni-app/pages.json
+++ b/绿纤uni-app/pages.json
@@ -7,25 +7,21 @@
},
"pages": [
{
- "path": "pages/home/home",
+ "path": "pages/login/login",
"style": {
- // "navigationBarTitleText": "绿纤工作台"
+ "navigationBarTitleText": "绿纤协同平台",
"navigationStyle": "custom"
}
},
{
- "path": "pages/index/index",
+ "path": "pages/home/home",
"style": {
- "navigationBarTitleText": "绿纤协同平台",
+ // "navigationBarTitleText": "绿纤工作台"
"navigationStyle": "custom"
}
},
-
-
-
-
{
- "path": "pages/login/login",
+ "path": "pages/index/index",
"style": {
"navigationBarTitleText": "绿纤协同平台",
"navigationStyle": "custom"
diff --git a/绿纤uni-app/pages/appointment-detail/appointment-detail.vue b/绿纤uni-app/pages/appointment-detail/appointment-detail.vue
index 8d0c4e6..0067a71 100644
--- a/绿纤uni-app/pages/appointment-detail/appointment-detail.vue
+++ b/绿纤uni-app/pages/appointment-detail/appointment-detail.vue
@@ -137,7 +137,7 @@
-
+
@@ -249,6 +249,11 @@
import request from '@/service/request.js'
import appointmentApi from '@/apis/modules/appointment.js'
import consumeApi from '@/apis/modules/consume.js'
+ import {
+ canConsumeMiniProgramEdit,
+ getConsumeEditablePolicy,
+ loadConsumeEditablePolicy
+ } from '@/utils/consume-editable-policy.js'
export default {
data() {
@@ -279,7 +284,8 @@
remarkSubmitting: false,
showCancelAppointmentDialog: false,
cancelAppointmentRemark: '',
- cancelSubmitting: false
+ cancelSubmitting: false,
+ consumeEditablePolicy: getConsumeEditablePolicy()
}
},
computed: {
@@ -306,8 +312,18 @@
if (s === '已取消') return 'failed';
if (s === '已完成') return 'success';
return 'pending';
+ },
+ canReopenConsume() {
+ const row = this.getRelatedConsumeRow();
+ if (!row) return false;
+ return canConsumeMiniProgramEdit(row, this.consumeEditablePolicy);
}
},
+ onShow() {
+ loadConsumeEditablePolicy().then(policy => {
+ this.consumeEditablePolicy = policy;
+ });
+ },
onLoad(options) {
this.initializePage(options);
},
@@ -386,6 +402,19 @@
return String(val);
},
+ getRelatedConsumeRow() {
+ const detail = this.appointmentDetail;
+ if (!detail) return null;
+ const list = detail.relatedConsumeList || [];
+ if (list.length > 0) {
+ return list.find(item => item && item.hksj) || list[0];
+ }
+ if (detail.consumeId && detail.consumeHksj) {
+ return { id: detail.consumeId, hksj: detail.consumeHksj };
+ }
+ return null;
+ },
+
formatOptionalTime(t) {
if (!t) return '无';
try {
diff --git a/绿纤uni-app/pages/consume-detail/consume-detail.vue b/绿纤uni-app/pages/consume-detail/consume-detail.vue
index cb2753e..84c7bea 100644
--- a/绿纤uni-app/pages/consume-detail/consume-detail.vue
+++ b/绿纤uni-app/pages/consume-detail/consume-detail.vue
@@ -178,7 +178,7 @@
-
+