Commit 86ba68cf106d2f32dc13e4e97cb07d8f49e87d28

Authored by 李宇
2 parents a0ca280e 786d4a7e

Merge branch 'master' of http://39.98.150.180/antissoft/lvqianmeiye_ERP

antis-ncc-admin/src/views/lqKhxx/Form.vue
1   -<template>
  1 +<template>
2 2 <el-dialog :title="!dataForm.id ? '新建客户信息' : isDetail ? '客户信息详情' : '编辑客户信息'" :close-on-click-modal="false"
3   - :visible.sync="visible" class="NCC-dialog NCC-dialog_center" lock-scroll width="1200px">
  3 + :visible.sync="visible" class="NCC-dialog NCC-dialog_center customer-form-dialog" lock-scroll width="1200px">
4 4 <el-row :gutter="20" class="compact-form">
5 5 <el-form ref="elForm" :model="dataForm" size="mini" label-width="100px" label-position="right"
6 6 :disabled="!!isDetail" :rules="rules">
  7 + <el-col :span="24">
  8 + <div class="form-section-title">基本信息</div>
  9 + </el-col>
7 10 <el-col :span="6">
8 11 <el-form-item label="客户名称" prop="khmc" required>
9 12 <el-input v-model="dataForm.khmc" placeholder="请输入客户姓名" clearable :style='{ "width": "100%" }'>
... ... @@ -30,13 +33,43 @@
30 33 </el-select>
31 34 </el-form-item>
32 35 </el-col>
33   - <el-col :span="6">
34   - <el-form-item label="出生日期" prop="yanglsr">
35   - <el-date-picker v-model="dataForm.yanglsr" placeholder="请选择出生日期" clearable
36   - :style='{ "width": "100%" }' type='date' format="yyyy-MM-dd" value-format="timestamp">
  36 + <el-col :span="24">
  37 + <div class="form-section-title">生日信息</div>
  38 + </el-col>
  39 + <el-col :span="6" class="birthday-field">
  40 + <el-form-item label="阳历生日" prop="yanglsr">
  41 + <el-date-picker v-model="dataForm.yanglsr" placeholder="请选择阳历生日" clearable
  42 + :style='{ "width": "100%" }' type='date' format="yyyy-MM-dd" value-format="timestamp"
  43 + @change="onYanglsrChange">
37 44 </el-date-picker>
38 45 </el-form-item>
39 46 </el-col>
  47 + <el-col :span="6" class="birthday-field">
  48 + <el-form-item label="阴历生日" prop="yinlsr" class="yinlsr-form-item">
  49 + <div class="yinlsr-selects">
  50 + <el-select v-model="yinlsrMonth" placeholder="月" clearable
  51 + @change="onYinlsrSelectChange">
  52 + <el-option v-for="item in lunarMonthOptions" :key="item.value" :label="item.label" :value="item.value" />
  53 + </el-select>
  54 + <el-select v-model="yinlsrDay" placeholder="日" clearable
  55 + @change="onYinlsrSelectChange">
  56 + <el-option v-for="item in lunarDayOptions" :key="item.value" :label="item.label" :value="item.value" />
  57 + </el-select>
  58 + </div>
  59 + <div v-if="dataForm.yanglsr" class="form-tip">选阳历可自动填入</div>
  60 + </el-form-item>
  61 + </el-col>
  62 + <el-col :span="6" class="birthday-field">
  63 + <el-form-item label="过生日类型" prop="birthdayType">
  64 + <el-select v-model="dataForm.birthdayType" placeholder="请选择" :style='{ "width": "100%" }'>
  65 + <el-option label="阳历生日" :value="0" />
  66 + <el-option label="农历生日" :value="1" />
  67 + </el-select>
  68 + </el-form-item>
  69 + </el-col>
  70 + <el-col :span="24">
  71 + <div class="form-section-title">门店与顾问</div>
  72 + </el-col>
40 73 <el-col :span="6">
41 74 <el-form-item label="归属门店" prop="gsmd">
42 75 <el-select v-model="dataForm.gsmd" placeholder="请选择" clearable :style='{"width":"100%"}' filterable >
... ... @@ -95,6 +128,9 @@
95 128 </el-select>
96 129 </el-form-item>
97 130 </el-col>
  131 + <el-col :span="24">
  132 + <div class="form-section-title">其他信息</div>
  133 + </el-col>
98 134 <el-col :span="6" v-if="dataForm.id">
99 135 <el-form-item label="拓客人员" prop="expandUserName">
100 136 <el-input v-model="dataForm.expandUserName" placeholder="无拓客人员" clearable :style='{ "width": "100%" }' readonly>
... ... @@ -170,6 +206,7 @@ export default {
170 206 bz: undefined,
171 207 yanglsr: undefined,
172 208 yinlsr: undefined,
  209 + birthdayType: 0,
173 210 ml: undefined,
174 211 expandUser: undefined,
175 212 expandUserName: undefined,
... ... @@ -208,6 +245,30 @@ export default {
208 245 fzgwOptions: [],
209 246 mrsOptions: [],
210 247 kdhyOptions: [],
  248 + yinlsrMonth: '',
  249 + yinlsrDay: '',
  250 + lunarMonthOptions: [
  251 + { label: '正月', value: '正月' }, { label: '二月', value: '二月' }, { label: '三月', value: '三月' },
  252 + { label: '四月', value: '四月' }, { label: '五月', value: '五月' }, { label: '六月', value: '六月' },
  253 + { label: '七月', value: '七月' }, { label: '八月', value: '八月' }, { label: '九月', value: '九月' },
  254 + { label: '十月', value: '十月' }, { label: '冬月', value: '冬月' }, { label: '腊月', value: '腊月' },
  255 + { label: '闰正月', value: '闰正月' }, { label: '闰二月', value: '闰二月' }, { label: '闰三月', value: '闰三月' },
  256 + { label: '闰四月', value: '闰四月' }, { label: '闰五月', value: '闰五月' }, { label: '闰六月', value: '闰六月' },
  257 + { label: '闰七月', value: '闰七月' }, { label: '闰八月', value: '闰八月' }, { label: '闰九月', value: '闰九月' },
  258 + { label: '闰十月', value: '闰十月' }, { label: '闰冬月', value: '闰冬月' }, { label: '闰腊月', value: '闰腊月' }
  259 + ],
  260 + lunarDayOptions: [
  261 + { label: '初一', value: '初一' }, { label: '初二', value: '初二' }, { label: '初三', value: '初三' },
  262 + { label: '初四', value: '初四' }, { label: '初五', value: '初五' }, { label: '初六', value: '初六' },
  263 + { label: '初七', value: '初七' }, { label: '初八', value: '初八' }, { label: '初九', value: '初九' },
  264 + { label: '初十', value: '初十' }, { label: '十一', value: '十一' }, { label: '十二', value: '十二' },
  265 + { label: '十三', value: '十三' }, { label: '十四', value: '十四' }, { label: '十五', value: '十五' },
  266 + { label: '十六', value: '十六' }, { label: '十七', value: '十七' }, { label: '十八', value: '十八' },
  267 + { label: '十九', value: '十九' }, { label: '二十', value: '二十' }, { label: '廿一', value: '廿一' },
  268 + { label: '廿二', value: '廿二' }, { label: '廿三', value: '廿三' }, { label: '廿四', value: '廿四' },
  269 + { label: '廿五', value: '廿五' }, { label: '廿六', value: '廿六' }, { label: '廿七', value: '廿七' },
  270 + { label: '廿八', value: '廿八' }, { label: '廿九', value: '廿九' }, { label: '三十', value: '三十' }
  271 + ]
211 272 }
212 273 },
213 274 computed: {},
... ... @@ -243,6 +304,63 @@ export default {
243 304 },
244 305 methods: {
245 306 // 更新推荐人验证规则
  307 + onYanglsrChange(val) {
  308 + if (val) {
  309 + const d = new Date(val)
  310 + const dateStr = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
  311 + request({
  312 + url: `/api/Extend/LqKhxx/solar-to-lunar`,
  313 + method: 'GET',
  314 + params: { date: dateStr }
  315 + }).then(res => {
  316 + const inner = res.data && res.data.data
  317 + if (res.code === 200 && inner && inner.yinlsr) {
  318 + this.dataForm.yinlsr = inner.yinlsr
  319 + this.parseYinlsrToSelect(inner.yinlsr)
  320 + }
  321 + })
  322 + } else {
  323 + this.dataForm.yinlsr = undefined
  324 + this.yinlsrMonth = ''
  325 + this.yinlsrDay = ''
  326 + }
  327 + },
  328 + onYinlsrSelectChange() {
  329 + if (this.yinlsrMonth && this.yinlsrDay) {
  330 + this.dataForm.yinlsr = this.yinlsrMonth + this.yinlsrDay
  331 + } else {
  332 + this.dataForm.yinlsr = undefined
  333 + }
  334 + },
  335 + parseYinlsrToSelect(yinlsr) {
  336 + if (!yinlsr || typeof yinlsr !== 'string') {
  337 + this.yinlsrMonth = ''
  338 + this.yinlsrDay = ''
  339 + return
  340 + }
  341 + const months = ['正月', '二月', '三月', '四月', '五月', '六月', '七月', '八月', '九月', '十月', '冬月', '腊月',
  342 + '闰正月', '闰二月', '闰三月', '闰四月', '闰五月', '闰六月', '闰七月', '闰八月', '闰九月', '闰十月', '闰冬月', '闰腊月']
  343 + const days = ['初一', '初二', '初三', '初四', '初五', '初六', '初七', '初八', '初九', '初十',
  344 + '十一', '十二', '十三', '十四', '十五', '十六', '十七', '十八', '十九', '二十',
  345 + '廿一', '廿二', '廿三', '廿四', '廿五', '廿六', '廿七', '廿八', '廿九', '三十']
  346 + let month = ''
  347 + let day = ''
  348 + for (const m of months) {
  349 + if (yinlsr.startsWith(m)) {
  350 + month = m
  351 + const rest = yinlsr.slice(m.length)
  352 + for (const d of days) {
  353 + if (rest === d || rest.startsWith(d)) {
  354 + day = d
  355 + break
  356 + }
  357 + }
  358 + break
  359 + }
  360 + }
  361 + this.yinlsrMonth = month
  362 + this.yinlsrDay = day
  363 + },
246 364 updateTjrRules(jdqd) {
247 365 console.log(jdqd)
248 366 // 移除现有的推荐人验证规则
... ... @@ -382,6 +500,7 @@ export default {
382 500 }).then(res => {
383 501 this.dataForm = res.data;
384 502 this.dataForm.khxf = this.dataForm.khxf?this.dataForm.khxf.split(','):[];
  503 + this.parseYinlsrToSelect(this.dataForm.yinlsr);
385 504 // 编辑时,根据归属门店加载负责顾问和美容师选项
386 505 if (this.dataForm.gsmd) {
387 506 this.getfzgwOptions();
... ... @@ -421,10 +540,13 @@ export default {
421 540 bz: undefined,
422 541 yanglsr: undefined,
423 542 yinlsr: undefined,
  543 + birthdayType: 0,
424 544 ml: undefined,
425 545 expandUser: undefined,
426 546 expandUserName: undefined,
427 547 }
  548 + this.yinlsrMonth = '';
  549 + this.yinlsrDay = '';
428 550 }
429 551 })
430 552 },
... ... @@ -435,6 +557,8 @@ export default {
435 557 type: 'warning'
436 558 }).then(() => {
437 559 this.$refs['elForm'].resetFields();
  560 + this.yinlsrMonth = '';
  561 + this.yinlsrDay = '';
438 562 this.$message.success('表单已重置');
439 563 }).catch(() => {
440 564 // 用户取消
... ... @@ -495,17 +619,49 @@ export default {
495 619 </script>
496 620  
497 621 <style scoped>
  622 +/* 现代化弹窗:圆角、柔和阴影、过渡 */
  623 +.customer-form-dialog >>> .el-dialog {
  624 + border-radius: 12px;
  625 + box-shadow: 0 12px 48px rgba(0, 0, 0, 0.12);
  626 + overflow: hidden;
  627 + transition: box-shadow 0.2s ease;
  628 +}
  629 +
  630 +.customer-form-dialog >>> .el-dialog__header {
  631 + padding: 18px 20px;
  632 + border-bottom: 1px solid #f0f2f5;
  633 + background: #fafbfc;
  634 +}
  635 +
  636 +.customer-form-dialog >>> .el-dialog__body {
  637 + padding: 20px 24px;
  638 + background: #fff;
  639 +}
  640 +
  641 +.customer-form-dialog >>> .el-dialog__footer {
  642 + padding: 16px 24px;
  643 + border-top: 1px solid #f0f2f5;
  644 + background: #fafbfc;
  645 +}
  646 +
  647 +/* 分组标题:柔和现代风格,保持主色 */
498 648 .form-section-title {
499 649 display: flex;
500 650 align-items: center;
501   - margin: 15px 0 10px 0;
502   - padding: 8px 12px;
503   - background: #409EFF;
  651 + margin: 20px 0 12px 0;
  652 + padding: 10px 14px;
  653 + background: linear-gradient(135deg, #409EFF 0%, #66b1ff 100%);
504 654 color: white;
505   - border-radius: 6px;
  655 + border-radius: 8px;
506 656 font-size: 14px;
507 657 font-weight: 600;
508   - box-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
  658 + letter-spacing: 0.3px;
  659 + box-shadow: 0 2px 8px rgba(64, 158, 255, 0.25);
  660 + transition: box-shadow 0.2s ease;
  661 +}
  662 +
  663 +.form-section-title:first-of-type {
  664 + margin-top: 0;
509 665 }
510 666  
511 667 .form-section-title i {
... ... @@ -515,10 +671,46 @@ export default {
515 671  
516 672 .el-form-item {
517 673 margin-bottom: 12px;
  674 + transition: opacity 0.15s ease;
518 675 }
519 676  
520 677 .compact-form .el-form-item {
521   - margin-bottom: 8px;
  678 + margin-bottom: 10px;
  679 +}
  680 +
  681 +/* 输入框 focus 过渡 */
  682 +.compact-form .el-input__inner,
  683 +.compact-form .el-textarea__inner {
  684 + transition: border-color 0.2s ease, box-shadow 0.2s ease;
  685 +}
  686 +
  687 +.form-tip {
  688 + font-size: 12px;
  689 + color: #909399;
  690 + margin-top: 4px;
  691 + line-height: 1.2;
  692 +}
  693 +
  694 +/* 生日信息行:阳历/阴历顶部对齐(内容可能多行),过生日类型居中对齐 */
  695 +.birthday-field .el-form-item {
  696 + align-items: flex-start;
  697 +}
  698 +
  699 +.birthday-field .el-form-item__label {
  700 + white-space: nowrap;
  701 +}
  702 +
  703 +/* 阴历生日:月日并排不换行 */
  704 +.yinlsr-selects {
  705 + display: flex;
  706 + flex-wrap: nowrap;
  707 + gap: 8px;
  708 + width: 100%;
  709 +}
  710 +
  711 +.yinlsr-selects .el-select {
  712 + flex: 1;
  713 + min-width: 0;
522 714 }
523 715  
524 716 .el-input-group__append {
... ... @@ -527,13 +719,23 @@ export default {
527 719 color: #606266;
528 720 }
529 721  
  722 +/* 底部按钮区:现代化间距与过渡 */
530 723 .dialog-footer {
531 724 text-align: right;
532   - padding: 20px 0;
  725 + padding: 16px 0 0;
533 726 }
534 727  
535 728 .dialog-footer .el-button {
536 729 margin-left: 10px;
  730 + transition: all 0.2s ease;
  731 +}
  732 +
  733 +.dialog-footer .el-button--primary {
  734 + box-shadow: 0 2px 6px rgba(64, 158, 255, 0.3);
  735 +}
  736 +
  737 +.dialog-footer .el-button--primary:hover {
  738 + box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
537 739 }
538 740  
539 741 /* 必填字段标识 */
... ... @@ -542,11 +744,12 @@ export default {
542 744 color: #f56c6c;
543 745 margin-right: 4px;
544 746 }
  747 +
545 748 .el-col {
546 749 margin-bottom: 10px;
547 750 }
548 751  
549   -/* 响应式布局 */
  752 +/* 响应式 */
550 753 @media (max-width: 768px) {
551 754 .el-col {
552 755 margin-bottom: 10px;
... ...
antis-ncc-admin/src/views/lqKhxx/detail-dialog.vue
... ... @@ -5,7 +5,7 @@
5 5 width="1400px"
6 6 :close-on-click-modal="false"
7 7 @close="handleClose"
8   - class="detail-dialog"
  8 + class="detail-dialog customer-detail-dialog"
9 9 >
10 10 <div v-loading="loading" class="detail-content">
11 11 <div v-if="detailData" class="detail-wrapper">
... ... @@ -251,7 +251,11 @@
251 251 </div>
252 252 <div class="info-item">
253 253 <span class="label">阴历生日:</span>
254   - <span class="value">{{ formatDate(detailData.yinlsr) || '无' }}</span>
  254 + <span class="value">{{ formatYinlsr(detailData.yinlsr) || '无' }}</span>
  255 + </div>
  256 + <div class="info-item">
  257 + <span class="label">过生日类型:</span>
  258 + <span class="value">{{ detailData.birthdayType === 1 ? '农历生日' : '阳历生日' }}</span>
255 259 </div>
256 260 </div>
257 261 </div>
... ... @@ -325,6 +329,11 @@ export default {
325 329 day: '2-digit'
326 330 })
327 331 },
  332 + formatYinlsr(val) {
  333 + if (!val) return null
  334 + if (typeof val === 'string' && !/^\d{4}-\d|^\d+$/.test(val)) return val
  335 + return this.formatDate(val)
  336 + },
328 337 formatMoney(amount) {
329 338 if (!amount && amount !== 0) return '¥0.00'
330 339 const num = parseFloat(amount)
... ... @@ -361,11 +370,31 @@ export default {
361 370 </script>
362 371  
363 372 <style lang="scss" scoped>
364   -.detail-dialog {
  373 +/* 现代化弹窗:与编辑表单风格统一 */
  374 +.customer-detail-dialog {
  375 + ::v-deep .el-dialog {
  376 + border-radius: 12px;
  377 + box-shadow: 0 12px 48px rgba(0, 0, 0, 0.12);
  378 + overflow: hidden;
  379 + }
  380 +
  381 + ::v-deep .el-dialog__header {
  382 + padding: 18px 20px;
  383 + border-bottom: 1px solid #f0f2f5;
  384 + background: #fafbfc;
  385 + }
  386 +
365 387 ::v-deep .el-dialog__body {
366   - padding: 20px;
  388 + padding: 20px 24px;
367 389 max-height: 70vh;
368 390 overflow-y: auto;
  391 + background: #fff;
  392 + }
  393 +
  394 + ::v-deep .el-dialog__footer {
  395 + padding: 16px 24px;
  396 + border-top: 1px solid #f0f2f5;
  397 + background: #fafbfc;
369 398 }
370 399 }
371 400  
... ... @@ -379,17 +408,20 @@ export default {
379 408 background: #fff;
380 409 border-radius: 8px;
381 410 overflow: hidden;
382   - border: 1px solid #EBEEF5;
  411 + border: 1px solid #ebeef5;
  412 + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
383 413  
384 414 .section-title {
385   - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  415 + background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
386 416 color: #fff;
387 417 padding: 12px 20px;
388   - font-size: 16px;
  418 + font-size: 15px;
389 419 font-weight: 600;
  420 + letter-spacing: 0.3px;
390 421 display: flex;
391 422 align-items: center;
392 423 gap: 8px;
  424 + box-shadow: 0 2px 8px rgba(64, 158, 255, 0.25);
393 425  
394 426 i {
395 427 font-size: 18px;
... ...
netcore/ExportFiles/合作成本表 (202601).xlsx 0 → 100644
No preview for this file type
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxBirthdayOutput.cs
... ... @@ -50,7 +50,12 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
50 50 /// <summary>
51 51 /// 阴历生日
52 52 /// </summary>
53   - public DateTime? yinlsr { get; set; }
  53 + public string yinlsr { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 生日类型(0-阳历生日,1-农历生日)
  57 + /// </summary>
  58 + public int birthdayType { get; set; }
54 59  
55 60 /// <summary>
56 61 /// 生日日期(用于日历显示,格式:MM-DD)
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxCrInput.cs
1   -using System;
  1 +using System;
2 2 using System.Collections.Generic;
3 3 using Newtonsoft.Json;
4 4  
... ... @@ -86,10 +86,14 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
86 86 public DateTime? yanglsr { get; set; }
87 87  
88 88 /// <summary>
89   - /// 阴历生日
  89 + /// 阴历生日(格式:腊月十九、四月廿一等)
90 90 /// </summary>
91   - public DateTime? yinlsr { get; set; }
  91 + public string yinlsr { get; set; }
92 92  
  93 + /// <summary>
  94 + /// 生日类型(0-阳历生日,1-农历生日)
  95 + /// </summary>
  96 + public int birthdayType { get; set; } = 0;
93 97  
94 98 /// <summary>
95 99 /// 拓客人员
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxInfoOutput.cs
1   -using System;
  1 +using System;
2 2 using System.Collections.Generic;
3 3  
4 4 namespace NCC.Extend.Entitys.Dto.LqKhxx
... ... @@ -103,7 +103,12 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
103 103 /// <summary>
104 104 /// 阴历生日
105 105 /// </summary>
106   - public DateTime? yinlsr { get; set; }
  106 + public string yinlsr { get; set; }
  107 +
  108 + /// <summary>
  109 + /// 生日类型(0-阳历生日,1-农历生日)
  110 + /// </summary>
  111 + public int birthdayType { get; set; }
107 112  
108 113 /// <summary>
109 114 /// 添加时间
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/LqKhxxListOutput.cs
1   -using System;
  1 +using System;
2 2 using System.Collections.Generic;
3 3 using NCC.Code;
4 4 using NCC.Extend.Entitys.Enum;
... ... @@ -121,7 +121,12 @@ namespace NCC.Extend.Entitys.Dto.LqKhxx
121 121 /// <summary>
122 122 /// 阴历生日
123 123 /// </summary>
124   - public DateTime? yinlsr { get; set; }
  124 + public string yinlsr { get; set; }
  125 +
  126 + /// <summary>
  127 + /// 生日类型(0-阳历生日,1-农历生日)
  128 + /// </summary>
  129 + public int birthdayType { get; set; }
125 130  
126 131 /// <summary>
127 132 /// 年龄
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqXhHyhk/LqXhHyhkNoFeedbackListQueryInput.cs 0 → 100644
  1 +using NCC.Common.Filter;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqXhHyhk
  4 +{
  5 + /// <summary>
  6 + /// 无耗卡日志的耗卡记录列表查询输入
  7 + /// </summary>
  8 + public class LqXhHyhkNoFeedbackListQueryInput : PageInputBase
  9 + {
  10 + /// <summary>
  11 + /// 耗卡日期范围(格式:开始时间,结束时间,如:2025-01-01,2025-01-31)
  12 + /// </summary>
  13 + public string hksj { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 会员姓名(模糊匹配)
  17 + /// </summary>
  18 + public string hymc { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 会员电话(模糊匹配)
  22 + /// </summary>
  23 + public string memberPhone { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 门店ID(可选)
  27 + /// </summary>
  28 + public string md { get; set; }
  29 + }
  30 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Entity/lq_khxx/LqKhxxEntity.cs
1   -using System;
  1 +using System;
2 2 using NCC.Common.Const;
3 3 using NCC.Extend.Entitys.Enum;
4 4 using SqlSugar;
... ... @@ -110,10 +110,16 @@ namespace NCC.Extend.Entitys.lq_khxx
110 110 public DateTime? Yanglsr { get; set; }
111 111  
112 112 /// <summary>
113   - /// 阴历生日
  113 + /// 阴历生日(格式:腊月十九、四月廿一等,便于直接阅读)
114 114 /// </summary>
115 115 [SugarColumn(ColumnName = "yinlsr")]
116   - public DateTime? Yinlsr { get; set; }
  116 + public string Yinlsr { get; set; }
  117 +
  118 + /// <summary>
  119 + /// 生日类型(0-阳历生日,1-农历生日)
  120 + /// </summary>
  121 + [SugarColumn(ColumnName = "F_BirthdayType")]
  122 + public int BirthdayType { get; set; } = 0;
117 123  
118 124 /// <summary>
119 125 /// 添加时间
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Enum/BirthdayTypeEnum.cs 0 → 100644
  1 +using System.ComponentModel;
  2 +
  3 +namespace NCC.Extend.Entitys.Enum
  4 +{
  5 + /// <summary>
  6 + /// 生日类型(会员过阳历生日还是农历生日)
  7 + /// </summary>
  8 + public enum BirthdayTypeEnum
  9 + {
  10 + /// <summary>
  11 + /// 阳历生日
  12 + /// </summary>
  13 + [Description("阳历生日")]
  14 + 阳历 = 0,
  15 +
  16 + /// <summary>
  17 + /// 农历生日
  18 + /// </summary>
  19 + [Description("农历生日")]
  20 + 农历 = 1
  21 + }
  22 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqCooperationCostService.cs
... ... @@ -448,13 +448,13 @@ namespace NCC.Extend.LqCooperationCost
448 448 // 使用ExcelImportHelper读取Excel文件(第一行为标题行)
449 449 var dataTable = ExcelImportHelper.ToDataTable(tempFilePath, 0, 0);
450 450  
451   - if (dataTable.Rows.Count <= 1)
  451 + if (dataTable.Rows.Count == 0)
452 452 {
453 453 throw NCCException.Oh("Excel文件中没有数据行(至少需要标题行和一行数据)");
454 454 }
455 455  
456   - // 从第1行开始读取数据(跳过标题行)
457   - for (int i = 1; i < dataTable.Rows.Count; i++)
  456 + // ExcelImportHelper 已跳过标题行,从第0行开始即为第一行数据
  457 + for (int i = 0; i < dataTable.Rows.Count; i++)
458 458 {
459 459 try
460 460 {
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
1 1 using System;
2 2 using System.Collections.Generic;
  3 +using System.Globalization;
3 4 using System.IO;
4 5 using System.Linq;
5 6 using System.Text;
... ... @@ -72,6 +73,21 @@ namespace NCC.Extend.LqKhxx
72 73  
73 74 #region 客户资料
74 75 /// <summary>
  76 + /// 阳历转农历(用于前端选择阳历后自动填入农历生日)
  77 + /// </summary>
  78 + /// <param name="date">阳历日期,格式 yyyy-MM-dd</param>
  79 + /// <returns>农历字符串,如 腊月十九</returns>
  80 + [HttpGet("solar-to-lunar")]
  81 + [AllowAnonymous]
  82 + public object SolarToLunar([FromQuery] string date)
  83 + {
  84 + if (string.IsNullOrEmpty(date) || !DateTime.TryParse(date, out var solarDate))
  85 + return new { code = 400, msg = "日期格式错误,请使用 yyyy-MM-dd" };
  86 + var yinlsr = SolarToLunarString(solarDate);
  87 + return new { code = 200, msg = "成功", data = new { yinlsr } };
  88 + }
  89 +
  90 + /// <summary>
75 91 /// 获取客户资料
76 92 /// </summary>
77 93 /// <param name="id">参数</param>
... ... @@ -200,6 +216,7 @@ namespace NCC.Extend.LqKhxx
200 216 bz = it.Bz,
201 217 yanglsr = it.Yanglsr,
202 218 yinlsr = it.Yinlsr,
  219 + birthdayType = it.BirthdayType,
203 220 createTime = it.CreateTime,
204 221 expandUser = it.ExpandUser,
205 222 mainHealthUser = it.MainHealthUser,
... ... @@ -256,6 +273,11 @@ namespace NCC.Extend.LqKhxx
256 273 entity.Id = YitIdHelper.NextId().ToString();
257 274 entity.Dah = "LQ" + DateTime.Now.ToString("yyyyMMddHHmmssfff");
258 275 entity.Zcsj = DateTime.Now;
  276 + // 阳历生日转农历:若填写了 yanglsr,自动转换并保存到 yinlsr(格式:腊月十九、四月廿一等,便于直接阅读)
  277 + if (input.yanglsr.HasValue)
  278 + {
  279 + entity.Yinlsr = SolarToLunarString(input.yanglsr.Value);
  280 + }
259 281 // 处理客户消费字段:将数组转换为逗号分隔的字符串
260 282 if (input.khxf != null && input.khxf.Count > 0)
261 283 {
... ... @@ -284,10 +306,6 @@ namespace NCC.Extend.LqKhxx
284 306 List<string> queryYanglsr = input.yanglsr != null ? input.yanglsr.Split(',').ToObeject<List<string>>() : null;
285 307 DateTime? startYanglsr = queryYanglsr != null ? Ext.GetDateTime(queryYanglsr.First()) : null;
286 308 DateTime? endYanglsr = queryYanglsr != null ? Ext.GetDateTime(queryYanglsr.Last()) : null;
287   - List<string> queryYinlsr = input.yinlsr != null ? input.yinlsr.Split(',').ToObeject<List<string>>() : null;
288   - DateTime? startYinlsr = queryYinlsr != null ? Ext.GetDateTime(queryYinlsr.First()) : null;
289   - DateTime? endYinlsr = queryYinlsr != null ? Ext.GetDateTime(queryYinlsr.Last()) : null;
290   -
291 309 // 处理沉睡天数范围
292 310 List<string> querySleepDaysRange = input.SleepDaysRange != null ? input.SleepDaysRange.Split(',').ToObeject<List<string>>() : null;
293 311 int? minSleepDays = null;
... ... @@ -338,8 +356,7 @@ namespace NCC.Extend.LqKhxx
338 356 .WhereIF(maxRemainingRights.HasValue, p => p.RemainingRightsAmount <= maxRemainingRights.Value)
339 357 .WhereIF(queryYanglsr != null, p => p.Yanglsr >= new DateTime(startYanglsr.ToDate().Year, startYanglsr.ToDate().Month, startYanglsr.ToDate().Day, 0, 0, 0))
340 358 .WhereIF(queryYanglsr != null, p => p.Yanglsr <= new DateTime(endYanglsr.ToDate().Year, endYanglsr.ToDate().Month, endYanglsr.ToDate().Day, 23, 59, 59))
341   - .WhereIF(queryYinlsr != null, p => p.Yinlsr >= new DateTime(startYinlsr.ToDate().Year, startYinlsr.ToDate().Month, startYinlsr.ToDate().Day, 0, 0, 0))
342   - .WhereIF(queryYinlsr != null, p => p.Yinlsr <= new DateTime(endYinlsr.ToDate().Year, endYinlsr.ToDate().Month, endYinlsr.ToDate().Day, 23, 59, 59))
  359 + .WhereIF(!string.IsNullOrEmpty(input.yinlsr), p => p.Yinlsr != null && p.Yinlsr.Contains(input.yinlsr))
343 360 .Select(it => new LqKhxxListOutput
344 361 {
345 362 id = it.Id,
... ... @@ -360,6 +377,7 @@ namespace NCC.Extend.LqKhxx
360 377 bz = it.Bz,
361 378 yanglsr = it.Yanglsr,
362 379 yinlsr = it.Yinlsr,
  380 + birthdayType = it.BirthdayType,
363 381 createTime = it.CreateTime,
364 382 expandUser = it.ExpandUser,
365 383 mainHealthUser = it.MainHealthUser,
... ... @@ -412,10 +430,6 @@ namespace NCC.Extend.LqKhxx
412 430 List<string> queryYanglsr = input.yanglsr != null ? input.yanglsr.Split(',').ToObeject<List<string>>() : null;
413 431 DateTime? startYanglsr = queryYanglsr != null ? Ext.GetDateTime(queryYanglsr.First()) : null;
414 432 DateTime? endYanglsr = queryYanglsr != null ? Ext.GetDateTime(queryYanglsr.Last()) : null;
415   - List<string> queryYinlsr = input.yinlsr != null ? input.yinlsr.Split(',').ToObeject<List<string>>() : null;
416   - DateTime? startYinlsr = queryYinlsr != null ? Ext.GetDateTime(queryYinlsr.First()) : null;
417   - DateTime? endYinlsr = queryYinlsr != null ? Ext.GetDateTime(queryYinlsr.Last()) : null;
418   -
419 433 var data = await _db.Queryable<LqKhxxEntity>()
420 434 .Where(p => p.Khlx == "线索") // 固定查询线索池客户
421 435 .WhereIF(!string.IsNullOrEmpty(input.id), p => p.Id.Contains(input.id))
... ... @@ -436,8 +450,7 @@ namespace NCC.Extend.LqKhxx
436 450 .WhereIF(!string.IsNullOrEmpty(input.bz), p => p.Bz.Contains(input.bz))
437 451 .WhereIF(queryYanglsr != null, p => p.Yanglsr >= new DateTime(startYanglsr.ToDate().Year, startYanglsr.ToDate().Month, startYanglsr.ToDate().Day, 0, 0, 0))
438 452 .WhereIF(queryYanglsr != null, p => p.Yanglsr <= new DateTime(endYanglsr.ToDate().Year, endYanglsr.ToDate().Month, endYanglsr.ToDate().Day, 23, 59, 59))
439   - .WhereIF(queryYinlsr != null, p => p.Yinlsr >= new DateTime(startYinlsr.ToDate().Year, startYinlsr.ToDate().Month, startYinlsr.ToDate().Day, 0, 0, 0))
440   - .WhereIF(queryYinlsr != null, p => p.Yinlsr <= new DateTime(endYinlsr.ToDate().Year, endYinlsr.ToDate().Month, endYinlsr.ToDate().Day, 23, 59, 59))
  453 + .WhereIF(!string.IsNullOrEmpty(input.yinlsr), p => p.Yinlsr != null && p.Yinlsr.Contains(input.yinlsr))
441 454 .Select(it => new LqKhxxListOutput
442 455 {
443 456 id = it.Id,
... ... @@ -458,6 +471,7 @@ namespace NCC.Extend.LqKhxx
458 471 bz = it.Bz,
459 472 yanglsr = it.Yanglsr,
460 473 yinlsr = it.Yinlsr,
  474 + birthdayType = it.BirthdayType,
461 475 createTime = it.CreateTime,
462 476 expandUser = it.ExpandUser,
463 477 mainHealthUser = it.MainHealthUser,
... ... @@ -1119,7 +1133,11 @@ namespace NCC.Extend.LqKhxx
1119 1133 public async Task Update(string id, [FromBody] LqKhxxUpInput input)
1120 1134 {
1121 1135 var entity = input.Adapt<LqKhxxEntity>();
1122   -
  1136 + // 阳历生日转农历:若填写了 yanglsr,自动转换并保存到 yinlsr(格式:腊月十九、四月廿一等,便于直接阅读)
  1137 + if (input.yanglsr.HasValue)
  1138 + {
  1139 + entity.Yinlsr = SolarToLunarString(input.yanglsr.Value);
  1140 + }
1123 1141 // 处理客户消费字段:将数组转换为逗号分隔的字符串
1124 1142 if (input.khxf != null && input.khxf.Count > 0)
1125 1143 {
... ... @@ -3235,7 +3253,9 @@ WHERE kh.F_IsEffective = 1&quot;;
3235 3253 /// 获取门店未来30天过生日的会员列表
3236 3254 /// </summary>
3237 3255 /// <remarks>
3238   - /// 根据门店ID获取未来30天内过生日的会员信息,支持按阳历生日查询
  3256 + /// 根据门店ID获取未来30天内过生日的会员信息,支持阳历生日和农历生日
  3257 + /// - 阳历生日:直接按公历月日计算今年/明年生日
  3258 + /// - 农历生日:将今年/明年农历月日转为公历日期后判断是否在未来30天内(便于按公历日期查询提醒)
3239 3259 ///
3240 3260 /// 会员等级说明:0=D,1=C,2=B,3=A,4=A+,5=A++
3241 3261 ///
... ... @@ -3258,11 +3278,12 @@ WHERE kh.F_IsEffective = 1&quot;;
3258 3278 {
3259 3279 var now = DateTime.Now;
3260 3280 var endDate = now.AddDays(30);
3261   -
3262   - // 构建查询
  3281 + var cal = new ChineseLunisolarCalendar();
  3282 +
  3283 + // 构建查询:阳历或农历生日任一有值即可
3263 3284 var query = _db.Queryable<LqKhxxEntity>()
3264 3285 .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
3265   - .Where(x => x.Yanglsr != null); // 必须有阳历生日
  3286 + .Where(x => x.Yanglsr != null || !string.IsNullOrEmpty(x.Yinlsr));
3266 3287  
3267 3288 // 门店过滤
3268 3289 if (!string.IsNullOrEmpty(storeId))
... ... @@ -3275,36 +3296,61 @@ WHERE kh.F_IsEffective = 1&quot;;
3275 3296  
3276 3297 // 在内存中筛选未来30天过生日的会员
3277 3298 var upcomingBirthdays = new List<LqKhxxBirthdayOutput>();
3278   -
  3299 +
3279 3300 foreach (var member in members)
3280 3301 {
3281   - if (!member.Yanglsr.HasValue) continue;
3282   -
3283   - var birthday = member.Yanglsr.Value;
3284   -
3285   - // 计算今年和明年的生日
3286   - var thisYearBirthday = new DateTime(now.Year, birthday.Month, birthday.Day);
3287   - var nextYearBirthday = new DateTime(now.Year + 1, birthday.Month, birthday.Day);
  3302 + DateTime? upcomingBirthday = null;
  3303 + string birthdayDateStr;
  3304 + int age;
  3305 + DateTime? birthDateForAge = null;
3288 3306  
3289   - DateTime upcomingBirthday;
3290   -
3291   - // 判断生日是今年还是明年
3292   - if (thisYearBirthday >= now.Date && thisYearBirthday <= endDate.Date)
  3307 + // 优先使用阳历生日
  3308 + if (member.Yanglsr.HasValue)
3293 3309 {
3294   - upcomingBirthday = thisYearBirthday;
  3310 + var birthday = member.Yanglsr.Value;
  3311 + birthDateForAge = birthday;
  3312 + var thisYearBirthday = new DateTime(now.Year, birthday.Month, birthday.Day);
  3313 + var nextYearBirthday = new DateTime(now.Year + 1, birthday.Month, birthday.Day);
  3314 +
  3315 + if (thisYearBirthday >= now.Date && thisYearBirthday <= endDate.Date)
  3316 + upcomingBirthday = thisYearBirthday;
  3317 + else if (nextYearBirthday >= now.Date && nextYearBirthday <= endDate.Date)
  3318 + upcomingBirthday = nextYearBirthday;
  3319 +
  3320 + birthdayDateStr = birthday.ToString("MM-dd");
3295 3321 }
3296   - else if (nextYearBirthday >= now.Date && nextYearBirthday <= endDate.Date)
  3322 + // 无阳历有农历:将今年/明年农历生日转为公历后判断
  3323 + else if (!string.IsNullOrEmpty(member.Yinlsr))
3297 3324 {
3298   - upcomingBirthday = nextYearBirthday;
  3325 + int currentLunarYear = cal.GetYear(now);
  3326 + int nextLunarYear = currentLunarYear + 1;
  3327 + var (lunarMonth, lunarDay) = LunarStringToMonthDay(member.Yinlsr, cal, currentLunarYear);
  3328 + if (!lunarMonth.HasValue || !lunarDay.HasValue)
  3329 + {
  3330 + continue;
  3331 + }
  3332 +
  3333 + DateTime? thisYearSolar = LunarToSolar(cal, currentLunarYear, lunarMonth.Value, lunarDay.Value);
  3334 + DateTime? nextYearSolar = LunarToSolar(cal, nextLunarYear, lunarMonth.Value, lunarDay.Value);
  3335 +
  3336 + if (thisYearSolar.HasValue && thisYearSolar.Value >= now.Date && thisYearSolar.Value <= endDate.Date)
  3337 + upcomingBirthday = thisYearSolar;
  3338 + else if (nextYearSolar.HasValue && nextYearSolar.Value >= now.Date && nextYearSolar.Value <= endDate.Date)
  3339 + upcomingBirthday = nextYearSolar;
  3340 +
  3341 + birthdayDateStr = member.Yinlsr;
3299 3342 }
3300 3343 else
3301 3344 {
3302   - continue; // 不在未来30天内
  3345 + continue;
3303 3346 }
3304 3347  
3305   - // 计算年龄
3306   - var age = now.Year - birthday.Year;
3307   - if (now.Month < birthday.Month || (now.Month == birthday.Month && now.Day < birthday.Day))
  3348 + if (!upcomingBirthday.HasValue) continue;
  3349 +
  3350 + // 计算年龄(有阳历用阳历,否则用农历年近似)
  3351 + age = birthDateForAge.HasValue ? now.Year - birthDateForAge.Value.Year : 0;
  3352 + if (age > 0 && birthDateForAge.HasValue &&
  3353 + (now.Month < birthDateForAge.Value.Month || (now.Month == birthDateForAge.Value.Month && now.Day < birthDateForAge.Value.Day)))
3308 3354 {
3309 3355 age--;
3310 3356 }
... ... @@ -3319,8 +3365,9 @@ WHERE kh.F_IsEffective = 1&quot;;
3319 3365 gsmd = member.Gsmd,
3320 3366 yanglsr = member.Yanglsr,
3321 3367 yinlsr = member.Yinlsr,
3322   - birthdayDate = birthday.ToString("MM-dd"),
3323   - birthdayFullDate = upcomingBirthday,
  3368 + birthdayType = member.BirthdayType,
  3369 + birthdayDate = birthdayDateStr,
  3370 + birthdayFullDate = upcomingBirthday.Value,
3324 3371 consumeLevel = member.ConsumeLevel,
3325 3372 consumeLevelName = GetConsumeLevelName(member.ConsumeLevel),
3326 3373 remainingRightsAmount = member.RemainingRightsAmount,
... ... @@ -3387,5 +3434,124 @@ WHERE kh.F_IsEffective = 1&quot;;
3387 3434  
3388 3435 #endregion
3389 3436  
  3437 + #region 阳历转农历
  3438 + private static readonly string[] LunarMonthNames = { "正月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "冬月", "腊月" };
  3439 + private static readonly string[] LunarDayNames = { "初一", "初二", "初三", "初四", "初五", "初六", "初七", "初八", "初九", "初十", "十一", "十二", "十三", "十四", "十五", "十六", "十七", "十八", "十九", "二十", "廿一", "廿二", "廿三", "廿四", "廿五", "廿六", "廿七", "廿八", "廿九", "三十" };
  3440 +
  3441 + /// <summary>
  3442 + /// 将阳历日期转换为农历字符串(格式:腊月十九、四月廿一等),便于直接阅读
  3443 + /// </summary>
  3444 + /// <param name="solarDate">阳历日期</param>
  3445 + /// <returns>农历字符串,若转换失败返回 null</returns>
  3446 + private static string SolarToLunarString(DateTime solarDate)
  3447 + {
  3448 + try
  3449 + {
  3450 + var cal = new ChineseLunisolarCalendar();
  3451 + int year = cal.GetYear(solarDate);
  3452 + int month = cal.GetMonth(solarDate);
  3453 + int day = cal.GetDayOfMonth(solarDate);
  3454 + int leapMonth = cal.GetLeapMonth(year);
  3455 +
  3456 + string monthStr;
  3457 + if (leapMonth > 0 && month == leapMonth + 1)
  3458 + {
  3459 + monthStr = "闰" + LunarMonthNames[leapMonth - 1];
  3460 + }
  3461 + else if (leapMonth > 0 && month > leapMonth + 1)
  3462 + {
  3463 + monthStr = LunarMonthNames[month - 2];
  3464 + }
  3465 + else
  3466 + {
  3467 + monthStr = LunarMonthNames[month - 1];
  3468 + }
  3469 +
  3470 + string dayStr = day >= 1 && day <= 30 ? LunarDayNames[day - 1] : day.ToString();
  3471 + return monthStr + dayStr;
  3472 + }
  3473 + catch (ArgumentOutOfRangeException)
  3474 + {
  3475 + return null;
  3476 + }
  3477 + }
  3478 +
  3479 + /// <summary>
  3480 + /// 将农历字符串(腊月十九、四月廿一等)解析为日历月、日数字(闰月需结合年份转换为日历月份)
  3481 + /// </summary>
  3482 + /// <param name="lunarStr">农历字符串</param>
  3483 + /// <param name="cal">农历日历</param>
  3484 + /// <param name="lunarYear">农历年(用于闰月转换)</param>
  3485 + /// <returns>(日历月, 日),解析失败返回 (null, null)</returns>
  3486 + private static (int? month, int? day) LunarStringToMonthDay(string lunarStr, ChineseLunisolarCalendar cal, int lunarYear)
  3487 + {
  3488 + if (string.IsNullOrEmpty(lunarStr)) return (null, null);
  3489 +
  3490 + if (DateTime.TryParse(lunarStr, out var dt))
  3491 + {
  3492 + return (dt.Month, dt.Day);
  3493 + }
  3494 +
  3495 + bool isLeap = lunarStr.StartsWith("闰");
  3496 + int startIdx = isLeap ? 1 : 0;
  3497 +
  3498 + int baseMonth = 0;
  3499 + for (int i = 0; i < LunarMonthNames.Length; i++)
  3500 + {
  3501 + if (lunarStr.IndexOf(LunarMonthNames[i], startIdx, StringComparison.Ordinal) == startIdx)
  3502 + {
  3503 + baseMonth = i + 1;
  3504 + startIdx += LunarMonthNames[i].Length;
  3505 + break;
  3506 + }
  3507 + }
  3508 + if (baseMonth == 0) return (null, null);
  3509 +
  3510 + string dayPart = lunarStr.Substring(startIdx);
  3511 + int day = 0;
  3512 + for (int i = 0; i < LunarDayNames.Length; i++)
  3513 + {
  3514 + if (dayPart == LunarDayNames[i] || dayPart.StartsWith(LunarDayNames[i]))
  3515 + {
  3516 + day = i + 1;
  3517 + break;
  3518 + }
  3519 + }
  3520 + if (day == 0) return (null, null);
  3521 +
  3522 + int leapMonth = cal.GetLeapMonth(lunarYear);
  3523 + int calendarMonth;
  3524 + if (isLeap)
  3525 + {
  3526 + calendarMonth = baseMonth + 1;
  3527 + }
  3528 + else if (leapMonth > 0 && baseMonth > leapMonth)
  3529 + {
  3530 + calendarMonth = baseMonth + 1;
  3531 + }
  3532 + else
  3533 + {
  3534 + calendarMonth = baseMonth;
  3535 + }
  3536 +
  3537 + return (calendarMonth, day);
  3538 + }
  3539 +
  3540 + /// <summary>
  3541 + /// 将农历日期转换为公历日期,用于生日提醒时按公历日期查询
  3542 + /// </summary>
  3543 + private static DateTime? LunarToSolar(ChineseLunisolarCalendar cal, int lunarYear, int lunarMonth, int lunarDay)
  3544 + {
  3545 + try
  3546 + {
  3547 + return cal.ToDateTime(lunarYear, lunarMonth, lunarDay, 0, 0, 0, 0);
  3548 + }
  3549 + catch (ArgumentOutOfRangeException)
  3550 + {
  3551 + return null;
  3552 + }
  3553 + }
  3554 + #endregion
  3555 +
3390 3556 }
3391 3557 }
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqTestService.cs 0 → 100644
  1 +using System;
  2 +using System.Threading.Tasks;
  3 +using Microsoft.AspNetCore.Authorization;
  4 +using Microsoft.AspNetCore.Mvc;
  5 +using NCC.Common.Core.Manager;
  6 +using NCC.Dependency;
  7 +using NCC.DynamicApiController;
  8 +
  9 +namespace NCC.Extend.LqTest
  10 +{
  11 + /// <summary>
  12 + /// 测试服务(用于验证接口可用性及自动委派流程)
  13 + /// </summary>
  14 + [ApiDescriptionSettings(Tag = "绿纤测试服务", Name = "LqTest", Order = 200)]
  15 + [Route("api/Extend/[controller]")]
  16 + public class LqTestService : IDynamicApiController, ITransient
  17 + {
  18 + private readonly IUserManager _userManager;
  19 +
  20 + /// <summary>
  21 + /// 初始化一个<see cref="LqTestService"/>类型的新实例
  22 + /// </summary>
  23 + public LqTestService(IUserManager userManager)
  24 + {
  25 + _userManager = userManager;
  26 + }
  27 +
  28 + /// <summary>
  29 + /// 健康检查 / 连通性测试
  30 + /// </summary>
  31 + /// <remarks>
  32 + /// 用于验证 API 服务是否正常运行,无需登录即可调用(若需鉴权可移除 AllowAnonymous)
  33 + ///
  34 + /// 示例请求:
  35 + /// ```http
  36 + /// GET /api/Extend/LqTest/Ping
  37 + /// ```
  38 + ///
  39 + /// 参数说明:无
  40 + /// </remarks>
  41 + /// <returns>服务状态信息</returns>
  42 + /// <response code="200">成功返回服务状态</response>
  43 + /// <response code="500">服务器错误</response>
  44 + [HttpGet("Ping")]
  45 + [AllowAnonymous]
  46 + public Task<object> Ping()
  47 + {
  48 + var result = new
  49 + {
  50 + success = true,
  51 + message = "绿纤 ERP 测试接口正常",
  52 + timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
  53 + service = "LqTestService"
  54 + };
  55 + return Task.FromResult<object>(result);
  56 + }
  57 +
  58 + /// <summary>
  59 + /// Hello World 测试接口
  60 + /// </summary>
  61 + /// <remarks>
  62 + /// 直接返回 hello world 字符串,用于简单连通性测试
  63 + /// </remarks>
  64 + /// <returns>hello world</returns>
  65 + /// <response code="200">成功返回</response>
  66 + [HttpGet("HelloWorld")]
  67 + [AllowAnonymous]
  68 + public Task<object> HelloWorld()
  69 + {
  70 + return Task.FromResult<object>(new { message = "hello world" });
  71 + }
  72 +
  73 + /// <summary>
  74 + /// Echo 回显测试接口
  75 + /// </summary>
  76 + /// <remarks>
  77 + /// 返回服务器时间与随机数,用于验证接口连通性
  78 + ///
  79 + /// 示例请求:
  80 + /// ```http
  81 + /// GET /api/Extend/LqTest/Echo
  82 + /// ```
  83 + /// </remarks>
  84 + /// <returns>服务器时间与随机数</returns>
  85 + /// <response code="200">成功返回</response>
  86 + [HttpGet("Echo")]
  87 + [AllowAnonymous]
  88 + public Task<object> Echo()
  89 + {
  90 + var random = new Random();
  91 + var result = new
  92 + {
  93 + message = "echo ok",
  94 + serverTime = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"),
  95 + randomValue = random.Next(1, 10000)
  96 + };
  97 + return Task.FromResult<object>(result);
  98 + }
  99 +
  100 + /// <summary>
  101 + /// 带鉴权的测试接口(需登录)
  102 + /// </summary>
  103 + /// <remarks>
  104 + /// 验证当前用户登录态,返回用户相关信息
  105 + ///
  106 + /// 示例请求:
  107 + /// ```http
  108 + /// GET /api/Extend/LqTest/WhoAmI
  109 + /// Authorization: Bearer {token}
  110 + /// ```
  111 + /// </remarks>
  112 + /// <returns>当前用户信息</returns>
  113 + /// <response code="200">成功返回用户信息</response>
  114 + /// <response code="401">未登录或 token 无效</response>
  115 + [HttpGet("WhoAmI")]
  116 + public async Task<object> WhoAmI()
  117 + {
  118 + var userId = _userManager.UserId;
  119 + var userInfo = await _userManager.GetUserInfo();
  120 + var result = new
  121 + {
  122 + success = true,
  123 + userId,
  124 + userName = userInfo?.userName ?? "未知",
  125 + account = userInfo?.userAccount ?? "未知",
  126 + timestamp = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")
  127 + };
  128 + return result;
  129 + }
  130 + }
  131 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqXhHyhkService.cs
... ... @@ -28,6 +28,7 @@ using NCC.Extend.Entitys.lq_kd_kdjlb;
28 28 using NCC.Extend.Entitys.lq_kd_pxmx;
29 29 using NCC.Extend.Entitys.lq_khxx;
30 30 using NCC.Extend.Entitys.lq_person_times_record;
  31 +using NCC.Extend.Entitys.lq_xh_feedback;
31 32 using NCC.Extend.Entitys.lq_xh_hyhk;
32 33 using NCC.Extend.Entitys.lq_xh_jksyj;
33 34 using NCC.Extend.Entitys.lq_xh_kjbsyj;
... ... @@ -521,6 +522,220 @@ namespace NCC.Extend.LqXhHyhk
521 522  
522 523 return PageResult<LqXhHyhkListOutput>.SqlSugarPageResult(data);
523 524 }
  525 +
  526 + /// <summary>
  527 + /// 获取无耗卡日志的耗卡记录列表
  528 + /// </summary>
  529 + /// <remarks>
  530 + /// 查询已发生耗卡但尚未填写耗卡日志(服务日志)的记录,支持按耗卡日期、会员姓名、电话等条件筛选
  531 + ///
  532 + /// 示例请求:
  533 + /// GET /api/Extend/LqXhHyhk/Actions/GetNoFeedbackList?currentPage=1&amp;pageSize=10&amp;hksj=2025-01-01,2025-01-31&amp;hymc=张三&amp;memberPhone=138
  534 + ///
  535 + /// 参数说明:
  536 + /// - hksj: 耗卡日期范围(格式:开始时间,结束时间,如:2025-01-01,2025-01-31)
  537 + /// - hymc: 会员姓名(模糊匹配)
  538 + /// - memberPhone: 会员电话(模糊匹配)
  539 + /// - keyword: 关键字(可选,搜索会员名称、账号、手机号)
  540 + /// - md: 门店ID(可选)
  541 + /// - currentPage: 当前页码
  542 + /// - pageSize: 每页数量
  543 + /// - sidx: 排序字段(默认hksj)
  544 + /// - sort: 排序方式(asc/desc,默认desc)
  545 + ///
  546 + /// 返回说明:
  547 + /// - 返回格式与GetList接口相同,包含耗卡基本信息、耗卡明细、健康师业绩、科技部老师业绩
  548 + /// </remarks>
  549 + /// <param name="input">查询参数</param>
  550 + /// <returns>无耗卡日志的耗卡记录列表(分页)</returns>
  551 + /// <response code="200">查询成功</response>
  552 + /// <response code="400">参数错误</response>
  553 + /// <response code="500">服务器内部错误</response>
  554 + [HttpGet("Actions/GetNoFeedbackList")]
  555 + public async Task<dynamic> GetNoFeedbackList([FromQuery] LqXhHyhkNoFeedbackListQueryInput input)
  556 + {
  557 + var sidx = input.sidx == null ? "hksj" : input.sidx;
  558 + var sort = string.IsNullOrEmpty(input.sort) ? "DESC" : input.sort;
  559 + DateTime? startHksj = null;
  560 + DateTime? endHksj = null;
  561 + if (!string.IsNullOrEmpty(input.hksj))
  562 + {
  563 + var parts = input.hksj.Split(',');
  564 + if (parts.Length >= 1 && DateTime.TryParse(parts[0].Trim(), out var start))
  565 + startHksj = start;
  566 + if (parts.Length >= 2 && DateTime.TryParse(parts[1].Trim(), out var end))
  567 + endHksj = end;
  568 + }
  569 + var queryHksj = startHksj != null || endHksj != null;
  570 +
  571 + // 耗卡表 LEFT JOIN 耗卡反馈表,筛选 feedback.Id 为 NULL 的记录(即无耗卡日志)
  572 + var query = _db.Queryable<LqXhHyhkEntity, LqXhFeedbackEntity>((hyhk, feedback) => new JoinQueryInfos(
  573 + JoinType.Left, hyhk.Id == feedback.ConsumeId))
  574 + .Where((hyhk, feedback) => feedback.Id == null)
  575 + .Where((hyhk, feedback) => hyhk.IsEffective == StatusEnum.有效.GetHashCode())
  576 + .WhereIF(!string.IsNullOrEmpty(input.keyword), (hyhk, feedback) => hyhk.Hymc.Contains(input.keyword) || hyhk.Hyzh.Contains(input.keyword) || hyhk.MemberPhone.Contains(input.keyword))
  577 + .WhereIF(!string.IsNullOrEmpty(input.md), (hyhk, feedback) => hyhk.Md == input.md)
  578 + .WhereIF(!string.IsNullOrEmpty(input.hymc), (hyhk, feedback) => hyhk.Hymc.Contains(input.hymc))
  579 + .WhereIF(!string.IsNullOrEmpty(input.memberPhone), (hyhk, feedback) => hyhk.MemberPhone.Contains(input.memberPhone))
  580 + .WhereIF(queryHksj && startHksj.HasValue, (hyhk, feedback) => hyhk.Hksj >= new DateTime(startHksj.Value.Year, startHksj.Value.Month, startHksj.Value.Day, 0, 0, 0))
  581 + .WhereIF(queryHksj && endHksj.HasValue, (hyhk, feedback) => hyhk.Hksj <= new DateTime(endHksj.Value.Year, endHksj.Value.Month, endHksj.Value.Day, 23, 59, 59))
  582 + .Select((hyhk, feedback) => new LqXhHyhkListOutput
  583 + {
  584 + id = hyhk.Id,
  585 + md = hyhk.Md,
  586 + mdbh = hyhk.Mdbh,
  587 + mdmc = hyhk.Mdmc,
  588 + hy = hyhk.Hy,
  589 + hyzh = hyhk.Hyzh,
  590 + hymc = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == hyhk.Hy).Select(w => w.Khmc),
  591 + gklx = hyhk.Gklx,
  592 + xfje = SqlFunc.ToString(hyhk.Xfje),
  593 + sgfy = SqlFunc.ToString(hyhk.Sgfy),
  594 + sfykjb = hyhk.Sfykjb,
  595 + hksj = hyhk.Hksj,
  596 + czry = hyhk.Czry,
  597 + memberPhone = SqlFunc.Subqueryable<LqKhxxEntity>().Where(w => w.Id == hyhk.Hy).Select(w => w.Sjh),
  598 + isEffective = hyhk.IsEffective,
  599 + signatureFile = hyhk.SignatureFile,
  600 + remark = hyhk.Remark,
  601 + overtimeCoefficient = hyhk.OvertimeCoefficient,
  602 + originalSgfy = hyhk.OriginalSgfy,
  603 + overtimeSgfy = hyhk.OvertimeSgfy,
  604 + appointmentId = hyhk.AppointmentId,
  605 + })
  606 + .MergeTable()
  607 + .OrderBy($"{sidx} {sort}")
  608 + .ToPagedListAsync(input.currentPage, input.pageSize);
  609 +
  610 + var data = await query;
  611 +
  612 + // 获取当前页的耗卡记录ID列表
  613 + var consumeIds = data.list.Select(x => x.id).ToList();
  614 +
  615 + // 批量查询耗卡明细、健康师业绩、科技部老师业绩、人次记录
  616 + var consumeDetails = new List<LqXhPxmxInfoOutput>();
  617 + var jksyjList = new List<LqXhJksyjInfoOutput>();
  618 + var kjbsyjList = new List<LqXhKjbsyjInfoOutput>();
  619 + var personTimesRecordList = new List<LqPersonTimesRecordListOutput>();
  620 +
  621 + if (consumeIds.Any())
  622 + {
  623 + consumeDetails = await _db.Queryable<LqXhPxmxEntity>()
  624 + .Where(x => consumeIds.Contains(x.ConsumeInfoId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  625 + .Select(x => new LqXhPxmxInfoOutput
  626 + {
  627 + id = x.Id,
  628 + consumeInfoId = x.ConsumeInfoId,
  629 + billingItemId = x.BillingItemId,
  630 + px = x.Px,
  631 + pxmc = x.Pxmc,
  632 + pxjg = x.Pxjg,
  633 + memberId = x.MemberId,
  634 + createTime = x.CreateTIme,
  635 + projectNumber = x.ProjectNumber,
  636 + originalProjectNumber = x.OriginalProjectNumber,
  637 + overtimeProjectNumber = x.OvertimeProjectNumber,
  638 + sourceType = x.SourceType,
  639 + totalPrice = x.TotalPrice,
  640 + isEffective = x.IsEffective,
  641 + })
  642 + .ToListAsync();
  643 +
  644 + jksyjList = await _db.Queryable<LqXhJksyjEntity>()
  645 + .Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode())
  646 + .Select(x => new LqXhJksyjInfoOutput
  647 + {
  648 + id = x.Id,
  649 + glkdbh = x.Glkdbh,
  650 + jks = x.Jks,
  651 + jkszh = x.Jkszh,
  652 + jksxm = x.Jksxm,
  653 + jksyj = SqlFunc.ToString(x.Jksyj),
  654 + yjsj = x.Yjsj,
  655 + jsjId = x.JsjId,
  656 + kdpxid = x.Kdpxid,
  657 + laborCost = x.LaborCost,
  658 + kdpxNumber = x.KdpxNumber,
  659 + originalKdpxNumber = x.OriginalKdpxNumber,
  660 + overtimeKdpxNumber = x.OvertimeKdpxNumber,
  661 + originalLaborCost = x.OriginalLaborCost,
  662 + overtimeLaborCost = x.OvertimeLaborCost,
  663 + isAccompanied = x.IsAccompanied,
  664 + accompaniedProjectNumber = x.AccompaniedProjectNumber,
  665 + })
  666 + .ToListAsync();
  667 +
  668 + kjbsyjList = await _db.Queryable<LqXhKjbsyjEntity>()
  669 + .Where(x => consumeIds.Contains(x.Glkdbh) && x.IsEffective == StatusEnum.有效.GetHashCode())
  670 + .Select(x => new LqXhKjbsyjInfoOutput
  671 + {
  672 + id = x.Id,
  673 + glkdbh = x.Glkdbh,
  674 + kjbls = x.Kjbls,
  675 + kjblszh = x.Kjblszh,
  676 + kjblsxm = x.Kjblsxm,
  677 + kjblsyj = SqlFunc.ToString(x.Kjblsyj),
  678 + yjsj = x.Yjsj,
  679 + hkpxid = x.Hkpxid,
  680 + laborCost = x.LaborCost,
  681 + hdpxNumber = x.HdpxNumber,
  682 + originalHdpxNumber = x.OriginalHdpxNumber,
  683 + overtimeHdpxNumber = x.OvertimeHdpxNumber,
  684 + originalLaborCost = x.OriginalLaborCost,
  685 + overtimeLaborCost = x.OvertimeLaborCost,
  686 + })
  687 + .ToListAsync();
  688 +
  689 + personTimesRecordList = await _db.Queryable<LqPersonTimesRecordEntity>()
  690 + .Where(x => consumeIds.Contains(x.BusinessId) && x.IsEffective == StatusEnum.有效.GetHashCode())
  691 + .Select(x => new LqPersonTimesRecordListOutput
  692 + {
  693 + id = x.Id,
  694 + businessId = x.BusinessId,
  695 + businessType = x.BusinessType,
  696 + hasBilling = x.HasBilling,
  697 + personType = x.PersonType,
  698 + personId = x.PersonId,
  699 + personName = x.PersonName,
  700 + memberId = x.MemberId,
  701 + memberName = x.MemberName,
  702 + workDate = x.WorkDate,
  703 + workMonth = x.WorkMonth,
  704 + quantity = x.Quantity,
  705 + createTime = x.CreateTime,
  706 + isEffective = x.IsEffective,
  707 + })
  708 + .ToListAsync();
  709 + }
  710 +
  711 + // 按耗卡记录ID分组
  712 + var consumeDetailsGrouped = consumeDetails.GroupBy(x => x.consumeInfoId)
  713 + .ToDictionary(g => g.Key, g => g.ToList());
  714 + var jksyjGrouped = jksyjList.GroupBy(x => x.glkdbh)
  715 + .ToDictionary(g => g.Key, g => g.ToList());
  716 + var kjbsyjGrouped = kjbsyjList.GroupBy(x => x.glkdbh)
  717 + .ToDictionary(g => g.Key, g => g.ToList());
  718 + var personTimesRecordGrouped = personTimesRecordList.GroupBy(x => x.businessId)
  719 + .ToDictionary(g => g.Key, g => g.ToList());
  720 +
  721 + foreach (var item in data.list)
  722 + {
  723 + item.ConsumeDetails = consumeDetailsGrouped.ContainsKey(item.id)
  724 + ? consumeDetailsGrouped[item.id]
  725 + : new List<LqXhPxmxInfoOutput>();
  726 + item.lqXhJksyjList = jksyjGrouped.ContainsKey(item.id)
  727 + ? jksyjGrouped[item.id]
  728 + : new List<LqXhJksyjInfoOutput>();
  729 + item.lqXhKjbsyjList = kjbsyjGrouped.ContainsKey(item.id)
  730 + ? kjbsyjGrouped[item.id]
  731 + : new List<LqXhKjbsyjInfoOutput>();
  732 + item.personTimesRecordList = personTimesRecordGrouped.ContainsKey(item.id)
  733 + ? personTimesRecordGrouped[item.id]
  734 + : new List<LqPersonTimesRecordListOutput>();
  735 + }
  736 +
  737 + return PageResult<LqXhHyhkListOutput>.SqlSugarPageResult(data);
  738 + }
524 739 #endregion
525 740  
526 741 #region 根据健康师ID获取耗卡列表
... ...
sql/添加会员生日类型字段.sql 0 → 100644
  1 +-- 为会员信息表(lq_khxx)添加生日类型字段
  2 +-- 用于标识会员过阳历生日还是农历生日
  3 +-- 执行时间:2026-02-05
  4 +-- 字段命名规范:F_ 开头,使用标准英文命名,不用拼音
  5 +
  6 +ALTER TABLE `lq_khxx`
  7 +ADD COLUMN `F_BirthdayType` INT(11) DEFAULT 0 COMMENT '生日类型(0-阳历生日,1-农历生日)' AFTER `yinlsr`;
... ...