From a8965d32df47cee9376900620d2cb46f77a96a5b Mon Sep 17 00:00:00 2001 From: “wangming” <“wangming@antissoft.com”> Date: Tue, 16 Jun 2026 23:15:56 +0800 Subject: [PATCH] ``` feat: 添加系统更新日志功能和沉睡会员提醒功能 --- antis-ncc-admin/src/api/extend/changelog.js | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ antis-ncc-admin/src/api/extend/lqCompanyCalendar.js | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ antis-ncc-admin/src/layout/components/NavbarRight.vue | 134 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- antis-ncc-admin/src/views/extend/companyCalendar/components/EventTypeDialog.vue | 273 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ antis-ncc-admin/src/views/extend/companyCalendar/index.vue | 619 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ antis-ncc-admin/src/views/lqChangelog/index.vue | 543 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ antis-ncc-admin/src/views/lqUploadRecord/index.vue | 51 ++++++++++++++++++++++++++++++++++++++++++++++++--- antis-ncc-admin/src/views/system/sysConfig/business-config-panel.vue | 38 ++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/Changelog/ChangelogDto.cs | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCompanyCalendar/LqCompanyCalendarDto.cs | 370 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreDashboard/SleepingMembersInput.cs | 26 ++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_changelog/LqChangelogEntity.cs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/lq_company_calendar/LqCompanyCalendarEntity.cs | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Entitys/lq_company_calendar/LqCompanyCalendarTypeEntity.cs | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqCompanyCalendar/ILqCompanyCalendarService.cs | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/ChangelogService.cs | 214 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/Helpers/ConsumeEditableDaysHelper.cs | 13 +++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/LqCompanyCalendarService.cs | 471 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs | 11 +++++++++-- netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ netcore/src/Modularity/Extend/NCC.Extend/OssDirectUploadService.cs | 2 +- netcore/src/Modularity/System/NCC.System.Entitys/Dto/System/SysConfig/SysConfigOutput.cs | 7 ++++++- package-lock.json | 234 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- package.json | 3 ++- store-pc/src/api/lqKhxx.js | 9 +++++++++ store-pc/src/api/storeDashboard.js | 27 +++++++++++++++++++++++++++ store-pc/src/components/BookingCalendarDialog.vue | 455 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------------------------- store-pc/src/components/CompanyCalendarDialog.vue | 166 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------------------------------------------------- store-pc/src/views/dashboard/index.vue | 163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------------- 项目文档相关/sql/2026-06-16/公司日历菜单配置.sql | 18 ++++++++++++++++++ 项目文档相关/sql/2026-06-16/创建公司日历事件类型表.sql | 22 ++++++++++++++++++++++ 项目文档相关/sql/2026-06-16/创建公司日历表.sql | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 32 files changed, 4522 insertions(+), 173 deletions(-) create mode 100644 antis-ncc-admin/src/api/extend/changelog.js create mode 100644 antis-ncc-admin/src/api/extend/lqCompanyCalendar.js create mode 100644 antis-ncc-admin/src/views/extend/companyCalendar/components/EventTypeDialog.vue create mode 100644 antis-ncc-admin/src/views/extend/companyCalendar/index.vue create mode 100644 antis-ncc-admin/src/views/lqChangelog/index.vue create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/Changelog/ChangelogDto.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCompanyCalendar/LqCompanyCalendarDto.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreDashboard/SleepingMembersInput.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_changelog/LqChangelogEntity.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/lq_company_calendar/LqCompanyCalendarEntity.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Entitys/lq_company_calendar/LqCompanyCalendarTypeEntity.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqCompanyCalendar/ILqCompanyCalendarService.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend/ChangelogService.cs create mode 100644 netcore/src/Modularity/Extend/NCC.Extend/LqCompanyCalendarService.cs create mode 100644 项目文档相关/sql/2026-06-16/公司日历菜单配置.sql create mode 100644 项目文档相关/sql/2026-06-16/创建公司日历事件类型表.sql create mode 100644 项目文档相关/sql/2026-06-16/创建公司日历表.sql diff --git a/antis-ncc-admin/src/api/extend/changelog.js b/antis-ncc-admin/src/api/extend/changelog.js new file mode 100644 index 0000000..48f50f9 --- /dev/null +++ b/antis-ncc-admin/src/api/extend/changelog.js @@ -0,0 +1,61 @@ +import request from '@/utils/request' + +export function getChangelogList(params) { + return request({ + url: '/api/Extend/Changelog/GetList', + method: 'get', + params + }) +} + +export function getPublishedList(params) { + return request({ + url: '/api/Extend/Changelog/GetPublishedList', + method: 'get', + params + }) +} + +export function getLatestChangelog() { + return request({ + url: '/api/Extend/Changelog/GetLatest', + method: 'get' + }) +} + +export function getChangelogDetail(id) { + return request({ + url: `/api/Extend/Changelog/GetDetail/${id}`, + method: 'get' + }) +} + +export function addChangelog(data) { + return request({ + url: '/api/Extend/Changelog/Add', + method: 'post', + data + }) +} + +export function updateChangelog(data) { + return request({ + url: '/api/Extend/Changelog/Update', + method: 'post', + data + }) +} + +export function deleteChangelog(id) { + return request({ + url: `/api/Extend/Changelog/Delete/${id}`, + method: 'post' + }) +} + +export function togglePublish(id) { + return request({ + url: `/api/Extend/Changelog/TogglePublish/${id}`, + method: 'post' + }) +} diff --git a/antis-ncc-admin/src/api/extend/lqCompanyCalendar.js b/antis-ncc-admin/src/api/extend/lqCompanyCalendar.js new file mode 100644 index 0000000..63b6f2b --- /dev/null +++ b/antis-ncc-admin/src/api/extend/lqCompanyCalendar.js @@ -0,0 +1,98 @@ +import request from '@/utils/request' + +/** + * 获取公司日历事件列表(分页) + */ +export function getCalendarEvents(data) { + return request({ + url: '/api/Extend/LqCompanyCalendar/GetCalendarEvents', + method: 'post', + data + }) +} + +/** + * 获取公司日历事件列表(不分页,用于日历展示) + */ +export function getCalendarEventList(data) { + return request({ + url: '/api/Extend/LqCompanyCalendar/GetCalendarEventList', + method: 'post', + data + }) +} + +/** + * 创建公司日历事件 + */ +export function createCalendarEvent(data) { + return request({ + url: '/api/Extend/LqCompanyCalendar/CreateCalendarEvent', + method: 'post', + data + }) +} + +/** + * 更新公司日历事件 + */ +export function updateCalendarEvent(data) { + return request({ + url: '/api/Extend/LqCompanyCalendar/UpdateCalendarEvent', + method: 'post', + data + }) +} + +/** + * 删除公司日历事件 + */ +export function deleteCalendarEvent(id) { + return request({ + url: `/api/Extend/LqCompanyCalendar/DeleteCalendarEvent/${id}`, + method: 'post' + }) +} + +/** + * 获取事件类型列表 + */ +export function getEventTypeList(data) { + return request({ + url: '/api/Extend/LqCompanyCalendar/GetEventTypeList', + method: 'post', + data + }) +} + +/** + * 创建事件类型 + */ +export function createEventType(data) { + return request({ + url: '/api/Extend/LqCompanyCalendar/CreateEventType', + method: 'post', + data + }) +} + +/** + * 更新事件类型 + */ +export function updateEventType(data) { + return request({ + url: '/api/Extend/LqCompanyCalendar/UpdateEventType', + method: 'post', + data + }) +} + +/** + * 删除事件类型 + */ +export function deleteEventType(id) { + return request({ + url: `/api/Extend/LqCompanyCalendar/DeleteEventType/${id}`, + method: 'post' + }) +} diff --git a/antis-ncc-admin/src/layout/components/NavbarRight.vue b/antis-ncc-admin/src/layout/components/NavbarRight.vue index d864474..ae648ba 100644 --- a/antis-ncc-admin/src/layout/components/NavbarRight.vue +++ b/antis-ncc-admin/src/layout/components/NavbarRight.vue @@ -50,6 +50,11 @@ + +
+ +
+
@@ -73,6 +78,32 @@ + + + +
+
暂无更新日志
+
+
+ v{{ item.version }} + + {{ getChangelogTypeText(item.type) }} + + {{ formatDate(item.publishedTime) }} +
+

{{ item.title }}

+
{{ item.content }}
+
+
+
+ 加载更多 +
+
@@ -87,6 +118,7 @@ import Settings from './settings' import UserList from './userList/UserList' import dragDialog from "@/directive/el-drag-dialog"; import ReconnectingWebSocket from 'reconnecting-websocket' +import { getPublishedList } from '@/api/extend/changelog' export default { directives: { dragDialog }, @@ -112,7 +144,12 @@ export default { visible: false, isTwinkle: false, messageCount: 0, - userList: [] + userList: [], + changelogVisible: false, + changelogLoading: false, + changelogList: [], + changelogTotal: 0, + changelogPage: 1 } }, created() { @@ -255,6 +292,45 @@ export default { }, changeTwinkle(boo) { this.isTwinkle = boo + }, + openChangelog() { + this.changelogVisible = true + this.changelogPage = 1 + this.changelogList = [] + this.loadChangelog() + }, + loadChangelog() { + this.changelogLoading = true + getPublishedList({ currentPage: this.changelogPage, pageSize: 10 }).then(res => { + if (res.data && res.data.list) { + this.changelogList = [...this.changelogList, ...res.data.list] + this.changelogTotal = res.data.pagination.total + } + this.changelogLoading = false + }).catch(() => { + this.changelogLoading = false + }) + }, + loadMoreChangelog() { + this.changelogPage++ + this.loadChangelog() + }, + getChangelogTypeText(type) { + const map = { 1: '功能更新', 2: 'Bug修复', 3: '性能优化', 4: '安全更新' } + return map[type] || '其他' + }, + getChangelogTypeTag(type) { + const map = { 1: 'primary', 2: 'danger', 3: 'success', 4: 'warning' } + return map[type] || 'info' + }, + formatDate(dateStr) { + if (!dateStr) return '' + const date = new Date(dateStr) + if (isNaN(date.getTime())) return dateStr + const y = date.getFullYear() + const m = String(date.getMonth() + 1).padStart(2, '0') + const d = String(date.getDate()).padStart(2, '0') + return `${y}-${m}-${d}` } } } @@ -377,4 +453,60 @@ export default { .twinkle { animation: blink 0.5s linear infinite; } + +.changelog-dialog { + .changelog-list { + max-height: 500px; + overflow-y: auto; + } + + .empty-tip { + text-align: center; + color: #909399; + padding: 40px 0; + } + + .changelog-item { + padding: 16px 0; + border-bottom: 1px solid #f0f0f0; + + &:last-child { + border-bottom: none; + } + + .changelog-item-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 8px; + + .changelog-time { + margin-left: auto; + color: #909399; + font-size: 12px; + } + } + + .changelog-title { + margin: 0 0 8px 0; + font-size: 15px; + color: #303133; + } + + .changelog-content { + color: #606266; + font-size: 13px; + line-height: 1.6; + white-space: pre-wrap; + background: #f5f7fa; + padding: 12px; + border-radius: 6px; + } + } + + .changelog-more { + text-align: center; + padding: 12px 0 0; + } +} \ No newline at end of file diff --git a/antis-ncc-admin/src/views/extend/companyCalendar/components/EventTypeDialog.vue b/antis-ncc-admin/src/views/extend/companyCalendar/components/EventTypeDialog.vue new file mode 100644 index 0000000..ad43846 --- /dev/null +++ b/antis-ncc-admin/src/views/extend/companyCalendar/components/EventTypeDialog.vue @@ -0,0 +1,273 @@ + + + + + diff --git a/antis-ncc-admin/src/views/extend/companyCalendar/index.vue b/antis-ncc-admin/src/views/extend/companyCalendar/index.vue new file mode 100644 index 0000000..3d60c22 --- /dev/null +++ b/antis-ncc-admin/src/views/extend/companyCalendar/index.vue @@ -0,0 +1,619 @@ + + + + + diff --git a/antis-ncc-admin/src/views/lqChangelog/index.vue b/antis-ncc-admin/src/views/lqChangelog/index.vue new file mode 100644 index 0000000..291d582 --- /dev/null +++ b/antis-ncc-admin/src/views/lqChangelog/index.vue @@ -0,0 +1,543 @@ + + + + + diff --git a/antis-ncc-admin/src/views/lqUploadRecord/index.vue b/antis-ncc-admin/src/views/lqUploadRecord/index.vue index 98f36d9..2b7c641 100644 --- a/antis-ncc-admin/src/views/lqUploadRecord/index.vue +++ b/antis-ncc-admin/src/views/lqUploadRecord/index.vue @@ -96,11 +96,22 @@ {{ scope.row.auditReason || '无' }} - - + + + + + + + @@ -258,6 +269,40 @@ export default { if (row.imageUrl) { window.open(row.imageUrl, '_blank') } + }, + formatDateTime(dateTime) { + if (!dateTime) return '-' + const date = new Date(dateTime) + if (isNaN(date.getTime())) return dateTime + const y = date.getFullYear() + const m = String(date.getMonth() + 1).padStart(2, '0') + const d = String(date.getDate()).padStart(2, '0') + const h = String(date.getHours()).padStart(2, '0') + const min = String(date.getMinutes()).padStart(2, '0') + const s = String(date.getSeconds()).padStart(2, '0') + return `${y}-${m}-${d} ${h}:${min}:${s}` + }, + getFileTypeText(fileType) { + const map = { + 'annexpic': '附件图片', + 'attendancepic': '考勤打卡', + 'workFlow': '工作流', + 'aiVoice': 'AI语音', + 'member': '会员资料', + 'store': '门店资料' + } + return map[fileType] || fileType || '未知' + }, + getFileTypeTagType(fileType) { + const map = { + 'annexpic': '', + 'attendancepic': 'success', + 'workFlow': 'warning', + 'aiVoice': 'info', + 'member': 'danger', + 'store': '' + } + return map[fileType] || 'info' } } } diff --git a/antis-ncc-admin/src/views/system/sysConfig/business-config-panel.vue b/antis-ncc-admin/src/views/system/sysConfig/business-config-panel.vue index 334d3dc..1e78119 100644 --- a/antis-ncc-admin/src/views/system/sysConfig/business-config-panel.vue +++ b/antis-ncc-admin/src/views/system/sysConfig/business-config-panel.vue @@ -70,6 +70,40 @@ /> + + + + + +
+ 门店PC端 + +
+

+ 设置沉睡会员提醒天数,超过该天数未消费的会员将在门店PC端显示在沉睡提醒列表中。 +

+ + + + + + + + +
+
@@ -152,6 +186,10 @@ export default { &--consume { color: #409EFF; } + + &--sleep { + color: #E6A23C; + } } .config-section { diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/Changelog/ChangelogDto.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/Changelog/ChangelogDto.cs new file mode 100644 index 0000000..6b91b27 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/Changelog/ChangelogDto.cs @@ -0,0 +1,151 @@ +namespace NCC.Extend.Entitys.Dto.Changelog +{ + /// + /// 更新日志列表查询输入 + /// + public class ChangelogListQueryInput + { + /// + /// 当前页码 + /// + public int currentPage { get; set; } = 1; + + /// + /// 每页条数 + /// + public int pageSize { get; set; } = 20; + + /// + /// 关键字搜索(标题、版本号) + /// + public string keyword { get; set; } + + /// + /// 是否发布 + /// + public bool? isPublished { get; set; } + + /// + /// 更新类型 + /// + public int? type { get; set; } + } + + /// + /// 更新日志列表输出 + /// + public class ChangelogListOutput + { + /// + /// 主键 ID + /// + public string id { get; set; } + + /// + /// 版本号 + /// + public string version { get; set; } + + /// + /// 更新标题 + /// + public string title { get; set; } + + /// + /// 更新内容 + /// + public string content { get; set; } + + /// + /// 更新类型(1=功能更新 2=Bug修复 3=性能优化 4=安全更新) + /// + public int type { get; set; } + + /// + /// 是否发布 + /// + public bool isPublished { get; set; } + + /// + /// 发布时间 + /// + public System.DateTime? publishedTime { get; set; } + + /// + /// 创建时间 + /// + public System.DateTime createTime { get; set; } + + /// + /// 修改时间 + /// + public System.DateTime? modifyTime { get; set; } + } + + /// + /// 更新日志添加输入 + /// + public class ChangelogAddInput + { + /// + /// 版本号 + /// + public string Version { get; set; } + + /// + /// 更新标题 + /// + public string Title { get; set; } + + /// + /// 更新内容 + /// + public string Content { get; set; } + + /// + /// 更新类型(1=功能更新 2=Bug修复 3=性能优化 4=安全更新) + /// + public int Type { get; set; } + + /// + /// 是否发布 + /// + public bool IsPublished { get; set; } + } + + /// + /// 更新日志修改输入 + /// + public class ChangelogUpdateInput + { + /// + /// 主键 ID + /// + public string Id { get; set; } + + /// + /// 版本号 + /// + public string Version { get; set; } + + /// + /// 更新标题 + /// + public string Title { get; set; } + + /// + /// 更新内容 + /// + public string Content { get; set; } + + /// + /// 更新类型 + /// + public int Type { get; set; } + + /// + /// 是否发布 + /// + public bool IsPublished { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCompanyCalendar/LqCompanyCalendarDto.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCompanyCalendar/LqCompanyCalendarDto.cs new file mode 100644 index 0000000..1312ecc --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqCompanyCalendar/LqCompanyCalendarDto.cs @@ -0,0 +1,370 @@ +using System; +using System.Collections.Generic; + +namespace NCC.Extend.Entitys.Dto.LqCompanyCalendar +{ + /// + /// 公司日历查询输入 + /// + public class LqCompanyCalendarQueryInput + { + /// + /// 开始时间 + /// + public DateTime? StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime? EndTime { get; set; } + + /// + /// 事件类型 + /// + public string EventType { get; set; } + + /// + /// 门店ID + /// + public string StoreId { get; set; } + + /// + /// 当前页码 + /// + public int CurrentPage { get; set; } = 1; + + /// + /// 每页条数 + /// + public int PageSize { get; set; } = 20; + } + + /// + /// 公司日历创建输入 + /// + public class LqCompanyCalendarCreateInput + { + /// + /// 事件标题 + /// + public string Title { get; set; } + + /// + /// 事件类型编码 + /// + public string EventType { get; set; } + + /// + /// 开始时间 + /// + public DateTime StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime EndTime { get; set; } + + /// + /// 事件描述 + /// + public string Description { get; set; } + + /// + /// 门店ID + /// + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } + + /// + /// 附件列表JSON + /// + public string Attachments { get; set; } + } + + /// + /// 公司日历更新输入 + /// + public class LqCompanyCalendarUpdateInput + { + /// + /// 主键ID + /// + public string Id { get; set; } + + /// + /// 事件标题 + /// + public string Title { get; set; } + + /// + /// 事件类型编码 + /// + public string EventType { get; set; } + + /// + /// 开始时间 + /// + public DateTime StartTime { get; set; } + + /// + /// 结束时间 + /// + public DateTime EndTime { get; set; } + + /// + /// 事件描述 + /// + public string Description { get; set; } + + /// + /// 门店ID + /// + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + public string StoreName { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } + + /// + /// 附件列表JSON + /// + public string Attachments { get; set; } + } + + /// + /// 附件项 + /// + public class AttachmentItem + { + /// + /// 附件名称 + /// + public string Name { get; set; } + + /// + /// 附件URL + /// + public string Url { get; set; } + + /// + /// 文件名 + /// + public string FileName { get; set; } + } + + /// + /// 公司日历输出 + /// + public class LqCompanyCalendarOutput + { + /// + /// 主键ID + /// + public string Id { get; set; } + + /// + /// 事件标题 + /// + public string Title { get; set; } + + /// + /// 事件类型编码 + /// + public string EventType { get; set; } + + /// + /// 事件类型名称 + /// + public string EventTypeName { get; set; } + + /// + /// 事件类型颜色 + /// + public string EventTypeColor { get; set; } + + /// + /// 日期字符串(YYYY-MM-DD) + /// + public string Date { get; set; } + + /// + /// 开始时间(ISO格式) + /// + public string Start { get; set; } + + /// + /// 结束时间(ISO格式) + /// + public string End { get; set; } + + /// + /// 事件描述 + /// + public string Desc { get; set; } + + /// + /// 门店ID + /// + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + public string Store { get; set; } + + /// + /// 备注 + /// + public string Remark { get; set; } + + /// + /// 附件JSON + /// + public string Attachments { get; set; } + + /// + /// 创建人 + /// + public string CreateUser { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } + } + + /// + /// 公司日历分页输出 + /// + public class LqCompanyCalendarPageOutput + { + /// + /// 列表 + /// + public List List { get; set; } + + /// + /// 总数 + /// + public int Total { get; set; } + } + + /// + /// 事件类型查询输入 + /// + public class LqCompanyCalendarTypeQueryInput + { + /// + /// 类型名称 + /// + public string TypeName { get; set; } + } + + /// + /// 事件类型创建输入 + /// + public class LqCompanyCalendarTypeCreateInput + { + /// + /// 类型名称 + /// + public string TypeName { get; set; } + + /// + /// 类型编码 + /// + public string TypeCode { get; set; } + + /// + /// 颜色 + /// + public string Color { get; set; } + + /// + /// 排序码 + /// + public int SortCode { get; set; } + } + + /// + /// 事件类型更新输入 + /// + public class LqCompanyCalendarTypeUpdateInput + { + /// + /// 主键ID + /// + public string Id { get; set; } + + /// + /// 类型名称 + /// + public string TypeName { get; set; } + + /// + /// 类型编码 + /// + public string TypeCode { get; set; } + + /// + /// 颜色 + /// + public string Color { get; set; } + + /// + /// 排序码 + /// + public int SortCode { get; set; } + } + + /// + /// 事件类型输出 + /// + public class LqCompanyCalendarTypeOutput + { + /// + /// 主键ID + /// + public string Id { get; set; } + + /// + /// 类型名称 + /// + public string TypeName { get; set; } + + /// + /// 类型编码 + /// + public string TypeCode { get; set; } + + /// + /// 颜色 + /// + public string Color { get; set; } + + /// + /// 排序码 + /// + public int SortCode { get; set; } + + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreDashboard/SleepingMembersInput.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreDashboard/SleepingMembersInput.cs new file mode 100644 index 0000000..d4e6e96 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreDashboard/SleepingMembersInput.cs @@ -0,0 +1,26 @@ +using System.ComponentModel.DataAnnotations; + +namespace NCC.Extend.Entitys.Dto.LqStoreDashboard +{ + /// + /// 沉睡会员查询输入 + /// + public class SleepingMembersInput + { + /// + /// 门店ID(必填) + /// + [Required(ErrorMessage = "门店ID不能为空")] + public string StoreId { get; set; } + + /// + /// 页码(从1开始,默认1) + /// + public int Page { get; set; } = 1; + + /// + /// 每页数量(默认20) + /// + public int PageSize { get; set; } = 20; + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_changelog/LqChangelogEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_changelog/LqChangelogEntity.cs new file mode 100644 index 0000000..7427d52 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_changelog/LqChangelogEntity.cs @@ -0,0 +1,72 @@ +using SqlSugar; +using System; + +namespace NCC.Extend.Entitys.lq_changelog +{ + /// + /// 系统更新日志 + /// + [SugarTable("lq_changelog")] + public class LqChangelogEntity + { + /// + /// 主键 ID + /// + [SugarColumn(ColumnName = "F_Id", IsPrimaryKey = true)] + public string Id { get; set; } + + /// + /// 版本号(如 3.2.1) + /// + [SugarColumn(ColumnName = "F_Version", ColumnDataType = "varchar(50)")] + public string Version { get; set; } + + /// + /// 更新标题 + /// + [SugarColumn(ColumnName = "F_Title", ColumnDataType = "varchar(200)")] + public string Title { get; set; } + + /// + /// 更新内容(支持富文本) + /// + [SugarColumn(ColumnName = "F_Content", ColumnDataType = "text")] + public string Content { get; set; } + + /// + /// 更新类型(1=功能更新 2=Bug修复 3=性能优化 4=安全更新) + /// + [SugarColumn(ColumnName = "F_Type")] + public int Type { get; set; } + + /// + /// 是否发布(true=发布 false=草稿) + /// + [SugarColumn(ColumnName = "F_IsPublished")] + public bool IsPublished { get; set; } + + /// + /// 发布时间 + /// + [SugarColumn(ColumnName = "F_PublishedTime", IsNullable = true)] + public DateTime? PublishedTime { get; set; } + + /// + /// 创建人 ID + /// + [SugarColumn(ColumnName = "F_CreateUserId", ColumnDataType = "varchar(50)", IsNullable = true)] + public string CreateUserId { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "F_CreateTime")] + public DateTime CreateTime { get; set; } + + /// + /// 修改时间 + /// + [SugarColumn(ColumnName = "F_ModifyTime", IsNullable = true)] + public DateTime? ModifyTime { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/lq_company_calendar/LqCompanyCalendarEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/lq_company_calendar/LqCompanyCalendarEntity.cs new file mode 100644 index 0000000..9c1773b --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/lq_company_calendar/LqCompanyCalendarEntity.cs @@ -0,0 +1,102 @@ +using System; +using SqlSugar; + +namespace NCC.Extend.Entitys.lq_company_calendar +{ + /// + /// 公司日历事件表 + /// + [SugarTable("lq_company_calendar")] + public class LqCompanyCalendarEntity + { + /// + /// 主键ID + /// + [SugarColumn(IsPrimaryKey = true, ColumnName = "F_Id")] + public string Id { get; set; } + + /// + /// 事件标题 + /// + [SugarColumn(ColumnName = "Title")] + public string Title { get; set; } + + /// + /// 事件类型ID + /// + [SugarColumn(ColumnName = "EventType")] + public string EventType { get; set; } + + /// + /// 开始时间 + /// + [SugarColumn(ColumnName = "StartTime")] + public DateTime StartTime { get; set; } + + /// + /// 结束时间 + /// + [SugarColumn(ColumnName = "EndTime")] + public DateTime EndTime { get; set; } + + /// + /// 事件描述 + /// + [SugarColumn(ColumnName = "Description", ColumnDataType = "text", IsNullable = true)] + public string Description { get; set; } + + /// + /// 门店ID + /// + [SugarColumn(ColumnName = "StoreId", IsNullable = true)] + public string StoreId { get; set; } + + /// + /// 门店名称 + /// + [SugarColumn(ColumnName = "StoreName", IsNullable = true)] + public string StoreName { get; set; } + + /// + /// 备注 + /// + [SugarColumn(ColumnName = "Remark", ColumnDataType = "text", IsNullable = true)] + public string Remark { get; set; } + + /// + /// 附件JSON + /// + [SugarColumn(ColumnName = "Attachments", ColumnDataType = "text", IsNullable = true)] + public string Attachments { get; set; } + + /// + /// 是否有效:1-有效, 0-无效 + /// + [SugarColumn(ColumnName = "IsEffective")] + public int IsEffective { get; set; } = 1; + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "CreateUser", IsNullable = true)] + public string CreateUser { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "CreateTime")] + public DateTime CreateTime { get; set; } + + /// + /// 修改人 + /// + [SugarColumn(ColumnName = "UpdateUser", IsNullable = true)] + public string UpdateUser { get; set; } + + /// + /// 修改时间 + /// + [SugarColumn(ColumnName = "UpdateTime", IsNullable = true)] + public DateTime? UpdateTime { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Entitys/lq_company_calendar/LqCompanyCalendarTypeEntity.cs b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/lq_company_calendar/LqCompanyCalendarTypeEntity.cs new file mode 100644 index 0000000..d65637a --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Entitys/lq_company_calendar/LqCompanyCalendarTypeEntity.cs @@ -0,0 +1,72 @@ +using System; +using SqlSugar; + +namespace NCC.Extend.Entitys.lq_company_calendar +{ + /// + /// 公司日历事件类型配置表 + /// + [SugarTable("lq_company_calendar_type")] + public class LqCompanyCalendarTypeEntity + { + /// + /// 主键ID + /// + [SugarColumn(IsPrimaryKey = true, ColumnName = "F_Id")] + public string Id { get; set; } + + /// + /// 类型名称 + /// + [SugarColumn(ColumnName = "TypeName")] + public string TypeName { get; set; } + + /// + /// 类型编码 + /// + [SugarColumn(ColumnName = "TypeCode")] + public string TypeCode { get; set; } + + /// + /// 颜色 + /// + [SugarColumn(ColumnName = "Color")] + public string Color { get; set; } + + /// + /// 排序码 + /// + [SugarColumn(ColumnName = "SortCode")] + public int SortCode { get; set; } + + /// + /// 是否有效:1-有效, 0-无效 + /// + [SugarColumn(ColumnName = "IsEffective")] + public int IsEffective { get; set; } = 1; + + /// + /// 创建人 + /// + [SugarColumn(ColumnName = "CreateUser", IsNullable = true)] + public string CreateUser { get; set; } + + /// + /// 创建时间 + /// + [SugarColumn(ColumnName = "CreateTime")] + public DateTime CreateTime { get; set; } + + /// + /// 修改人 + /// + [SugarColumn(ColumnName = "UpdateUser", IsNullable = true)] + public string UpdateUser { get; set; } + + /// + /// 修改时间 + /// + [SugarColumn(ColumnName = "UpdateTime", IsNullable = true)] + public DateTime? UpdateTime { get; set; } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqCompanyCalendar/ILqCompanyCalendarService.cs b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqCompanyCalendar/ILqCompanyCalendarService.cs new file mode 100644 index 0000000..3c2f948 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend.Interfaces/LqCompanyCalendar/ILqCompanyCalendarService.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using NCC.Extend.Entitys.Dto.LqCompanyCalendar; + +namespace NCC.Extend.Interfaces.LqCompanyCalendar +{ + /// + /// 公司日历服务接口 + /// + public interface ILqCompanyCalendarService + { + #region 事件类型 + + /// + /// 获取事件类型列表 + /// + Task> GetEventTypeListAsync(LqCompanyCalendarTypeQueryInput input); + + /// + /// 创建事件类型 + /// + Task CreateEventTypeAsync(LqCompanyCalendarTypeCreateInput input); + + /// + /// 更新事件类型 + /// + Task UpdateEventTypeAsync(LqCompanyCalendarTypeUpdateInput input); + + /// + /// 删除事件类型 + /// + Task DeleteEventTypeAsync(string id); + + #endregion + + #region 事件 + + /// + /// 获取公司日历事件列表(分页) + /// + Task GetCalendarEventsAsync(LqCompanyCalendarQueryInput input); + + /// + /// 获取公司日历事件列表(不分页,用于日历展示) + /// + Task> GetCalendarEventListAsync(LqCompanyCalendarQueryInput input); + + /// + /// 创建公司日历事件 + /// + Task CreateCalendarEventAsync(LqCompanyCalendarCreateInput input); + + /// + /// 更新公司日历事件 + /// + Task UpdateCalendarEventAsync(LqCompanyCalendarUpdateInput input); + + /// + /// 删除公司日历事件 + /// + Task DeleteCalendarEventAsync(string id); + + #endregion + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend/ChangelogService.cs b/netcore/src/Modularity/Extend/NCC.Extend/ChangelogService.cs new file mode 100644 index 0000000..d0a80fb --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/ChangelogService.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using NCC.Common.Core.Manager; +using NCC.Common.Filter; +using NCC.DynamicApiController; +using NCC.Extend.Entitys.Dto.Changelog; +using NCC.Extend.Entitys.lq_changelog; +using NCC.Dependency; +using SqlSugar; +using Yitter.IdGenerator; + +namespace NCC.Extend +{ + /// + /// 系统更新日志服务 + /// + [ApiDescriptionSettings(Tag = "Common", Name = "Changelog", Order = 170)] + [Route("api/Extend/[controller]")] + public class ChangelogService : IDynamicApiController, ITransient + { + private readonly ISqlSugarClient _db; + private readonly IUserManager _userManager; + + public ChangelogService(ISqlSugarClient db, IUserManager userManager) + { + _db = db; + _userManager = userManager; + _db.CodeFirst.InitTables(typeof(LqChangelogEntity)); + } + + /// + /// 获取更新日志列表(分页) + /// + [HttpGet("GetList")] + public async Task GetList([FromQuery] ChangelogListQueryInput input) + { + var data = await _db.Queryable() + .WhereIF(input.isPublished.HasValue, x => x.IsPublished == input.isPublished.Value) + .WhereIF(input.type.HasValue, x => x.Type == input.type.Value) + .WhereIF(!string.IsNullOrWhiteSpace(input.keyword), + x => x.Title.Contains(input.keyword) || x.Version.Contains(input.keyword)) + .OrderBy(x => x.PublishedTime, OrderByType.Desc) + .OrderBy(x => x.CreateTime, OrderByType.Desc) + .Select(x => new ChangelogListOutput + { + id = x.Id, + version = x.Version, + title = x.Title, + content = x.Content, + type = x.Type, + isPublished = x.IsPublished, + publishedTime = x.PublishedTime, + createTime = x.CreateTime, + modifyTime = x.ModifyTime + }) + .ToPagedListAsync(input.currentPage, input.pageSize); + + return PageResult.SqlSugarPageResult(data); + } + + /// + /// 获取已发布的更新日志列表(前端用) + /// + [HttpGet("GetPublishedList")] + public async Task GetPublishedList([FromQuery] int currentPage = 1, int pageSize = 20) + { + var data = await _db.Queryable() + .Where(x => x.IsPublished) + .OrderBy(x => x.PublishedTime, OrderByType.Desc) + .Select(x => new ChangelogListOutput + { + id = x.Id, + version = x.Version, + title = x.Title, + content = x.Content, + type = x.Type, + isPublished = x.IsPublished, + publishedTime = x.PublishedTime, + createTime = x.CreateTime, + modifyTime = x.ModifyTime + }) + .ToPagedListAsync(currentPage, pageSize); + + return PageResult.SqlSugarPageResult(data); + } + + /// + /// 获取最新一条已发布更新日志(右上角红点提示用) + /// + [HttpGet("GetLatest")] + public async Task GetLatest() + { + var latest = await _db.Queryable() + .Where(x => x.IsPublished) + .OrderBy(x => x.PublishedTime, OrderByType.Desc) + .FirstAsync(); + + return latest; + } + + /// + /// 获取更新日志详情 + /// + [HttpGet("GetDetail/{id}")] + public async Task GetDetail(string id) + { + var entity = await _db.Queryable() + .Where(x => x.Id == id) + .FirstAsync(); + + if (entity == null) + throw new Exception("更新日志不存在"); + + return entity; + } + + /// + /// 添加更新日志 + /// + [HttpPost("Add")] + public async Task Add([FromBody] ChangelogAddInput input) + { + var entity = new LqChangelogEntity + { + Id = YitIdHelper.NextId().ToString(), + Version = input.Version, + Title = input.Title, + Content = input.Content, + Type = input.Type, + IsPublished = input.IsPublished, + PublishedTime = input.IsPublished ? DateTime.Now : null, + CreateUserId = _userManager?.UserId, + CreateTime = DateTime.Now + }; + + await _db.Insertable(entity).ExecuteCommandAsync(); + + return new { success = true, id = entity.Id }; + } + + /// + /// 修改更新日志 + /// + [HttpPost("Update")] + public async Task Update([FromBody] ChangelogUpdateInput input) + { + var entity = await _db.Queryable() + .Where(x => x.Id == input.Id) + .FirstAsync(); + + if (entity == null) + throw new Exception("更新日志不存在"); + + entity.Version = input.Version ?? entity.Version; + entity.Title = input.Title ?? entity.Title; + entity.Content = input.Content ?? entity.Content; + entity.Type = input.Type; + entity.ModifyTime = DateTime.Now; + + if (input.IsPublished && !entity.IsPublished) + { + entity.IsPublished = true; + entity.PublishedTime = DateTime.Now; + } + else if (!input.IsPublished) + { + entity.IsPublished = false; + entity.PublishedTime = null; + } + + await _db.Updateable(entity).ExecuteCommandAsync(); + + return new { success = true }; + } + + /// + /// 删除更新日志 + /// + [HttpPost("Delete/{id}")] + public async Task Delete(string id) + { + await _db.Deleteable(id).ExecuteCommandAsync(); + + return new { success = true }; + } + + /// + /// 切换发布状态 + /// + [HttpPost("TogglePublish/{id}")] + public async Task TogglePublish(string id) + { + var entity = await _db.Queryable() + .Where(x => x.Id == id) + .FirstAsync(); + + if (entity == null) + throw new Exception("更新日志不存在"); + + entity.IsPublished = !entity.IsPublished; + entity.PublishedTime = entity.IsPublished ? DateTime.Now : null; + entity.ModifyTime = DateTime.Now; + + await _db.Updateable(entity) + .UpdateColumns(x => new { x.IsPublished, x.PublishedTime, x.ModifyTime }) + .ExecuteCommandAsync(); + + return new { success = true, isPublished = entity.IsPublished }; + } + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend/Helpers/ConsumeEditableDaysHelper.cs b/netcore/src/Modularity/Extend/NCC.Extend/Helpers/ConsumeEditableDaysHelper.cs index 83c1ab6..92d6b2e 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/Helpers/ConsumeEditableDaysHelper.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/Helpers/ConsumeEditableDaysHelper.cs @@ -33,6 +33,11 @@ namespace NCC.Extend.Helpers public const string KeyAdminConsumeDeleteEditableDays = "adminConsumeDeleteEditableDays"; /// + /// 配置键:沉睡提醒天数(门店PC),超过该天数未消费的会员将显示在沉睡提醒列表中 + /// + public const string KeySleepRemindDays = "sleepRemindDays"; + + /// /// 已废弃:合并配置键(读取天数时兼容) /// public const string KeyAdminConsumeCancelEditableDaysLegacy = "adminConsumeCancelEditableDays"; @@ -110,6 +115,14 @@ namespace NCC.Extend.Helpers DefaultAdminOperationEditableDays); } + /// + /// 沉睡提醒天数;默认 30 + /// + public static async Task GetSleepRemindDaysAsync(ISqlSugarClient db) + { + return await GetIntConfigAsync(db, KeySleepRemindDays, 30); + } + private static void EnsureWithinEditableWindow(LqXhHyhkEntity card, int days, string messageTemplate) { var baseTime = card.Hksj ?? card.CreateTime; diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqCompanyCalendarService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqCompanyCalendarService.cs new file mode 100644 index 0000000..3bb36c5 --- /dev/null +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqCompanyCalendarService.cs @@ -0,0 +1,471 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using NCC.Common.Core.Manager; +using NCC.Common.Enum; +using NCC.Dependency; +using NCC.DynamicApiController; +using NCC.Extend.Entitys.Dto.LqCompanyCalendar; +using NCC.Extend.Entitys.Enum; +using NCC.Extend.Entitys.lq_company_calendar; +using NCC.Extend.Interfaces.LqCompanyCalendar; +using NCC.FriendlyException; +using SqlSugar; +using Yitter.IdGenerator; + +namespace NCC.Extend +{ + /// + /// 公司日历服务 + /// + [ApiDescriptionSettings(Tag = "绿纤公司日历管理", Name = "LqCompanyCalendar", Order = 200)] + [Route("api/Extend/LqCompanyCalendar")] + public class LqCompanyCalendarService : IDynamicApiController, ITransient, ILqCompanyCalendarService + { + private readonly IUserManager _userManager; + private readonly ILogger _logger; + private readonly ISqlSugarClient _db; + + /// + /// 构造函数 + /// + public LqCompanyCalendarService(IUserManager userManager, ILogger logger, ISqlSugarClient db) + { + _userManager = userManager; + _logger = logger; + _db = db; + } + + #region 事件类型管理 + + /// + /// 获取事件类型列表 + /// + [HttpPost("GetEventTypeList")] + public async Task> GetEventTypeListAsync([FromBody] LqCompanyCalendarTypeQueryInput input) + { + try + { + var query = _db.Queryable() + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()); + + if (!string.IsNullOrWhiteSpace(input?.TypeName)) + { + query = query.Where(x => x.TypeName.Contains(input.TypeName)); + } + + var list = await query.OrderBy(x => x.SortCode).ToListAsync(); + + return list.Select(x => new LqCompanyCalendarTypeOutput + { + Id = x.Id, + TypeName = x.TypeName, + TypeCode = x.TypeCode, + Color = x.Color, + SortCode = x.SortCode, + CreateTime = x.CreateTime + }).ToList(); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取事件类型列表失败"); + throw NCCException.Oh($"获取失败:{ex.Message}"); + } + } + + /// + /// 创建事件类型 + /// + [HttpPost("CreateEventType")] + public async Task CreateEventTypeAsync([FromBody] LqCompanyCalendarTypeCreateInput input) + { + try + { + var exists = await _db.Queryable() + .Where(x => x.TypeCode == input.TypeCode && x.IsEffective == StatusEnum.有效.GetHashCode()) + .AnyAsync(); + + if (exists) + { + throw NCCException.Oh("类型编码已存在"); + } + + var id = YitIdHelper.NextId().ToString(); + var entity = new LqCompanyCalendarTypeEntity + { + Id = id, + TypeName = input.TypeName, + TypeCode = input.TypeCode, + Color = input.Color, + SortCode = input.SortCode, + IsEffective = StatusEnum.有效.GetHashCode(), + CreateUser = _userManager.UserId, + CreateTime = DateTime.Now + }; + + await _db.Insertable(entity).ExecuteCommandAsync(); + + return id; + } + catch (Exception ex) + { + _logger.LogError(ex, "创建事件类型失败"); + throw NCCException.Oh($"创建失败:{ex.Message}"); + } + } + + /// + /// 更新事件类型 + /// + [HttpPost("UpdateEventType")] + public async Task UpdateEventTypeAsync([FromBody] LqCompanyCalendarTypeUpdateInput input) + { + try + { + var entity = await _db.Queryable() + .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + + if (entity == null) + { + throw NCCException.Oh("事件类型不存在或已失效"); + } + + var codeExists = await _db.Queryable() + .Where(x => x.TypeCode == input.TypeCode && x.Id != input.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .AnyAsync(); + + if (codeExists) + { + throw NCCException.Oh("类型编码已存在"); + } + + entity.TypeName = input.TypeName; + entity.TypeCode = input.TypeCode; + entity.Color = input.Color; + entity.SortCode = input.SortCode; + entity.UpdateUser = _userManager.UserId; + entity.UpdateTime = DateTime.Now; + + await _db.Updateable(entity).ExecuteCommandAsync(); + + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "更新事件类型失败"); + throw NCCException.Oh($"更新失败:{ex.Message}"); + } + } + + /// + /// 删除事件类型 + /// + [HttpPost("DeleteEventType/{id}")] + public async Task DeleteEventTypeAsync(string id) + { + try + { + var hasEvents = await _db.Queryable() + .Where(x => x.EventType == id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .AnyAsync(); + + if (hasEvents) + { + throw NCCException.Oh("该类型下存在事件,无法删除"); + } + + var entity = await _db.Queryable() + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + + if (entity == null) + { + throw NCCException.Oh("事件类型不存在或已失效"); + } + + entity.IsEffective = StatusEnum.无效.GetHashCode(); + entity.UpdateUser = _userManager.UserId; + entity.UpdateTime = DateTime.Now; + + await _db.Updateable(entity).ExecuteCommandAsync(); + + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "删除事件类型失败"); + throw NCCException.Oh($"删除失败:{ex.Message}"); + } + } + + #endregion + + #region 事件管理 + + /// + /// 获取公司日历事件列表(分页) + /// + [HttpPost("GetCalendarEvents")] + public async Task GetCalendarEventsAsync([FromBody] LqCompanyCalendarQueryInput input) + { + try + { + var page = input.CurrentPage < 1 ? 1 : input.CurrentPage; + var pageSize = input.PageSize < 1 ? 20 : (input.PageSize > 100 ? 100 : input.PageSize); + + var query = _db.Queryable() + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()); + + if (input.StartTime.HasValue) + { + query = query.Where(x => x.EndTime >= input.StartTime.Value); + } + + if (input.EndTime.HasValue) + { + query = query.Where(x => x.StartTime <= input.EndTime.Value); + } + + if (!string.IsNullOrWhiteSpace(input.EventType)) + { + query = query.Where(x => x.EventType == input.EventType); + } + + if (!string.IsNullOrWhiteSpace(input.StoreId)) + { + query = query.Where(x => x.StoreId == input.StoreId); + } + + var total = await query.CountAsync(); + var list = await query.OrderBy(x => x.StartTime, OrderByType.Desc) + .Skip((page - 1) * pageSize) + .Take(pageSize) + .ToListAsync(); + + var typeIds = list.Select(x => x.EventType).Distinct().ToList(); + var types = await _db.Queryable() + .Where(x => typeIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .ToListAsync(); + + var typeMap = types.ToDictionary(x => x.Id, x => x); + + return new LqCompanyCalendarPageOutput + { + List = list.Select(x => + { + var type = typeMap.ContainsKey(x.EventType) ? typeMap[x.EventType] : null; + return new LqCompanyCalendarOutput + { + Id = x.Id, + Title = x.Title, + EventType = x.EventType, + EventTypeName = type?.TypeName ?? "", + EventTypeColor = type?.Color ?? "#3b82f6", + Date = x.StartTime.ToString("yyyy-MM-dd"), + Start = x.StartTime.ToString("yyyy-MM-ddTHH:mm:ss"), + End = x.EndTime.ToString("yyyy-MM-ddTHH:mm:ss"), + Desc = x.Description, + StoreId = x.StoreId, + Store = x.StoreName, + Remark = x.Remark, + Attachments = x.Attachments, + CreateUser = x.CreateUser, + CreateTime = x.CreateTime + }; + }).ToList(), + Total = total + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "获取公司日历事件列表失败"); + throw NCCException.Oh($"获取失败:{ex.Message}"); + } + } + + /// + /// 获取公司日历事件列表(不分页,用于日历展示) + /// + [HttpPost("GetCalendarEventList")] + public async Task> GetCalendarEventListAsync([FromBody] LqCompanyCalendarQueryInput input) + { + try + { + var query = _db.Queryable() + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode()); + + if (input.StartTime.HasValue) + { + query = query.Where(x => x.EndTime >= input.StartTime.Value); + } + + if (input.EndTime.HasValue) + { + query = query.Where(x => x.StartTime <= input.EndTime.Value); + } + + if (!string.IsNullOrWhiteSpace(input.EventType)) + { + query = query.Where(x => x.EventType == input.EventType); + } + + if (!string.IsNullOrWhiteSpace(input.StoreId)) + { + query = query.Where(x => x.StoreId == input.StoreId); + } + + var list = await query.OrderBy(x => x.StartTime).ToListAsync(); + + var typeIds = list.Select(x => x.EventType).Distinct().ToList(); + var types = await _db.Queryable() + .Where(x => typeIds.Contains(x.Id) && x.IsEffective == StatusEnum.有效.GetHashCode()) + .ToListAsync(); + + var typeMap = types.ToDictionary(x => x.Id, x => x); + + return list.Select(x => + { + var type = typeMap.ContainsKey(x.EventType) ? typeMap[x.EventType] : null; + return new LqCompanyCalendarOutput + { + Id = x.Id, + Title = x.Title, + EventType = x.EventType, + EventTypeName = type?.TypeName ?? "", + EventTypeColor = type?.Color ?? "#3b82f6", + Date = x.StartTime.ToString("yyyy-MM-dd"), + Start = x.StartTime.ToString("yyyy-MM-ddTHH:mm:ss"), + End = x.EndTime.ToString("yyyy-MM-ddTHH:mm:ss"), + Desc = x.Description, + StoreId = x.StoreId, + Store = x.StoreName, + Remark = x.Remark, + Attachments = x.Attachments, + CreateUser = x.CreateUser, + CreateTime = x.CreateTime + }; + }).ToList(); + } + catch (Exception ex) + { + _logger.LogError(ex, "获取公司日历事件列表失败"); + throw NCCException.Oh($"获取失败:{ex.Message}"); + } + } + + /// + /// 创建公司日历事件 + /// + [HttpPost("CreateCalendarEvent")] + public async Task CreateCalendarEventAsync([FromBody] LqCompanyCalendarCreateInput input) + { + try + { + var id = YitIdHelper.NextId().ToString(); + var entity = new LqCompanyCalendarEntity + { + Id = id, + Title = input.Title, + EventType = input.EventType, + StartTime = input.StartTime, + EndTime = input.EndTime, + Description = input.Description, + StoreId = input.StoreId, + StoreName = input.StoreName, + Remark = input.Remark, + Attachments = input.Attachments, + IsEffective = StatusEnum.有效.GetHashCode(), + CreateUser = _userManager.UserId, + CreateTime = DateTime.Now + }; + + await _db.Insertable(entity).ExecuteCommandAsync(); + + return id; + } + catch (Exception ex) + { + _logger.LogError(ex, "创建公司日历事件失败"); + throw NCCException.Oh($"创建失败:{ex.Message}"); + } + } + + /// + /// 更新公司日历事件 + /// + [HttpPost("UpdateCalendarEvent")] + public async Task UpdateCalendarEventAsync([FromBody] LqCompanyCalendarUpdateInput input) + { + try + { + var entity = await _db.Queryable() + .Where(x => x.Id == input.Id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + + if (entity == null) + { + throw NCCException.Oh("事件不存在或已失效"); + } + + entity.Title = input.Title; + entity.EventType = input.EventType; + entity.StartTime = input.StartTime; + entity.EndTime = input.EndTime; + entity.Description = input.Description; + entity.StoreId = input.StoreId; + entity.StoreName = input.StoreName; + entity.Remark = input.Remark; + entity.Attachments = input.Attachments; + entity.UpdateUser = _userManager.UserId; + entity.UpdateTime = DateTime.Now; + + await _db.Updateable(entity).ExecuteCommandAsync(); + + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "更新公司日历事件失败"); + throw NCCException.Oh($"更新失败:{ex.Message}"); + } + } + + /// + /// 删除公司日历事件 + /// + [HttpPost("DeleteCalendarEvent/{id}")] + public async Task DeleteCalendarEventAsync(string id) + { + try + { + var entity = await _db.Queryable() + .Where(x => x.Id == id && x.IsEffective == StatusEnum.有效.GetHashCode()) + .FirstAsync(); + + if (entity == null) + { + throw NCCException.Oh("事件不存在或已失效"); + } + + entity.IsEffective = StatusEnum.无效.GetHashCode(); + entity.UpdateUser = _userManager.UserId; + entity.UpdateTime = DateTime.Now; + + await _db.Updateable(entity).ExecuteCommandAsync(); + + return true; + } + catch (Exception ex) + { + _logger.LogError(ex, "删除公司日历事件失败"); + throw NCCException.Oh($"删除失败:{ex.Message}"); + } + } + + #endregion + } +} diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs index 3c96cad..700f09e 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs @@ -3398,6 +3398,10 @@ WHERE kh.F_IsEffective = 1"; { var birthday = member.Yanglsr.Value; birthDateForAge = birthday; + + if (birthday.Day > DateTime.DaysInMonth(now.Year, birthday.Month)) + continue; + var thisYearBirthday = new DateTime(now.Year, birthday.Month, birthday.Day); var nextYearBirthday = new DateTime(now.Year + 1, birthday.Month, birthday.Day); @@ -3614,7 +3618,10 @@ WHERE kh.F_IsEffective = 1"; int calendarMonth; if (isLeap) { - calendarMonth = baseMonth + 1; + if (leapMonth > 0 && leapMonth == baseMonth) + calendarMonth = baseMonth + 1; + else + return (null, null); } else if (leapMonth > 0 && baseMonth > leapMonth) { @@ -3637,7 +3644,7 @@ WHERE kh.F_IsEffective = 1"; { return cal.ToDateTime(lunarYear, lunarMonth, lunarDay, 0, 0, 0, 0); } - catch (ArgumentOutOfRangeException) + catch (Exception) { return null; } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs index a7a8ee2..0241375 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs @@ -780,6 +780,164 @@ namespace NCC.Extend throw NCCException.Oh($"获取门店一周运营热力图数据失败:{ex.Message}"); } } + + /// + /// 获取沉睡会员列表 + /// + /// + /// 查询指定门店中超过配置天数未消费的会员列表。 + /// 沉睡天数通过系统配置 sleepRemindDays 控制,默认30天。 + /// + /// 示例请求: + /// ```json + /// { + /// "storeId": "1649328471923847169" + /// } + /// ``` + /// + /// 返回数据说明: + /// - id: 会员ID + /// - name: 会员姓名 + /// - phone: 手机号(脱敏) + /// - level: 会员等级 + /// - days: 沉睡天数 + /// - lastVisit: 最后到店日期 + /// - balance: 账户余额 + /// + /// 查询参数 + /// 沉睡会员列表 + /// 成功返回沉睡会员列表 + [HttpPost("GetSleepingMembers")] + public async Task GetSleepingMembers([FromBody] SleepingMembersInput input) + { + try + { + if (input == null || string.IsNullOrWhiteSpace(input.StoreId)) + { + throw NCCException.Oh("门店ID不能为空"); + } + + var page = input.Page < 1 ? 1 : input.Page; + var pageSize = input.PageSize < 1 ? 20 : (input.PageSize > 100 ? 100 : input.PageSize); + var offset = (page - 1) * pageSize; + + // 获取沉睡天数配置 + var sleepDays = await Helpers.ConsumeEditableDaysHelper.GetSleepRemindDaysAsync(_db); + var cutoffDate = DateTime.Now.AddDays(-sleepDays); + + _logger.LogInformation("查询沉睡会员,门店ID:{StoreId},沉睡天数:{SleepDays},页码:{Page},每页:{PageSize}", input.StoreId, sleepDays, page, pageSize); + + // 查询总数 + var countSql = $@" + SELECT COUNT(*) FROM ( + SELECT kh.F_Id + FROM lq_khxx kh + INNER JOIN lq_xh_hyhk xh ON kh.F_Id = xh.Hy AND xh.F_IsEffective = 1 + WHERE xh.Md = '{input.StoreId}' + GROUP BY kh.F_Id + HAVING MAX(xh.Hksj) < '{cutoffDate:yyyy-MM-dd HH:mm:ss}' OR MAX(xh.Hksj) IS NULL + ) t"; + var total = await _db.Ado.SqlQueryAsync(countSql); + var totalCount = total.FirstOrDefault(); + + // 查询沉睡会员:最后消费时间早于截止日期的会员 + var sql = $@" + SELECT + kh.F_Id as Id, + kh.khmc as Name, + kh.sjh as Phone, + kh.khjd as Level, + DATEDIFF(NOW(), MAX(xh.Hksj)) as Days, + MAX(xh.Hksj) as LastVisit, + kh.F_RemainingRightsAmount as Balance + FROM lq_khxx kh + INNER JOIN lq_xh_hyhk xh ON kh.F_Id = xh.Hy AND xh.F_IsEffective = 1 + WHERE xh.Md = '{input.StoreId}' + GROUP BY kh.F_Id, kh.khmc, kh.sjh, kh.khjd, kh.F_RemainingRightsAmount + HAVING MAX(xh.Hksj) < '{cutoffDate:yyyy-MM-dd HH:mm:ss}' OR MAX(xh.Hksj) IS NULL + ORDER BY Days DESC + LIMIT {pageSize} OFFSET {offset}"; + + var result = await _db.Ado.SqlQueryAsync(sql); + + // 手机号脱敏 + foreach (var item in result) + { + if (!string.IsNullOrWhiteSpace(item.Phone) && item.Phone.Length >= 7) + { + item.Phone = item.Phone.Substring(0, 3) + "****" + item.Phone.Substring(item.Phone.Length - 4); + } + } + + return new SleepingMemberPageOutput + { + Items = result, + Total = totalCount + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "获取沉睡会员列表失败,门店ID:{StoreId}", input?.StoreId); + throw NCCException.Oh($"获取沉睡会员列表失败:{ex.Message}"); + } + } + } + + /// + /// 沉睡会员输出 + /// + public class SleepingMemberOutput + { + /// + /// 会员ID + /// + public string Id { get; set; } + + /// + /// 会员姓名 + /// + public string Name { get; set; } + + /// + /// 手机号(脱敏) + /// + public string Phone { get; set; } + + /// + /// 会员等级 + /// + public string Level { get; set; } + + /// + /// 沉睡天数 + /// + public int Days { get; set; } + + /// + /// 最后到店日期 + /// + public DateTime? LastVisit { get; set; } + + /// + /// 账户余额 + /// + public decimal? Balance { get; set; } + } + + /// + /// 沉睡会员分页输出 + /// + public class SleepingMemberPageOutput + { + /// + /// 沉睡会员列表 + /// + public List Items { get; set; } + + /// + /// 总数 + /// + public int Total { get; set; } } } diff --git a/netcore/src/Modularity/Extend/NCC.Extend/OssDirectUploadService.cs b/netcore/src/Modularity/Extend/NCC.Extend/OssDirectUploadService.cs index eed642a..7857fc3 100644 --- a/netcore/src/Modularity/Extend/NCC.Extend/OssDirectUploadService.cs +++ b/netcore/src/Modularity/Extend/NCC.Extend/OssDirectUploadService.cs @@ -141,7 +141,7 @@ namespace NCC.Extend expiration = expireTime.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"), conditions = new object[] { - new object[] { "content-length-range", 0, 10485760 }, + new object[] { "content-length-range", 0, 20971520 }, new object[] { "starts-with", "$key", objectKeyPrefix } } }; 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 97b84b5..bbcba36 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 @@ -1,4 +1,4 @@ -using NCC.Dependency; +using NCC.Dependency; using System; using System.Collections.Generic; using System.Linq; @@ -287,5 +287,10 @@ namespace NCC.System.Entitys.Dto.System.SysConfig /// 管理后台删除耗卡允许天数;0 表示不限制天数(是否显示按钮由角色按钮权限 btn_remove 控制) /// public string adminConsumeDeleteEditableDays { get; set; } + + /// + /// 沉睡提醒天数(门店PC),超过该天数未消费的会员将显示在沉睡提醒列表中;默认 30(sys_config:Category=SysConfig,Key=sleepRemindDays) + /// + public string sleepRemindDays { get; set; } } } diff --git a/package-lock.json b/package-lock.json index 33111e4..e5ef364 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,13 +5,156 @@ "packages": { "": { "dependencies": { - "@qiun/ucharts": "^2.5.0-20230101" + "@qiun/ucharts": "^2.5.0-20230101", + "mysql2": "^3.22.5" } }, "node_modules/@qiun/ucharts": { "version": "2.5.0-20230101", "resolved": "https://registry.npmmirror.com/@qiun/ucharts/-/ucharts-2.5.0-20230101.tgz", "integrity": "sha512-C7ccBgfPuGF6dxTRuMW0NPPMSCf1k/kh3I9zkRVBc5PaivudX/rPL+jd2Wty6gn5ya5L3Ob+YmYe09V5xw66Cw==" + }, + "node_modules/@types/node": { + "version": "25.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", + "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", + "license": "MIT", + "peer": true, + "dependencies": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "node_modules/aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==", + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT" + }, + "node_modules/long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==", + "license": "Apache-2.0" + }, + "node_modules/lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=1.30.0", + "node": ">=8.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wellwelwel" + } + }, + "node_modules/mysql2": { + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.5.tgz", + "integrity": "sha512-95uZ2TrPWAZdwpB3vvvDbmEMcNG8yIeNCyu6GUcr/QnWEE/wXm7+mhOCsdQfWQDTV7qYT/PDUZ4U4UPP4AsXqQ==", + "license": "MIT", + "dependencies": { + "aws-ssl-profiles": "^1.1.2", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" + }, + "engines": { + "node": ">= 8.0" + }, + "peerDependencies": { + "@types/node": ">= 8" + } + }, + "node_modules/named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "license": "MIT", + "dependencies": { + "lru.min": "^1.1.0" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==", + "license": "MIT", + "engines": { + "bun": ">=1.0.0", + "deno": ">=2.0.0", + "node": ">=12.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" + } + }, + "node_modules/undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "license": "MIT", + "peer": true } }, "dependencies": { @@ -19,6 +162,95 @@ "version": "2.5.0-20230101", "resolved": "https://registry.npmmirror.com/@qiun/ucharts/-/ucharts-2.5.0-20230101.tgz", "integrity": "sha512-C7ccBgfPuGF6dxTRuMW0NPPMSCf1k/kh3I9zkRVBc5PaivudX/rPL+jd2Wty6gn5ya5L3Ob+YmYe09V5xw66Cw==" + }, + "@types/node": { + "version": "25.9.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz", + "integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==", + "peer": true, + "requires": { + "undici-types": ">=7.24.0 <7.24.7" + } + }, + "aws-ssl-profiles": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", + "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==" + }, + "denque": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", + "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==" + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "requires": { + "is-property": "^1.0.2" + } + }, + "iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==" + }, + "long": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", + "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==" + }, + "lru.min": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", + "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==" + }, + "mysql2": { + "version": "3.22.5", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.22.5.tgz", + "integrity": "sha512-95uZ2TrPWAZdwpB3vvvDbmEMcNG8yIeNCyu6GUcr/QnWEE/wXm7+mhOCsdQfWQDTV7qYT/PDUZ4U4UPP4AsXqQ==", + "requires": { + "aws-ssl-profiles": "^1.1.2", + "denque": "^2.1.0", + "generate-function": "^2.3.1", + "iconv-lite": "^0.7.2", + "long": "^5.3.2", + "lru.min": "^1.1.4", + "named-placeholders": "^1.1.6", + "sql-escaper": "^1.3.3" + } + }, + "named-placeholders": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", + "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==", + "requires": { + "lru.min": "^1.1.0" + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sql-escaper": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/sql-escaper/-/sql-escaper-1.3.3.tgz", + "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==" + }, + "undici-types": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz", + "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==", + "peer": true } } } diff --git a/package.json b/package.json index 27ba349..fd1288d 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,6 @@ { "dependencies": { - "@qiun/ucharts": "^2.5.0-20230101" + "@qiun/ucharts": "^2.5.0-20230101", + "mysql2": "^3.22.5" } } diff --git a/store-pc/src/api/lqKhxx.js b/store-pc/src/api/lqKhxx.js index bc2ae23..f9be519 100644 --- a/store-pc/src/api/lqKhxx.js +++ b/store-pc/src/api/lqKhxx.js @@ -31,6 +31,15 @@ export function getMemberOrderType(memberId) { }) } +/** 获取即将过生日的会员列表 */ +export function getUpcomingBirthdays(data) { + return request({ + url: '/api/Extend/LqKhxx/GetUpcomingBirthdays', + method: 'get', + data + }) +} + /** 健康师金三角(按月) */ export function getJsjInfo(userId, dateYmd) { return request({ diff --git a/store-pc/src/api/storeDashboard.js b/store-pc/src/api/storeDashboard.js index 9b08249..96fef93 100644 --- a/store-pc/src/api/storeDashboard.js +++ b/store-pc/src/api/storeDashboard.js @@ -26,3 +26,30 @@ export function getStorePerformanceCompletion(data) { data }) } + +/** 获取沉睡会员列表(门店PC) */ +export function getSleepingMembers(data) { + return request({ + url: '/api/Extend/LqStoreDashboard/GetSleepingMembers', + method: 'post', + data + }) +} + +/** 获取公司日历事件列表(不分页,用于日历展示) */ +export function getCalendarEventList(data) { + return request({ + url: '/api/Extend/LqCompanyCalendar/GetCalendarEventList', + method: 'post', + data + }) +} + +/** 获取事件类型列表 */ +export function getEventTypeList(data) { + return request({ + url: '/api/Extend/LqCompanyCalendar/GetEventTypeList', + method: 'post', + data + }) +} diff --git a/store-pc/src/components/BookingCalendarDialog.vue b/store-pc/src/components/BookingCalendarDialog.vue index 1e1599f..fb626b6 100644 --- a/store-pc/src/components/BookingCalendarDialog.vue +++ b/store-pc/src/components/BookingCalendarDialog.vue @@ -9,14 +9,26 @@ >
-
预约日历
+
+ 预约日历 +
+
+ + + 日历 + + + 列表 + + +
- + +
+ +
+ + +
+
+
+ {{ dateRangeText }} + {{ bookingList.length }} 条预约 +
+
+ 已预约 {{ statusCount('booked') }} + 服务中 {{ statusCount('serving') }} + 已完成 {{ statusCount('converted') }} + 已取消 {{ statusCount('cancelled') }} +
+
+ + + + + + + + + + + + + + + + + + + +
@@ -98,6 +173,7 @@ export default { data() { return { loading: false, + currentView: 'calendar', query: { status: undefined }, calendarPlugins: [dayGridPlugin, timeGridPlugin, interactionPlugin], calendarEvents: [], @@ -118,6 +194,15 @@ export default { visibleProxy: { get() { return this.visible }, set(v) { this.$emit('update:visible', v) } + }, + dateRangeText() { + if (this.startTime && this.endTime) { + const start = this.formatDate(this.startTime) + const end = this.formatDate(this.endTime) + if (start === end) return start + return `${start} 至 ${end}` + } + return '' } }, watch: { @@ -171,6 +256,21 @@ export default { statusText(status) { return appointmentStatusText(status) }, + statusCount(status) { + return (this.bookingList || []).filter(r => r.status === status).length + }, + tableRowClassName({ row }) { + if (row.status === 'cancelled') return 'cancelled-row' + if (row.status === 'serving') return 'serving-row' + return '' + }, + getStatusType(status) { + if (status === 'booked') return 'primary' + if (status === 'serving') return 'success' + if (status === 'converted') return 'info' + if (status === 'cancelled') return 'danger' + return 'info' + }, statusColor(status) { if (status === 'booked') return '#409EFF' if (status === 'serving') return '#67C23A' @@ -225,13 +325,18 @@ export default { return } try { - const rangeStart = this.startTime || new Date() - const rangeEnd = this.endTime || new Date() + let rangeStart = this.startTime + let rangeEnd = this.endTime + if (!rangeStart || !rangeEnd) { + const now = new Date() + rangeStart = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0) + rangeEnd = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999) + } const yysjRange = buildYysjTimestampRange(rangeStart, rangeEnd) const params = { djmd: storeId, currentPage: 1, - pageSize: 1000, + pageSize: 500, sidx: 'yysj', sort: 'asc' } @@ -415,6 +520,9 @@ export default { const rec = (this.bookingList || []).find(x => x && x.id === id) if (rec) this.openBookingDetail(rec) }, + handleListRowClick(row) { + this.openBookingDetail(row) + }, async openBookingDetail(rec) { try { const detail = await this.loadBookingDetail(rec.id) @@ -552,18 +660,289 @@ export default { diff --git a/store-pc/src/components/CompanyCalendarDialog.vue b/store-pc/src/components/CompanyCalendarDialog.vue index 0ea4136..7483138 100644 --- a/store-pc/src/components/CompanyCalendarDialog.vue +++ b/store-pc/src/components/CompanyCalendarDialog.vue @@ -11,9 +11,9 @@
公司日历
- + - {{ item.label }} + {{ item.typeName }}
@@ -58,8 +58,8 @@
类型 - - {{ selectedEventTypeLabel }} + + {{ selectedEvent.typeName || '无' }}
日期{{ selectedEventDateStr }}
@@ -114,6 +114,7 @@ import FullCalendar from '@fullcalendar/vue' import dayGridPlugin from '@fullcalendar/daygrid' import timeGridPlugin from '@fullcalendar/timegrid' import interactionPlugin from '@fullcalendar/interaction' +import { getCalendarEventList, getEventTypeList } from '@/api/storeDashboard' export default { name: 'CompanyCalendarDialog', @@ -122,50 +123,11 @@ export default { visible: { type: Boolean, default: false } }, data() { - const today = new Date() - const y = today.getFullYear() - const m = String(today.getMonth() + 1).padStart(2, '0') - const d = String(today.getDate()).padStart(2, '0') - const todayStr = `${y}-${m}-${d}` return { loading: false, - // 公司层面的会议/活动数据(后续可换成接口) - companyEvents: [ - // 当天 7 条示例(操作会 + 大会 + 活动),方便预览密集情况 - { id: 'E001', date: todayStr, start: `${todayStr}T09:00:00`, end: `${todayStr}T10:00:00`, type: 'ops', title: '操作会 - 紫荆店' }, - { id: 'E002', date: todayStr, start: `${todayStr}T10:30:00`, end: `${todayStr}T11:30:00`, type: 'ops', title: '操作会 - 西沙店' }, - { id: 'E003', date: todayStr, start: `${todayStr}T13:00:00`, end: `${todayStr}T14:00:00`, type: 'training', title: '培训会 - 新员工入职营', remark: '请提前准备入职材料', attachments: [{ name: '入职手册.pdf' }, { name: '培训PPT.pptx' }] }, - { id: 'E004', date: todayStr, start: `${todayStr}T14:30:00`, end: `${todayStr}T15:30:00`, type: 'training', title: '培训会 - 科美新品讲解' }, - { id: 'E005', date: todayStr, start: `${todayStr}T16:00:00`, end: `${todayStr}T17:00:00`, type: 'activity', title: '门店活动 - 紫荆店会员沙龙', remark: '需准备茶点与签到礼' }, - { id: 'E006', date: todayStr, start: `${todayStr}T18:00:00`, end: `${todayStr}T19:00:00`, type: 'activity', title: '门店活动 - 西沙店体验日' }, - { id: 'E007', date: todayStr, start: `${todayStr}T19:30:00`, end: `${todayStr}T21:00:00`, type: 'meeting', title: '全体大会 - 本月复盘', remark: '各店长携带本月数据报表', attachments: [{ name: '本月经营数据.xlsx' }, { name: '会议议程.pdf' }] }, - // 其它日期:尽量覆盖当月大部分天 - { id: 'E008', date: `${y}-${m}-01`, start: `${y}-${m}-01T10:00:00`, end: `${y}-${m}-01T12:00:00`, type: 'meeting', title: '月初经营例会' }, - { id: 'E009', date: `${y}-${m}-02`, start: `${y}-${m}-02T14:00:00`, end: `${y}-${m}-02T16:00:00`, type: 'ops', title: '操作会 - 静居寺店' }, - { id: 'E010', date: `${y}-${m}-03`, start: `${y}-${m}-03T09:30:00`, end: `${y}-${m}-03T11:00:00`, type: 'training', title: '培训会 - 会员沟通话术' }, - { id: 'E011', date: `${y}-${m}-04`, start: `${y}-${m}-04T15:00:00`, end: `${y}-${m}-04T17:00:00`, type: 'activity', title: '门店活动 - 双流店开放日' }, - { id: 'E012', date: `${y}-${m}-05`, start: `${y}-${m}-05T10:00:00`, end: `${y}-${m}-05T11:30:00`, type: 'ops', title: '操作会 - 保利店' }, - { id: 'E013', date: `${y}-${m}-06`, start: `${y}-${m}-06T14:00:00`, end: `${y}-${m}-06T15:30:00`, type: 'training', title: '培训会 - 逆龄项目进阶' }, - { id: 'E014', date: `${y}-${m}-07`, start: `${y}-${m}-07T19:00:00`, end: `${y}-${m}-07T20:30:00`, type: 'activity', title: '门店活动 - 线上直播专场' }, - { id: 'E015', date: `${y}-${m}-08`, start: `${y}-${m}-08T09:30:00`, end: `${y}-${m}-08T11:00:00`, type: 'meeting', title: '事业部经营会' }, - { id: 'E016', date: `${y}-${m}-09`, start: `${y}-${m}-09T13:30:00`, end: `${y}-${m}-09T15:00:00`, type: 'ops', title: '操作会 - 南湖店' }, - { id: 'E017', date: `${y}-${m}-10`, start: `${y}-${m}-10T10:00:00`, end: `${y}-${m}-10T11:30:00`, type: 'training', title: '培训会 - 顾问心法' }, - { id: 'E018', date: `${y}-${m}-11`, start: `${y}-${m}-11T15:00:00`, end: `${y}-${m}-11T17:00:00`, type: 'activity', title: '门店活动 - 西站店沙龙' }, - { id: 'E019', date: `${y}-${m}-12`, start: `${y}-${m}-12T09:30:00`, end: `${y}-${m}-12T11:00:00`, type: 'ops', title: '操作会 - 融创店' }, - { id: 'E020', date: `${y}-${m}-13`, start: `${y}-${m}-13T14:00:00`, end: `${y}-${m}-13T15:30:00`, type: 'training', title: '培训会 - 经典案例分享' }, - { id: 'E021', date: `${y}-${m}-14`, start: `${y}-${m}-14T19:00:00`, end: `${y}-${m}-14T20:30:00`, type: 'activity', title: '门店活动 - 会员观影会' }, - { id: 'E022', date: `${y}-${m}-15`, start: `${y}-${m}-15T10:00:00`, end: `${y}-${m}-15T12:00:00`, type: 'training', title: '产品培训 - 生命之波' }, - { id: 'E023', date: `${y}-${m}-16`, start: `${y}-${m}-16T09:30:00`, end: `${y}-${m}-16T11:00:00`, type: 'ops', title: '操作会 - 科技部' }, - { id: 'E024', date: `${y}-${m}-17`, start: `${y}-${m}-17T15:00:00`, end: `${y}-${m}-17T17:00:00`, type: 'activity', title: '门店活动 - 会员答谢宴' }, - { id: 'E025', date: `${y}-${m}-18`, start: `${y}-${m}-18T14:00:00`, end: `${y}-${m}-18T15:30:00`, type: 'training', title: '培训会 - 绩效与辅导' }, - { id: 'E026', date: `${y}-${m}-19`, start: `${y}-${m}-19T10:00:00`, end: `${y}-${m}-19T11:30:00`, type: 'ops', title: '操作会 - 门店联动' }, - { id: 'E027', date: `${y}-${m}-20`, start: `${y}-${m}-20T09:30:00`, end: `${y}-${m}-20T11:30:00`, type: 'meeting', title: '季度全体大会', remark: '全体参加,线上同步', attachments: [{ name: '季度总结报告.pdf' }, { name: '下季目标分解.xlsx' }] }, - { id: 'E028', date: `${y}-${m}-21`, start: `${y}-${m}-21T19:00:00`, end: `${y}-${m}-21T20:30:00`, type: 'activity', title: '门店活动 - 新品体验会' }, - { id: 'E029', date: `${y}-${m}-22`, start: `${y}-${m}-22T10:00:00`, end: `${y}-${m}-22T11:30:00`, type: 'training', title: '培训会 - 工具使用' }, - { id: 'E030', date: `${y}-${m}-23`, start: `${y}-${m}-23T14:00:00`, end: `${y}-${m}-23T15:30:00`, type: 'ops', title: '操作会 - 区域复盘' }, - { id: 'E031', date: `${y}-${m}-24`, start: `${y}-${m}-24T15:00:00`, end: `${y}-${m}-24T16:30:00`, type: 'activity', title: '门店活动 - 老客回访日' }, - { id: 'E032', date: `${y}-${m}-25`, start: `${y}-${m}-25T09:30:00`, end: `${y}-${m}-25T11:00:00`, type: 'meeting', title: '高管经营例会' } - ], + companyEvents: [], + eventTypeList: [], + typeColorMap: {}, calendarPlugins: [dayGridPlugin, timeGridPlugin, interactionPlugin], calendarEvents: [], calendarHeader: { @@ -178,13 +140,7 @@ export default { endTime: null, calendarHeight: 720, detailVisible: false, - selectedEvent: null, - typeColorMap: { - ops: '#6366f1', - training: '#22c55e', - meeting: '#f97316', - activity: '#ec4899' - } + selectedEvent: null } }, computed: { @@ -196,19 +152,6 @@ export default { this.$emit('update:visible', v) } }, - legendList() { - return [ - { type: 'ops', label: '操作会', color: this.typeColorMap.ops }, - { type: 'training', label: '培训会', color: this.typeColorMap.training }, - { type: 'meeting', label: '全体大会', color: this.typeColorMap.meeting }, - { type: 'activity', label: '门店活动', color: this.typeColorMap.activity } - ] - }, - selectedEventTypeLabel() { - if (!this.selectedEvent || !this.selectedEvent.type) return '—' - const item = this.legendList.find(x => x.type === this.selectedEvent.type) - return item ? item.label : this.selectedEvent.type - }, selectedEventDateStr() { if (!this.selectedEvent || !this.selectedEvent.start) return '—' return this.formatDate(new Date(this.selectedEvent.start)) @@ -222,8 +165,14 @@ export default { }, selectedEventAttachments() { if (!this.selectedEvent || !this.selectedEvent.attachments) return [] - const list = Array.isArray(this.selectedEvent.attachments) ? this.selectedEvent.attachments : [] - return list.filter(a => a && (a.name || a.fileName)) + try { + const list = typeof this.selectedEvent.attachments === 'string' + ? JSON.parse(this.selectedEvent.attachments) + : this.selectedEvent.attachments + return Array.isArray(list) ? list.filter(a => a && (a.name || a.fileName)) : [] + } catch { + return [] + } } }, watch: { @@ -231,6 +180,7 @@ export default { if (v) { this.$nextTick(() => { this.calcHeight() + this.loadEventTypeList() this.initData() }) } @@ -247,7 +197,6 @@ export default { this.$nextTick(() => { const el = this.$el && this.$el.querySelector('.dialog-content') if (el) { - // 提高最小高度,让每天能展示更多事件 this.calendarHeight = Math.max(el.clientHeight - 20, 650) } }) @@ -258,33 +207,56 @@ export default { this.endTime = view.activeEnd this.initData() }, - initData() { + async loadEventTypeList() { + try { + const res = await getEventTypeList({}) + const list = res.data || res || [] + this.eventTypeList = Array.isArray(list) ? list : [] + this.typeColorMap = {} + this.eventTypeList.forEach(item => { + this.typeColorMap[item.Id] = item.Color + }) + } catch (e) { + console.error('[公司日历] 加载事件类型失败:', e) + } + }, + async initData() { this.loading = true - setTimeout(() => { - let filtered = [...this.companyEvents] - if (this.startTime && this.endTime) { - filtered = filtered.filter(e => { - const t = new Date(e.start).getTime() - return t >= this.startTime.getTime() && t < this.endTime.getTime() - }) + try { + const params = {} + if (this.startTime) { + params.startTime = this.startTime.toISOString() } - this.calendarEvents = filtered.map(evt => { - const color = this.typeColorMap[evt.type] || '#3b82f6' + if (this.endTime) { + params.endTime = this.endTime.toISOString() + } + + const res = await getCalendarEventList(params) + const list = res.data || res || [] + this.companyEvents = Array.isArray(list) ? list : [] + + this.calendarEvents = this.companyEvents.map(evt => { + const color = evt.EventTypeColor || this.typeColorMap[evt.EventType] || '#3b82f6' return { - id: evt.id, - title: evt.title, - start: new Date(evt.start).toISOString(), - end: new Date(evt.end).toISOString(), + id: evt.Id, + title: evt.Title, + start: evt.Start, + end: evt.End, color, editable: false, allDay: false, extendedProps: { - type: evt.type + type: evt.EventType } } }) + } catch (e) { + console.error('[公司日历] 加载失败:', e) + this.companyEvents = [] + this.calendarEvents = [] + } finally { this.loading = false - }, 200) + } }, formatDate(d) { const dt = d instanceof Date ? d : new Date(d) @@ -310,17 +282,19 @@ export default { handleEventClick(info) { const evt = info && info.event if (!evt) return - const raw = this.companyEvents.find(e => e.id === evt.id) || {} + const raw = this.companyEvents.find(e => e.Id === evt.id) || {} this.selectedEvent = { - id: raw.id, - title: raw.title || evt.title, - type: (evt.extendedProps && evt.extendedProps.type) || raw.type, - start: raw.start, - end: raw.end, - desc: raw.desc, - store: raw.store, - remark: raw.remark, - attachments: raw.attachments ? [...raw.attachments] : [] + id: raw.Id || evt.id, + title: raw.Title || evt.title, + type: (evt.extendedProps && evt.extendedProps.type) || raw.EventType, + typeName: raw.EventTypeName || '', + typeColor: raw.EventTypeColor || this.typeColorMap[(evt.extendedProps && evt.extendedProps.type) || raw.EventType] || '#3b82f6', + start: raw.Start, + end: raw.End, + desc: raw.Desc, + store: raw.Store, + remark: raw.Remark, + attachments: raw.Attachments } this.detailVisible = true } @@ -698,5 +672,3 @@ export default { box-shadow: 0 4px 10px rgba(37, 99, 235, 0.35); } - - diff --git a/store-pc/src/views/dashboard/index.vue b/store-pc/src/views/dashboard/index.vue index 8c8e539..05239d6 100644 --- a/store-pc/src/views/dashboard/index.vue +++ b/store-pc/src/views/dashboard/index.vue @@ -304,7 +304,7 @@
今日暂无预约记录
-
+
@@ -360,7 +360,7 @@
-
+
{{ item.days }}天
@@ -373,6 +373,8 @@
+
加载中...
+
没有更多了
暂无沉睡会员
@@ -461,7 +463,8 @@ import request from '@/utils/request' import { getStoreDashboardStatistics, getStoreDailyStatistics, - getStorePerformanceCompletion + getStorePerformanceCompletion, + getSleepingMembers } from '@/api/storeDashboard' import { getTkjlbList } from '@/api/lqTkjlb' import { getKdKdjlbList } from '@/api/lqKdKdjlb' @@ -497,7 +500,7 @@ import ConsumeListDialog from '@/components/ConsumeListDialog.vue' import RefundListDialog from '@/components/RefundListDialog.vue' import GoldTriangleDialog from '@/components/GoldTriangleDialog.vue' import StoreTargetDialog from '@/components/StoreTargetDialog.vue' -import { getKhxxList } from '@/api/lqKhxx' +import { getKhxxList, getUpcomingBirthdays } from '@/api/lqKhxx' import { getMyProfile } from '@/api/lqStoreAuth' import { mapKhxxListToSuggestion } from '@/utils/memberProfileHelper' @@ -649,28 +652,13 @@ export default { birthdayYear: new Date().getFullYear(), birthdayMonth: new Date().getMonth() + 1, selectedBirthdayDay: null, - birthdayAllData: [ - { id: 'B001', name: '林晓薇', phone: '138****8801', level: 'A++', totalAmount: '38,600', month: 3, day: 5 }, - { id: 'B002', name: '陈美玲', phone: '139****2233', level: 'A+', totalAmount: '22,400', month: 3, day: 12 }, - { id: 'B003', name: '张芳芳', phone: '158****9901', level: 'A', totalAmount: '15,800', month: 3, day: 12 }, - { id: 'B004', name: '王晓燕', phone: '137****4456', level: 'A++', totalAmount: '45,200', month: 3, day: 18 }, - { id: 'B005', name: '李明珠', phone: '186****3322', level: 'B', totalAmount: '8,900', month: 3, day: 22 }, - { id: 'B006', name: '周丽', phone: '133****7788', level: 'A', totalAmount: '18,300', month: 3, day: 22 }, - { id: 'B007', name: '刘思雨', phone: '159****6678', level: 'C', totalAmount: '5,200', month: 3, day: 28 }, - { id: 'B008', name: '赵美华', phone: '181****2234', level: 'A+', totalAmount: '31,800', month: 4, day: 6 }, - { id: 'B009', name: '孙晴晴', phone: '135****8890', level: 'B', totalAmount: '11,200', month: 4, day: 15 }, - { id: 'B010', name: '钱雯雯', phone: '189****0012', level: 'D', totalAmount: '2,800', month: 4, day: 15 }, - { id: 'B011', name: '吴佳敏', phone: '136****5567', level: 'A++', totalAmount: '52,400', month: 5, day: 3 }, - { id: 'B012', name: '郑晓红', phone: '182****3345', level: 'A', totalAmount: '19,600', month: 5, day: 20 }, - { id: 'B013', name: '冯丽娟', phone: '155****7723', level: 'B', totalAmount: '7,300', month: 1, day: 8 }, - { id: 'B014', name: '蒋雪梅', phone: '177****6634', level: 'A+', totalAmount: '28,500', month: 2, day: 14 } - ], - sleepingMembers: [ - { id: 'S001', name: '周丽娟', days: 187, phone: '133****7788', level: 'A++', lastVisit: '2024-09-01', balance: '12,800' }, - { id: 'S002', name: '王晓燕', days: 95, phone: '137****4456', level: 'A+', lastVisit: '2024-12-01', balance: '8,500' }, - { id: 'S003', name: '刘倩', days: 63, phone: '159****3312', level: 'A', lastVisit: '2025-01-03', balance: '3,200' }, - { id: 'S004', name: '赵海燕', days: 60, phone: '186****0023', level: 'B', lastVisit: '2025-01-06', balance: '1,600' } - ], + birthdayAllData: [], + birthdayLoading: false, + sleepingMembers: [], + sleepingPage: 1, + sleepingTotal: 0, + sleepingLoading: false, + sleepingPageSize: 20, tuokeDialogVisible: false, bookingDialogVisible: false, bookingPrefill: { @@ -799,7 +787,7 @@ export default { { key: 'sleeping', label: '沉睡提醒', - count: this.sleepingMembers.length, + count: this.sleepingTotal, icon: 'el-icon-moon' } ] @@ -918,6 +906,8 @@ export default { this.loadScheduleOnDutyCount() this.loadTodoList() this.loadTodoCount() + this.loadSleepingMembers(true) + this.loadBirthdayData() }, formatTodoDueLabel(dt) { return formatTodoDueLabelText(dt) @@ -1485,6 +1475,59 @@ export default { handleBirthdayCall(item) { this.$message.success(`已为 ${item.name} 发送生日祝福短信`) }, + async loadBirthdayData() { + const storeId = this.resolveStoreId() + if (!storeId) return + this.birthdayLoading = true + try { + const y = this.birthdayYear + const m = this.birthdayMonth + const lastDay = new Date(y, m, 0).getDate() + const startDate = `${y}-${String(m).padStart(2, '0')}-01` + const endDate = `${y}-${String(m).padStart(2, '0')}-${String(lastDay).padStart(2, '0')}` + const res = await getUpcomingBirthdays({ + storeId, + showSolar: true, + showLunar: true, + startDate, + endDate + }) + const apiData = res.data || {} + const items = apiData.data || apiData || [] + const list = (Array.isArray(items) ? items : []).map(item => { + const fullDate = item.birthdayFullDate + let month = 0 + let day = 0 + if (fullDate) { + const d = new Date(fullDate) + if (!isNaN(d.getTime())) { + month = d.getMonth() + 1 + day = d.getDate() + } + } + return { + id: item.id, + name: item.khmc || '', + phone: item.sjh || '', + level: item.consumeLevelName || '', + month, + day, + totalAmount: item.remainingRightsAmount != null ? Number(item.remainingRightsAmount).toLocaleString('zh-CN', { maximumFractionDigits: 0 }) : '0', + birthdayType: item.birthdayType, + yinlsr: item.yinlsr || '', + yanglsr: item.yanglsr || '', + age: item.age, + gsmdName: item.gsmdName || '', + bz: item.bz || '' + } + }) + this.birthdayAllData = list.filter(m => m.month > 0 && m.day > 0) + } catch (e) { + console.error('[生日提醒]', e.message || e) + } finally { + this.birthdayLoading = false + } + }, handleSleepingCall(item) { this.activeTodoId = '' this.currentContactCustomer = { @@ -1494,6 +1537,61 @@ export default { } this.contactDialogVisible = true }, + async loadSleepingMembers(reset = false) { + const storeId = this.resolveStoreId() + if (!storeId) return + if (reset) { + this.sleepingPage = 1 + this.sleepingMembers = [] + this.sleepingTotal = 0 + } + if (this.sleepingLoading) return + this.sleepingLoading = true + try { + const res = await getSleepingMembers({ + storeId, + page: this.sleepingPage, + pageSize: this.sleepingPageSize + }) + const raw = res.data || res || {} + const items = raw.Items || raw.items || [] + const total = raw.Total ?? raw.total ?? 0 + const list = (Array.isArray(items) ? items : []).map(item => ({ + id: item.Id || item.id || '', + name: item.Name || item.name || '', + days: item.Days || item.days || 0, + phone: item.Phone || item.phone || '', + level: item.Level || item.level || '', + lastVisit: (() => { + const lv = item.LastVisit || item.lastVisit + if (!lv) return '--' + const d = new Date(typeof lv === 'number' ? lv : lv) + if (isNaN(d.getTime())) return '--' + return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}` + })(), + balance: item.Balance || item.balance || 0 + })) + if (reset) { + this.sleepingMembers = list + } else { + this.sleepingMembers = [...this.sleepingMembers, ...list] + } + this.sleepingTotal = total > 0 ? total : this.sleepingMembers.length + } catch (e) { + console.error('[沉睡提醒] 报错:', e.message || e) + } finally { + this.sleepingLoading = false + } + }, + handleSleepingScroll(e) { + const el = e.target + if (el.scrollTop + el.clientHeight >= el.scrollHeight - 50) { + if (this.sleepingMembers.length < this.sleepingTotal && !this.sleepingLoading) { + this.sleepingPage++ + this.loadSleepingMembers() + } + } + }, handleContactCancel() { this.activeTodoId = '' }, @@ -1650,6 +1748,7 @@ export default { } else { this.birthdayMonth-- } + this.loadBirthdayData() }, nextBirthdayMonth() { this.selectedBirthdayDay = null @@ -1659,6 +1758,7 @@ export default { } else { this.birthdayMonth++ } + this.loadBirthdayData() }, levelDotColor(level) { const map = { 'A++': '#ef4444', 'A+': '#f59e0b', 'A': '#d97706', 'B': '#22c55e', 'C': '#3b82f6', 'D': '#94a3b8' } @@ -2595,6 +2695,8 @@ export default { flex: 0 0 400px; position: relative; z-index: 1; + min-height: 0; + height: 100%; } /* 提醒面板布局:独立四角圆角卡片 */ @@ -2654,10 +2756,15 @@ export default { flex: 1; min-height: 0; overflow: hidden; + position: relative; } .reminder-center-panel .today-card ::v-deep .el-tab-pane { - height: 100%; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; display: flex; flex-direction: column; } diff --git a/项目文档相关/sql/2026-06-16/公司日历菜单配置.sql b/项目文档相关/sql/2026-06-16/公司日历菜单配置.sql new file mode 100644 index 0000000..91f2f01 --- /dev/null +++ b/项目文档相关/sql/2026-06-16/公司日历菜单配置.sql @@ -0,0 +1,18 @@ +-- 公司日历菜单配置脚本 + +SET @AdminRoleId = '94e3a9bb0fce4547886972998fddba1c'; -- 系统管理员角色ID + +-- 1. 清理旧数据 (防止重复执行报错) +DELETE FROM BASE_MODULE WHERE F_Id IN ('company-calendar', 'company-calendar-type'); +DELETE FROM BASE_AUTHORIZE WHERE F_ItemId IN ('company-calendar', 'company-calendar-type'); + +-- 2. 创建菜单: 公司日历 (父级: 扩展功能 725873504657868037) +-- 请根据实际的父级菜单ID修改 F_ParentId +INSERT INTO BASE_MODULE (F_Id, F_ParentId, F_Type, F_FullName, F_EnCode, F_UrlAddress, F_Icon, F_SortCode, F_EnabledMark, F_Category, F_DeleteMark, F_LinkTarget, F_PropertyJson, F_IsButtonAuthorize, F_IsColumnAuthorize, F_IsDataAuthorize, F_IsFormAuthorize, F_CreatorTime) +VALUES +('company-calendar', '725873504657868037', 2, '公司日历', 'companyCalendar', 'extend/companyCalendar', 'icon-ym icon-ym-calendar', 12, 1, 'Web', NULL, '_self', '{"moduleId":"","iconBackgroundColor":"","isTree":0}', 1, 1, 1, 1, NOW()); + +-- 3. 授权给 系统管理员 角色 +INSERT INTO BASE_AUTHORIZE (F_Id, F_ItemType, F_ItemId, F_ObjectType, F_ObjectId, F_SortCode, F_CreatorTime, F_CreatorUserId) +VALUES +(REPLACE(UUID(), '-', ''), 'module', 'company-calendar', 'Role', @AdminRoleId, 1, NOW(), 'admin'); diff --git a/项目文档相关/sql/2026-06-16/创建公司日历事件类型表.sql b/项目文档相关/sql/2026-06-16/创建公司日历事件类型表.sql new file mode 100644 index 0000000..0a4f78b --- /dev/null +++ b/项目文档相关/sql/2026-06-16/创建公司日历事件类型表.sql @@ -0,0 +1,22 @@ +-- 创建公司日历事件类型配置表 +CREATE TABLE IF NOT EXISTS `lq_company_calendar_type` ( + `F_Id` varchar(50) NOT NULL COMMENT '主键ID', + `TypeName` varchar(50) NOT NULL COMMENT '类型名称', + `TypeCode` varchar(50) NOT NULL COMMENT '类型编码', + `Color` varchar(20) NOT NULL DEFAULT '#3b82f6' COMMENT '颜色', + `SortCode` int NOT NULL DEFAULT 0 COMMENT '排序码', + `IsEffective` int NOT NULL DEFAULT 1 COMMENT '是否有效:1-有效, 0-无效', + `CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人', + `CreateTime` datetime DEFAULT NULL COMMENT '创建时间', + `UpdateUser` varchar(50) DEFAULT NULL COMMENT '修改人', + `UpdateTime` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`F_Id`), + UNIQUE KEY `uk_type_code` (`TypeCode`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司日历事件类型配置表'; + +-- 插入默认事件类型 +INSERT INTO `lq_company_calendar_type` (`F_Id`, `TypeName`, `TypeCode`, `Color`, `SortCode`, `IsEffective`, `CreateTime`) VALUES +('1', '操作会', 'ops', '#6366f1', 1, 1, NOW()), +('2', '培训会', 'training', '#22c55e', 2, 1, NOW()), +('3', '全体大会', 'meeting', '#f97316', 3, 1, NOW()), +('4', '门店活动', 'activity', '#ec4899', 4, 1, NOW()); diff --git a/项目文档相关/sql/2026-06-16/创建公司日历表.sql b/项目文档相关/sql/2026-06-16/创建公司日历表.sql new file mode 100644 index 0000000..ceebc3b --- /dev/null +++ b/项目文档相关/sql/2026-06-16/创建公司日历表.sql @@ -0,0 +1,47 @@ +-- 创建公司日历事件表 +CREATE TABLE IF NOT EXISTS `lq_company_calendar` ( + `F_Id` varchar(50) NOT NULL COMMENT '主键ID', + `Title` varchar(200) NOT NULL COMMENT '事件标题', + `EventType` varchar(50) NOT NULL COMMENT '事件类型ID', + `StartTime` datetime NOT NULL COMMENT '开始时间', + `EndTime` datetime NOT NULL COMMENT '结束时间', + `Description` text DEFAULT NULL COMMENT '事件描述', + `StoreId` varchar(50) DEFAULT NULL COMMENT '门店ID', + `StoreName` varchar(100) DEFAULT NULL COMMENT '门店名称', + `Remark` text DEFAULT NULL COMMENT '备注', + `Attachments` text DEFAULT NULL COMMENT '附件JSON', + `IsEffective` int NOT NULL DEFAULT 1 COMMENT '是否有效:1-有效, 0-无效', + `CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人', + `CreateTime` datetime DEFAULT NULL COMMENT '创建时间', + `UpdateUser` varchar(50) DEFAULT NULL COMMENT '修改人', + `UpdateTime` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`F_Id`), + KEY `idx_event_type` (`EventType`), + KEY `idx_start_time` (`StartTime`), + KEY `idx_end_time` (`EndTime`), + KEY `idx_store_id` (`StoreId`), + KEY `idx_is_effective` (`IsEffective`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司日历事件表'; + +-- 创建公司日历事件类型配置表 +CREATE TABLE IF NOT EXISTS `lq_company_calendar_type` ( + `F_Id` varchar(50) NOT NULL COMMENT '主键ID', + `TypeName` varchar(50) NOT NULL COMMENT '类型名称', + `TypeCode` varchar(50) NOT NULL COMMENT '类型编码', + `Color` varchar(20) NOT NULL DEFAULT '#3b82f6' COMMENT '颜色', + `SortCode` int NOT NULL DEFAULT 0 COMMENT '排序码', + `IsEffective` int NOT NULL DEFAULT 1 COMMENT '是否有效:1-有效, 0-无效', + `CreateUser` varchar(50) DEFAULT NULL COMMENT '创建人', + `CreateTime` datetime DEFAULT NULL COMMENT '创建时间', + `UpdateUser` varchar(50) DEFAULT NULL COMMENT '修改人', + `UpdateTime` datetime DEFAULT NULL COMMENT '修改时间', + PRIMARY KEY (`F_Id`), + UNIQUE KEY `uk_type_code` (`TypeCode`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='公司日历事件类型配置表'; + +-- 插入默认事件类型 +INSERT INTO `lq_company_calendar_type` (`F_Id`, `TypeName`, `TypeCode`, `Color`, `SortCode`, `IsEffective`, `CreateTime`) VALUES +('1', '操作会', 'ops', '#6366f1', 1, 1, NOW()), +('2', '培训会', 'training', '#22c55e', 2, 1, NOW()), +('3', '全体大会', 'meeting', '#f97316', 3, 1, NOW()), +('4', '门店活动', 'activity', '#ec4899', 4, 1, NOW()); -- libgit2 0.21.4