Commit d21065a0bb1568c5f091eec8e258426942e45719

Authored by “wangming”
1 parent c57702ff

添加获取当前用户App权限接口

- 在IAuthorizeService接口中添加GetCurrentUserAppModuleAuthorize方法
- 在AuthorizeService中实现根据用户角色获取App权限的逻辑
- 在UsersCurrentService中添加GetAppAuthorize接口(GET /api/permission/Users/Current/AppAuthorize)
- 权限获取逻辑:根据用户角色从权限表中获取Category为'App'的模块权限
- 管理员返回所有App权限,普通用户返回角色权限
- 返回树形结构的App权限列表

其他修改:
- 更新门店看板相关功能
- 更新会员相关功能
- 更新样式文件
antis-ncc-admin/public/index.html
... ... @@ -8,6 +8,10 @@
8 8 <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
9 9 <link rel="icon" href="<%= BASE_URL %>favicon.ico">
10 10 <title><%= webpackConfig.name %></title>
  11 + <!-- Inter Font - Professional, modern font for enterprise applications -->
  12 + <link rel="preconnect" href="https://fonts.googleapis.com">
  13 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  14 + <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
11 15 <script src="<%= BASE_URL %>cdn/echarts/4.2.1/echarts.min.js"></script>
12 16  
13 17 </head>
... ...
antis-ncc-admin/src/styles/index.scss
... ... @@ -10,7 +10,7 @@ body {
10 10 -moz-osx-font-smoothing: grayscale;
11 11 -webkit-font-smoothing: antialiased;
12 12 text-rendering: optimizeLegibility;
13   - font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
  13 + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', 'Helvetica Neue', Helvetica, Arial, sans-serif;
14 14 }
15 15  
16 16 // label {
... ...
antis-ncc-admin/src/views/extend/storeDashboard/index.vue
... ... @@ -74,6 +74,30 @@
74 74 formatMoney(storeData.Performance.NetPerformance) : '0.00' }}</div>
75 75 <div class="stat-trend" v-if="false">+15.8%</div>
76 76 </div>
  77 + <div class="core-stat-item"
  78 + style="background: linear-gradient(135deg, #fdf2f8 0%, #fce7f3 100%); border-left: 4px solid #ec4899;">
  79 + <div class="stat-label">生美业绩</div>
  80 + <div class="stat-value">¥{{ storeData && storeData.Performance &&
  81 + storeData.Performance.LifeBeautyPerformance ?
  82 + formatMoney(storeData.Performance.LifeBeautyPerformance) : '0.00' }}</div>
  83 + <div class="stat-trend" v-if="false">+12.5%</div>
  84 + </div>
  85 + <div class="core-stat-item"
  86 + style="background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%); border-left: 4px solid #3b82f6;">
  87 + <div class="stat-label">医美业绩</div>
  88 + <div class="stat-value">¥{{ storeData && storeData.Performance &&
  89 + storeData.Performance.MedicalBeautyPerformance ?
  90 + formatMoney(storeData.Performance.MedicalBeautyPerformance) : '0.00' }}</div>
  91 + <div class="stat-trend" v-if="false">+8.3%</div>
  92 + </div>
  93 + <div class="core-stat-item"
  94 + style="background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%); border-left: 4px solid #22c55e;">
  95 + <div class="stat-label">科美业绩</div>
  96 + <div class="stat-value">¥{{ storeData && storeData.Performance &&
  97 + storeData.Performance.TechBeautyPerformance ?
  98 + formatMoney(storeData.Performance.TechBeautyPerformance) : '0.00' }}</div>
  99 + <div class="stat-trend" v-if="false">+2.1%</div>
  100 + </div>
77 101 </div>
78 102 </div>
79 103 </div>
... ... @@ -182,7 +206,7 @@
182 206 <el-table-column prop="itemName" label="品项名称" min-width="180" />
183 207 <el-table-column prop="billingAmount" label="开单金额" width="140" align="right">
184 208 <template slot-scope="scope">
185   - <span style="font-weight: 600; color: #67C23A;">¥{{
  209 + <span style="font-weight: 600; color: #22c55e;">¥{{
186 210 formatMoney(scope.row.billingAmount) }}</span>
187 211 </template>
188 212 </el-table-column>
... ... @@ -235,7 +259,7 @@
235 259 <el-table-column prop="itemName" label="品项名称" min-width="150" />
236 260 <el-table-column prop="consumeAmount" label="消耗金额" width="120" align="right">
237 261 <template slot-scope="scope">
238   - <span style="font-weight: 600; color: #409EFF;">¥{{
  262 + <span style="font-weight: 600; color: #22c55e;">¥{{
239 263 formatMoney(scope.row.consumeAmount) }}</span>
240 264 </template>
241 265 </el-table-column>
... ... @@ -619,7 +643,10 @@ export default {
619 643 RefundAmount: response.data.RefundAmount || 0,
620 644 RefundCount: response.data.RefundCount || 0,
621 645 RemainingRightsAmount: response.data.RemainingRightsAmount || 0,
622   - TargetPerformance: response.data.TargetPerformance || 0
  646 + TargetPerformance: response.data.TargetPerformance || 0,
  647 + LifeBeautyPerformance: response.data.LifeBeautyPerformance || 0,
  648 + MedicalBeautyPerformance: response.data.MedicalBeautyPerformance || 0,
  649 + TechBeautyPerformance: response.data.TechBeautyPerformance || 0
623 650 },
624 651 Operation: {
625 652 HeadCount: response.data.HeadCount || 0,
... ... @@ -1901,17 +1928,49 @@ export default {
1901 1928  
1902 1929 <style lang="scss" scoped>
1903 1930 .store-dashboard {
1904   - padding: 20px;
1905   - background: #f5f7fa;
  1931 + padding: 16px;
  1932 + background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 50%, #f0f4f8 100%);
1906 1933 min-height: calc(100vh - 84px);
  1934 + overflow-y: auto;
  1935 + overflow-x: hidden;
  1936 + display: flex;
  1937 + flex-direction: column;
  1938 +
  1939 + // 自定义滚动条样式
  1940 + &::-webkit-scrollbar {
  1941 + width: 8px;
  1942 + }
  1943 +
  1944 + &::-webkit-scrollbar-track {
  1945 + background: rgba(144, 147, 153, 0.1);
  1946 + border-radius: 4px;
  1947 + }
  1948 +
  1949 + &::-webkit-scrollbar-thumb {
  1950 + background: rgba(144, 147, 153, 0.3);
  1951 + border-radius: 4px;
  1952 + transition: background 0.3s ease;
  1953 +
  1954 + &:hover {
  1955 + background: rgba(144, 147, 153, 0.5);
  1956 + }
  1957 + }
1907 1958  
1908 1959 // 筛选器栏
1909 1960 .filter-bar {
1910   - background: #fff;
1911   - padding: 16px 20px;
  1961 + background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.9) 100%);
  1962 + backdrop-filter: blur(10px);
  1963 + padding: 14px 18px;
1912 1964 border-radius: 12px;
1913   - margin-bottom: 20px;
1914   - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  1965 + margin-bottom: 16px;
  1966 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04);
  1967 + border: 1px solid rgba(144, 147, 153, 0.15);
  1968 + transition: all 0.3s ease;
  1969 +
  1970 + &:hover {
  1971 + box-shadow: 0 6px 24px rgba(0, 0, 0, 0.12), 0 4px 12px rgba(0, 0, 0, 0.06);
  1972 + transform: translateY(-1px);
  1973 + }
1915 1974  
1916 1975 .filter-form {
1917 1976 margin: 0;
... ... @@ -1921,9 +1980,38 @@ export default {
1921 1980 }
1922 1981  
1923 1982 ::v-deep .el-form-item__label {
1924   - font-weight: 500;
  1983 + font-weight: 600;
1925 1984 color: #606266;
1926 1985 }
  1986 +
  1987 + ::v-deep .el-button {
  1988 + border-radius: 8px;
  1989 + transition: all 0.3s ease;
  1990 +
  1991 + &.el-button--primary {
  1992 + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
  1993 + border: none;
  1994 + box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
  1995 +
  1996 + &:hover {
  1997 + background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
  1998 + transform: translateY(-2px);
  1999 + box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
  2000 + }
  2001 + }
  2002 +
  2003 + &:not(.el-button--primary) {
  2004 + background: rgba(255, 255, 255, 0.9);
  2005 + border-color: rgba(144, 147, 153, 0.3);
  2006 + color: #606266;
  2007 +
  2008 + &:hover {
  2009 + background: rgba(255, 255, 255, 1);
  2010 + border-color: rgba(144, 147, 153, 0.5);
  2011 + transform: translateY(-1px);
  2012 + }
  2013 + }
  2014 + }
1927 2015 }
1928 2016 }
1929 2017  
... ... @@ -1932,11 +2020,15 @@ export default {
1932 2020 display: flex;
1933 2021 justify-content: space-between;
1934 2022 align-items: center;
1935   - padding: 20px 24px;
1936   - background: #fff;
  2023 + padding: 18px 22px;
  2024 + background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(255, 255, 255, 0.95) 100%);
  2025 + backdrop-filter: blur(10px);
1937 2026 border-radius: 12px;
1938   - margin-bottom: 20px;
1939   - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  2027 + margin-bottom: 16px;
  2028 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04);
  2029 + border: 1px solid rgba(144, 147, 153, 0.15);
  2030 + transition: all 0.3s ease;
  2031 + flex-shrink: 0;
1940 2032  
1941 2033 .header-left {
1942 2034 .store-info {
... ... @@ -1945,15 +2037,55 @@ export default {
1945 2037 gap: 16px;
1946 2038  
1947 2039 .store-avatar {
1948   - width: 64px;
1949   - height: 64px;
1950   - border-radius: 12px;
1951   - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  2040 + width: 72px;
  2041 + height: 72px;
  2042 + border-radius: 16px;
  2043 + background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #667eea 100%);
  2044 + background-size: 200% 200%;
1952 2045 display: flex;
1953 2046 align-items: center;
1954 2047 justify-content: center;
1955 2048 color: #fff;
1956   - font-size: 28px;
  2049 + font-size: 32px;
  2050 + box-shadow:
  2051 + 0 8px 24px rgba(102, 126, 234, 0.3),
  2052 + 0 4px 12px rgba(118, 75, 162, 0.2),
  2053 + inset 0 1px 0 rgba(255, 255, 255, 0.2);
  2054 + transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
  2055 + position: relative;
  2056 + overflow: hidden;
  2057 +
  2058 + &::before {
  2059 + content: '';
  2060 + position: absolute;
  2061 + top: -50%;
  2062 + left: -50%;
  2063 + width: 200%;
  2064 + height: 200%;
  2065 + background: linear-gradient(45deg, transparent, rgba(255, 255, 255, 0.3), transparent);
  2066 + transform: rotate(45deg);
  2067 + transition: all 0.6s ease;
  2068 + }
  2069 +
  2070 + i {
  2071 + position: relative;
  2072 + z-index: 1;
  2073 + filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.2));
  2074 + }
  2075 +
  2076 + &:hover {
  2077 + transform: scale(1.08) rotate(5deg);
  2078 + box-shadow:
  2079 + 0 12px 32px rgba(102, 126, 234, 0.4),
  2080 + 0 6px 16px rgba(118, 75, 162, 0.3),
  2081 + inset 0 1px 0 rgba(255, 255, 255, 0.25);
  2082 + background-position: 100% 100%;
  2083 +
  2084 + &::before {
  2085 + top: 50%;
  2086 + left: 50%;
  2087 + }
  2088 + }
1957 2089 }
1958 2090  
1959 2091 .store-details {
... ... @@ -1966,8 +2098,11 @@ export default {
1966 2098 .store-name {
1967 2099 margin: 0;
1968 2100 font-size: 24px;
1969   - font-weight: 600;
1970   - color: #303133;
  2101 + font-weight: 700;
  2102 + background: linear-gradient(135deg, #303133 0%, #606266 100%);
  2103 + -webkit-background-clip: text;
  2104 + -webkit-text-fill-color: transparent;
  2105 + background-clip: text;
1971 2106 }
1972 2107 }
1973 2108  
... ... @@ -1980,10 +2115,92 @@ export default {
1980 2115 .meta-item {
1981 2116 display: flex;
1982 2117 align-items: center;
1983   - gap: 6px;
  2118 + gap: 8px;
  2119 + padding: 6px 12px;
  2120 + border-radius: 8px;
  2121 + background: linear-gradient(135deg, rgba(239, 246, 255, 0.6) 0%, rgba(219, 234, 254, 0.6) 100%);
  2122 + border: 1px solid rgba(59, 130, 246, 0.2);
  2123 + transition: all 0.3s ease;
  2124 + cursor: pointer;
  2125 +
  2126 + &:nth-child(1) {
  2127 + background: linear-gradient(135deg, rgba(239, 246, 255, 0.6) 0%, rgba(219, 234, 254, 0.6) 100%);
  2128 + border-color: rgba(59, 130, 246, 0.2);
  2129 + }
  2130 +
  2131 + &:nth-child(2) {
  2132 + background: linear-gradient(135deg, rgba(240, 253, 244, 0.6) 0%, rgba(220, 252, 231, 0.6) 100%);
  2133 + border-color: rgba(34, 197, 94, 0.2);
  2134 + }
  2135 +
  2136 + &:nth-child(3) {
  2137 + background: linear-gradient(135deg, rgba(255, 247, 237, 0.6) 0%, rgba(255, 237, 213, 0.6) 100%);
  2138 + border-color: rgba(245, 158, 11, 0.2);
  2139 + }
1984 2140  
1985 2141 i {
1986   - color: #909399;
  2142 + font-size: 16px;
  2143 + width: 18px;
  2144 + height: 18px;
  2145 + display: flex;
  2146 + align-items: center;
  2147 + justify-content: center;
  2148 + border-radius: 6px;
  2149 + padding: 2px;
  2150 + transition: all 0.3s ease;
  2151 + }
  2152 +
  2153 + &:nth-child(1) i {
  2154 + color: #3b82f6;
  2155 + background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(37, 99, 235, 0.15) 100%);
  2156 + }
  2157 +
  2158 + &:nth-child(2) i {
  2159 + color: #22c55e;
  2160 + background: linear-gradient(135deg, rgba(34, 197, 94, 0.15) 0%, rgba(21, 128, 61, 0.15) 100%);
  2161 + }
  2162 +
  2163 + &:nth-child(3) i {
  2164 + color: #f59e0b;
  2165 + background: linear-gradient(135deg, rgba(245, 158, 11, 0.15) 0%, rgba(217, 119, 6, 0.15) 100%);
  2166 + }
  2167 +
  2168 + &:hover {
  2169 + transform: translateY(-2px);
  2170 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  2171 +
  2172 + &:nth-child(1) {
  2173 + background: linear-gradient(135deg, rgba(239, 246, 255, 0.9) 0%, rgba(219, 234, 254, 0.9) 100%);
  2174 + border-color: rgba(59, 130, 246, 0.4);
  2175 +
  2176 + i {
  2177 + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
  2178 + color: #fff;
  2179 + transform: scale(1.1);
  2180 + }
  2181 + }
  2182 +
  2183 + &:nth-child(2) {
  2184 + background: linear-gradient(135deg, rgba(240, 253, 244, 0.9) 0%, rgba(220, 252, 231, 0.9) 100%);
  2185 + border-color: rgba(34, 197, 94, 0.4);
  2186 +
  2187 + i {
  2188 + background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
  2189 + color: #fff;
  2190 + transform: scale(1.1);
  2191 + }
  2192 + }
  2193 +
  2194 + &:nth-child(3) {
  2195 + background: linear-gradient(135deg, rgba(255, 247, 237, 0.9) 0%, rgba(255, 237, 213, 0.9) 100%);
  2196 + border-color: rgba(245, 158, 11, 0.4);
  2197 +
  2198 + i {
  2199 + background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
  2200 + color: #fff;
  2201 + transform: scale(1.1);
  2202 + }
  2203 + }
1987 2204 }
1988 2205 }
1989 2206 }
... ... @@ -1998,29 +2215,56 @@ export default {
1998 2215  
1999 2216 .core-stat-item {
2000 2217 padding: 16px 20px;
2001   - border-radius: 10px;
  2218 + border-radius: 12px;
2002 2219 min-width: 140px;
2003 2220 text-align: center;
2004   - transition: all 0.3s;
  2221 + transition: all 0.3s ease;
  2222 + cursor: pointer;
  2223 + position: relative;
  2224 + overflow: hidden;
  2225 +
  2226 + &::before {
  2227 + content: '';
  2228 + position: absolute;
  2229 + top: 0;
  2230 + left: -100%;
  2231 + width: 100%;
  2232 + height: 100%;
  2233 + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
  2234 + transition: left 0.5s ease;
  2235 + }
  2236 +
  2237 + &:hover {
  2238 + transform: translateY(-4px);
  2239 + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
  2240 +
  2241 + &::before {
  2242 + left: 100%;
  2243 + }
  2244 + }
2005 2245  
2006 2246 &.primary {
2007   - background: linear-gradient(135deg, #ecf5ff 0%, #d9ecff 100%);
2008   - border-left: 4px solid #409EFF;
  2247 + background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
  2248 + border-left: 4px solid #3b82f6;
  2249 + box-shadow: 0 2px 8px rgba(59, 130, 246, 0.2);
2009 2250 }
2010 2251  
2011 2252 &.success {
2012   - background: linear-gradient(135deg, #f0f9ff 0%, #e1f3ff 100%);
2013   - border-left: 4px solid #67C23A;
  2253 + background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
  2254 + border-left: 4px solid #22c55e;
  2255 + box-shadow: 0 2px 8px rgba(34, 197, 94, 0.2);
2014 2256 }
2015 2257  
2016 2258 &.info {
2017   - background: linear-gradient(135deg, #f4f4f5 0%, #e9e9eb 100%);
2018   - border-left: 4px solid #909399;
  2259 + background: linear-gradient(135deg, #faf5ff 0%, #f3e8ff 100%);
  2260 + border-left: 4px solid #a855f7;
  2261 + box-shadow: 0 2px 8px rgba(168, 85, 247, 0.2);
2019 2262 }
2020 2263  
2021 2264 &.warning {
2022   - background: linear-gradient(135deg, #fdf6ec 0%, #fae6d3 100%);
2023   - border-left: 4px solid #E6A23C;
  2265 + background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%);
  2266 + border-left: 4px solid #f59e0b;
  2267 + box-shadow: 0 2px 8px rgba(245, 158, 11, 0.2);
2024 2268 }
2025 2269  
2026 2270 .stat-label {
... ... @@ -2032,7 +2276,10 @@ export default {
2032 2276 .stat-value {
2033 2277 font-size: 22px;
2034 2278 font-weight: 700;
2035   - color: #303133;
  2279 + background: linear-gradient(135deg, #303133 0%, #606266 100%);
  2280 + -webkit-background-clip: text;
  2281 + -webkit-text-fill-color: transparent;
  2282 + background-clip: text;
2036 2283 margin-bottom: 6px;
2037 2284 }
2038 2285  
... ... @@ -2057,9 +2304,10 @@ export default {
2057 2304 .main-content {
2058 2305 display: grid;
2059 2306 grid-template-columns: 1fr 400px;
2060   - gap: 20px;
2061   - margin-bottom: 20px;
  2307 + gap: 16px;
  2308 + margin-bottom: 0;
2062 2309 align-items: start;
  2310 + flex: 1;
2063 2311  
2064 2312 .content-left {
2065 2313 display: flex;
... ... @@ -2081,11 +2329,22 @@ export default {
2081 2329 // 图表卡片
2082 2330 .chart-card {
2083 2331 border-radius: 12px;
2084   - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  2332 + background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(255, 255, 255, 0.95) 100%);
  2333 + backdrop-filter: blur(10px);
  2334 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04);
  2335 + border: 1px solid rgba(144, 147, 153, 0.15);
  2336 + transition: all 0.3s ease;
  2337 +
  2338 + &:hover {
  2339 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 4px 16px rgba(0, 0, 0, 0.06);
  2340 + transform: translateY(-2px);
  2341 + }
2085 2342  
2086 2343 ::v-deep .el-card__header {
2087 2344 padding: 16px 20px;
2088   - border-bottom: 1px solid #ebeef5;
  2345 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.8) 0%, rgba(240, 242, 245, 0.8) 100%);
  2346 + border-bottom: 2px solid rgba(144, 147, 153, 0.2);
  2347 + border-radius: 12px 12px 0 0;
2089 2348 }
2090 2349  
2091 2350 ::v-deep .el-card__body {
... ... @@ -2101,11 +2360,22 @@ export default {
2101 2360  
2102 2361 .chart-card-small {
2103 2362 border-radius: 12px;
2104   - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  2363 + background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(255, 255, 255, 0.95) 100%);
  2364 + backdrop-filter: blur(10px);
  2365 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04);
  2366 + border: 1px solid rgba(144, 147, 153, 0.15);
  2367 + transition: all 0.3s ease;
  2368 +
  2369 + &:hover {
  2370 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 4px 16px rgba(0, 0, 0, 0.06);
  2371 + transform: translateY(-2px);
  2372 + }
2105 2373  
2106 2374 ::v-deep .el-card__header {
2107 2375 padding: 14px 18px;
2108   - border-bottom: 1px solid #ebeef5;
  2376 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.8) 0%, rgba(240, 242, 245, 0.8) 100%);
  2377 + border-bottom: 2px solid rgba(144, 147, 153, 0.2);
  2378 + border-radius: 12px 12px 0 0;
2109 2379 }
2110 2380  
2111 2381 ::v-deep .el-card__body {
... ... @@ -2122,11 +2392,22 @@ export default {
2122 2392 // 指标卡片
2123 2393 .metrics-card {
2124 2394 border-radius: 12px;
2125   - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  2395 + background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(255, 255, 255, 0.95) 100%);
  2396 + backdrop-filter: blur(10px);
  2397 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04);
  2398 + border: 1px solid rgba(144, 147, 153, 0.15);
  2399 + transition: all 0.3s ease;
  2400 +
  2401 + &:hover {
  2402 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 4px 16px rgba(0, 0, 0, 0.06);
  2403 + transform: translateY(-2px);
  2404 + }
2126 2405  
2127 2406 ::v-deep .el-card__header {
2128 2407 padding: 14px 18px;
2129   - border-bottom: 1px solid #ebeef5;
  2408 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.8) 0%, rgba(240, 242, 245, 0.8) 100%);
  2409 + border-bottom: 2px solid rgba(144, 147, 153, 0.2);
  2410 + border-radius: 12px 12px 0 0;
2130 2411 }
2131 2412  
2132 2413 ::v-deep .el-card__body {
... ... @@ -2142,13 +2423,17 @@ export default {
2142 2423 display: flex;
2143 2424 align-items: center;
2144 2425 padding: 12px;
2145   - background: #f8f9fa;
  2426 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.8) 0%, rgba(240, 242, 245, 0.8) 100%);
2146 2427 border-radius: 8px;
2147   - transition: all 0.3s;
  2428 + border: 1px solid rgba(144, 147, 153, 0.1);
  2429 + transition: all 0.3s ease;
  2430 + cursor: pointer;
2148 2431  
2149 2432 &:hover {
2150   - background: #f0f2f5;
2151   - transform: translateY(-1px);
  2433 + background: linear-gradient(135deg, rgba(240, 242, 245, 0.9) 0%, rgba(232, 236, 241, 0.9) 100%);
  2434 + transform: translateY(-2px);
  2435 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  2436 + border-color: rgba(144, 147, 153, 0.2);
2152 2437 }
2153 2438  
2154 2439 .metric-icon {
... ... @@ -2202,12 +2487,195 @@ export default {
2202 2487 align-items: center;
2203 2488 font-size: 15px;
2204 2489 font-weight: 600;
2205   - color: #303133;
  2490 + background: linear-gradient(135deg, #303133 0%, #606266 100%);
  2491 + -webkit-background-clip: text;
  2492 + -webkit-text-fill-color: transparent;
  2493 + background-clip: text;
2206 2494  
2207 2495 i {
2208   - margin-right: 8px;
2209   - color: #409EFF;
  2496 + margin-right: 10px;
  2497 + width: 32px;
  2498 + height: 32px;
  2499 + display: flex;
  2500 + align-items: center;
  2501 + justify-content: center;
  2502 + border-radius: 8px;
2210 2503 font-size: 16px;
  2504 + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  2505 + position: relative;
  2506 + overflow: hidden;
  2507 + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  2508 +
  2509 + &::before {
  2510 + content: '';
  2511 + position: absolute;
  2512 + top: 0;
  2513 + left: -100%;
  2514 + width: 100%;
  2515 + height: 100%;
  2516 + background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
  2517 + transition: left 0.5s ease;
  2518 + }
  2519 +
  2520 + // 根据图标类型设置不同颜色
  2521 + &.el-icon-data-line {
  2522 + background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(37, 99, 235, 0.15) 100%);
  2523 + color: #3b82f6;
  2524 + }
  2525 +
  2526 + &.el-icon-pie-chart {
  2527 + background: linear-gradient(135deg, rgba(168, 85, 247, 0.15) 0%, rgba(147, 51, 234, 0.15) 100%);
  2528 + color: #a855f7;
  2529 + }
  2530 +
  2531 + &.el-icon-s-marketing {
  2532 + background: linear-gradient(135deg, rgba(236, 72, 153, 0.15) 0%, rgba(219, 39, 119, 0.15) 100%);
  2533 + color: #ec4899;
  2534 + }
  2535 +
  2536 + &.el-icon-s-data {
  2537 + background: linear-gradient(135deg, rgba(34, 197, 94, 0.15) 0%, rgba(21, 128, 61, 0.15) 100%);
  2538 + color: #22c55e;
  2539 + }
  2540 +
  2541 + &.el-icon-sort {
  2542 + background: linear-gradient(135deg, rgba(245, 158, 11, 0.15) 0%, rgba(217, 119, 6, 0.15) 100%);
  2543 + color: #f59e0b;
  2544 + }
  2545 +
  2546 + &.el-icon-s-grid {
  2547 + background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(37, 99, 235, 0.15) 100%);
  2548 + color: #3b82f6;
  2549 + }
  2550 +
  2551 + &.el-icon-shopping-bag-1,
  2552 + &.el-icon-goods {
  2553 + background: linear-gradient(135deg, rgba(236, 72, 153, 0.15) 0%, rgba(219, 39, 119, 0.15) 100%);
  2554 + color: #ec4899;
  2555 + }
  2556 +
  2557 + &.el-icon-user-solid,
  2558 + &.el-icon-user {
  2559 + background: linear-gradient(135deg, rgba(34, 197, 94, 0.15) 0%, rgba(21, 128, 61, 0.15) 100%);
  2560 + color: #22c55e;
  2561 + }
  2562 +
  2563 + &.el-icon-odometer {
  2564 + background: linear-gradient(135deg, rgba(168, 85, 247, 0.15) 0%, rgba(147, 51, 234, 0.15) 100%);
  2565 + color: #a855f7;
  2566 + }
  2567 +
  2568 + &.el-icon-trophy {
  2569 + background: linear-gradient(135deg, rgba(245, 158, 11, 0.15) 0%, rgba(217, 119, 6, 0.15) 100%);
  2570 + color: #f59e0b;
  2571 + }
  2572 +
  2573 + &.el-icon-warning {
  2574 + background: linear-gradient(135deg, rgba(245, 158, 11, 0.15) 0%, rgba(217, 119, 6, 0.15) 100%);
  2575 + color: #f59e0b;
  2576 + }
  2577 +
  2578 + &.el-icon-data-analysis {
  2579 + background: linear-gradient(135deg, rgba(59, 130, 246, 0.15) 0%, rgba(37, 99, 235, 0.15) 100%);
  2580 + color: #3b82f6;
  2581 + }
  2582 +
  2583 + &.el-icon-s-flag {
  2584 + background: linear-gradient(135deg, rgba(236, 72, 153, 0.15) 0%, rgba(219, 39, 119, 0.15) 100%);
  2585 + color: #ec4899;
  2586 + }
  2587 +
  2588 + // 默认样式(如果没有匹配的图标类)
  2589 + &:not([class*="el-icon-"]) {
  2590 + background: linear-gradient(135deg, rgba(96, 98, 102, 0.1) 0%, rgba(144, 147, 153, 0.1) 100%);
  2591 + color: #606266;
  2592 + }
  2593 +
  2594 + &:hover {
  2595 + transform: scale(1.1) rotate(5deg);
  2596 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
  2597 +
  2598 + &::before {
  2599 + left: 100%;
  2600 + }
  2601 +
  2602 + // hover时根据图标类型设置不同的渐变背景
  2603 + &.el-icon-data-line {
  2604 + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
  2605 + color: #fff;
  2606 + }
  2607 +
  2608 + &.el-icon-pie-chart {
  2609 + background: linear-gradient(135deg, #a855f7 0%, #9333ea 100%);
  2610 + color: #fff;
  2611 + }
  2612 +
  2613 + &.el-icon-s-marketing {
  2614 + background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);
  2615 + color: #fff;
  2616 + }
  2617 +
  2618 + &.el-icon-s-data {
  2619 + background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
  2620 + color: #fff;
  2621 + }
  2622 +
  2623 + &.el-icon-sort {
  2624 + background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
  2625 + color: #fff;
  2626 + }
  2627 +
  2628 + &.el-icon-s-grid {
  2629 + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
  2630 + color: #fff;
  2631 + }
  2632 +
  2633 + &.el-icon-shopping-bag-1,
  2634 + &.el-icon-goods {
  2635 + background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);
  2636 + color: #fff;
  2637 + }
  2638 +
  2639 + &.el-icon-user-solid,
  2640 + &.el-icon-user {
  2641 + background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
  2642 + color: #fff;
  2643 + }
  2644 +
  2645 + &.el-icon-odometer {
  2646 + background: linear-gradient(135deg, #a855f7 0%, #9333ea 100%);
  2647 + color: #fff;
  2648 + }
  2649 +
  2650 + &.el-icon-trophy {
  2651 + background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
  2652 + color: #fff;
  2653 + }
  2654 +
  2655 + &.el-icon-warning {
  2656 + background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);
  2657 + color: #fff;
  2658 + }
  2659 +
  2660 + &.el-icon-data-analysis {
  2661 + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
  2662 + color: #fff;
  2663 + }
  2664 +
  2665 + &.el-icon-s-flag {
  2666 + background: linear-gradient(135deg, #ec4899 0%, #db2777 100%);
  2667 + color: #fff;
  2668 + }
  2669 +
  2670 + &:not([class*="el-icon-"]) {
  2671 + background: linear-gradient(135deg, #606266 0%, #909399 100%);
  2672 + color: #fff;
  2673 + }
  2674 + }
  2675 + }
  2676 +
  2677 + &:hover i {
  2678 + transform: scale(1.1) rotate(5deg);
2211 2679 }
2212 2680 }
2213 2681  
... ... @@ -2231,7 +2699,10 @@ export default {
2231 2699 .rank-number {
2232 2700 font-size: 36px;
2233 2701 font-weight: 700;
2234   - color: #409EFF;
  2702 + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
  2703 + -webkit-background-clip: text;
  2704 + -webkit-text-fill-color: transparent;
  2705 + background-clip: text;
2235 2706 }
2236 2707  
2237 2708 .rank-total {
... ... @@ -2254,7 +2725,7 @@ export default {
2254 2725 }
2255 2726  
2256 2727 &.good {
2257   - background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
  2728 + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
2258 2729 color: #fff;
2259 2730 }
2260 2731  
... ... @@ -2292,11 +2763,22 @@ export default {
2292 2763 // 经营提示卡片
2293 2764 .tips-card {
2294 2765 border-radius: 12px;
2295   - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  2766 + background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(255, 255, 255, 0.95) 100%);
  2767 + backdrop-filter: blur(10px);
  2768 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04);
  2769 + border: 1px solid rgba(144, 147, 153, 0.15);
  2770 + transition: all 0.3s ease;
  2771 +
  2772 + &:hover {
  2773 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 4px 16px rgba(0, 0, 0, 0.06);
  2774 + transform: translateY(-2px);
  2775 + }
2296 2776  
2297 2777 ::v-deep .el-card__header {
2298 2778 padding: 14px 18px;
2299   - border-bottom: 1px solid #ebeef5;
  2779 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.8) 0%, rgba(240, 242, 245, 0.8) 100%);
  2780 + border-bottom: 2px solid rgba(144, 147, 153, 0.2);
  2781 + border-radius: 12px 12px 0 0;
2300 2782 }
2301 2783  
2302 2784 ::v-deep .el-card__body {
... ... @@ -2330,26 +2812,29 @@ export default {
2330 2812 }
2331 2813  
2332 2814 &.success {
2333   - background: #f0f9ff;
2334   - color: #67C23A;
  2815 + background: linear-gradient(135deg, rgba(240, 253, 244, 0.8) 0%, rgba(220, 252, 231, 0.8) 100%);
  2816 + border-left: 3px solid #22c55e;
  2817 + color: #16a34a;
2335 2818  
2336 2819 i {
2337   - color: #67C23A;
  2820 + color: #22c55e;
2338 2821 }
2339 2822 }
2340 2823  
2341 2824 &.warning {
2342   - background: #fdf6ec;
2343   - color: #E6A23C;
  2825 + background: linear-gradient(135deg, rgba(255, 247, 237, 0.8) 0%, rgba(255, 237, 213, 0.8) 100%);
  2826 + border-left: 3px solid #f59e0b;
  2827 + color: #d97706;
2344 2828  
2345 2829 i {
2346   - color: #E6A23C;
  2830 + color: #f59e0b;
2347 2831 }
2348 2832 }
2349 2833  
2350 2834 &.info {
2351   - background: #f4f4f5;
2352   - color: #909399;
  2835 + background: linear-gradient(135deg, rgba(244, 244, 245, 0.8) 0%, rgba(233, 233, 235, 0.8) 100%);
  2836 + border-left: 3px solid #909399;
  2837 + color: #6b7280;
2353 2838  
2354 2839 i {
2355 2840 color: #909399;
... ... @@ -2366,11 +2851,22 @@ export default {
2366 2851 // 快速数据洞察卡片
2367 2852 .insight-card {
2368 2853 border-radius: 12px;
2369   - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  2854 + background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(255, 255, 255, 0.95) 100%);
  2855 + backdrop-filter: blur(10px);
  2856 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04);
  2857 + border: 1px solid rgba(144, 147, 153, 0.15);
  2858 + transition: all 0.3s ease;
  2859 +
  2860 + &:hover {
  2861 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 4px 16px rgba(0, 0, 0, 0.06);
  2862 + transform: translateY(-2px);
  2863 + }
2370 2864  
2371 2865 ::v-deep .el-card__header {
2372 2866 padding: 14px 18px;
2373   - border-bottom: 1px solid #ebeef5;
  2867 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.8) 0%, rgba(240, 242, 245, 0.8) 100%);
  2868 + border-bottom: 2px solid rgba(144, 147, 153, 0.2);
  2869 + border-radius: 12px 12px 0 0;
2374 2870 }
2375 2871  
2376 2872 ::v-deep .el-card__body {
... ... @@ -2381,18 +2877,22 @@ export default {
2381 2877 .insight-item {
2382 2878 padding: 14px;
2383 2879 margin-bottom: 12px;
2384   - background: #f8f9fa;
  2880 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.8) 0%, rgba(240, 242, 245, 0.8) 100%);
2385 2881 border-radius: 8px;
2386   - border-left: 3px solid #409EFF;
2387   - transition: all 0.3s;
  2882 + border-left: 3px solid #3b82f6;
  2883 + border: 1px solid rgba(144, 147, 153, 0.1);
  2884 + transition: all 0.3s ease;
  2885 + cursor: pointer;
2388 2886  
2389 2887 &:last-child {
2390 2888 margin-bottom: 0;
2391 2889 }
2392 2890  
2393 2891 &:hover {
2394   - background: #f0f2f5;
  2892 + background: linear-gradient(135deg, rgba(240, 242, 245, 0.9) 0%, rgba(232, 236, 241, 0.9) 100%);
2395 2893 transform: translateX(4px);
  2894 + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
  2895 + border-color: rgba(144, 147, 153, 0.2);
2396 2896 }
2397 2897  
2398 2898 .insight-header {
... ... @@ -2411,7 +2911,10 @@ export default {
2411 2911 .insight-value {
2412 2912 font-size: 20px;
2413 2913 font-weight: 700;
2414   - color: #409EFF;
  2914 + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
  2915 + -webkit-background-clip: text;
  2916 + -webkit-text-fill-color: transparent;
  2917 + background-clip: text;
2415 2918 margin-bottom: 6px;
2416 2919 }
2417 2920  
... ... @@ -2427,11 +2930,22 @@ export default {
2427 2930 // 关键指标卡片
2428 2931 .key-metrics-card {
2429 2932 border-radius: 12px;
2430   - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  2933 + background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(255, 255, 255, 0.95) 100%);
  2934 + backdrop-filter: blur(10px);
  2935 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04);
  2936 + border: 1px solid rgba(144, 147, 153, 0.15);
  2937 + transition: all 0.3s ease;
  2938 +
  2939 + &:hover {
  2940 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 4px 16px rgba(0, 0, 0, 0.06);
  2941 + transform: translateY(-2px);
  2942 + }
2431 2943  
2432 2944 ::v-deep .el-card__header {
2433 2945 padding: 14px 18px;
2434   - border-bottom: 1px solid #ebeef5;
  2946 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.8) 0%, rgba(240, 242, 245, 0.8) 100%);
  2947 + border-bottom: 2px solid rgba(144, 147, 153, 0.2);
  2948 + border-radius: 12px 12px 0 0;
2435 2949 }
2436 2950  
2437 2951 ::v-deep .el-card__body {
... ... @@ -2472,16 +2986,59 @@ export default {
2472 2986 .table-section {
2473 2987 .table-card {
2474 2988 border-radius: 12px;
2475   - box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
  2989 + background: linear-gradient(135deg, rgba(255, 255, 255, 0.98) 0%, rgba(255, 255, 255, 0.95) 100%);
  2990 + backdrop-filter: blur(10px);
  2991 + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08), 0 2px 8px rgba(0, 0, 0, 0.04);
  2992 + border: 1px solid rgba(144, 147, 153, 0.15);
  2993 + transition: all 0.3s ease;
  2994 +
  2995 + &:hover {
  2996 + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12), 0 4px 16px rgba(0, 0, 0, 0.06);
  2997 + transform: translateY(-2px);
  2998 + }
2476 2999  
2477 3000 ::v-deep .el-card__header {
2478 3001 padding: 16px 20px;
2479   - border-bottom: 1px solid #ebeef5;
  3002 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.8) 0%, rgba(240, 242, 245, 0.8) 100%);
  3003 + border-bottom: 2px solid rgba(144, 147, 153, 0.2);
  3004 + border-radius: 12px 12px 0 0;
2480 3005 }
2481 3006  
2482 3007 ::v-deep .el-card__body {
2483 3008 padding: 20px;
2484 3009 }
  3010 +
  3011 + ::v-deep .el-table {
  3012 + border-radius: 8px;
  3013 + overflow: hidden;
  3014 + border: 1px solid rgba(144, 147, 153, 0.2);
  3015 +
  3016 + .el-table__header {
  3017 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.9) 0%, rgba(240, 242, 245, 0.9) 100%);
  3018 +
  3019 + th {
  3020 + background: transparent;
  3021 + color: #303133;
  3022 + font-weight: 600;
  3023 + border-bottom: 2px solid rgba(144, 147, 153, 0.2);
  3024 + }
  3025 + }
  3026 +
  3027 + .el-table__body {
  3028 + tr {
  3029 + transition: all 0.3s ease;
  3030 +
  3031 + &:hover {
  3032 + background: linear-gradient(135deg, rgba(248, 249, 250, 0.5) 0%, rgba(240, 242, 245, 0.5) 100%);
  3033 + transform: translateX(2px);
  3034 + }
  3035 + }
  3036 +
  3037 + td {
  3038 + border-bottom: 1px solid rgba(144, 147, 153, 0.15);
  3039 + }
  3040 + }
  3041 + }
2485 3042 }
2486 3043 }
2487 3044 }
... ...
antis-ncc-admin/src/views/lqKhxx/index.vue
... ... @@ -128,6 +128,8 @@
128 128 @click="addOrUpdateHandle()">新增会员</el-button>
129 129 <el-button icon="el-icon-download" :loading="exportLoading"
130 130 @click="exportMemberItems()">导出数据</el-button>
  131 + <el-button icon="el-icon-tickets" :loading="exportRightsLoading"
  132 + @click="exportMemberRights()">导出会员权益</el-button>
131 133 <el-button v-has="'btn_remove'" type="danger" icon="el-icon-delete" plain
132 134 @click="handleBatchRemoveDel()">批量删除</el-button>
133 135 </el-button-group>
... ... @@ -530,6 +532,7 @@ export default {
530 532 list: [],
531 533 listLoading: true,
532 534 exportLoading: false,
  535 + exportRightsLoading: false,
533 536 multipleSelection: [], total: 0,
534 537 listQuery: {
535 538 currentPage: 1,
... ... @@ -893,6 +896,35 @@ export default {
893 896 this.$message.error('导出失败:' + (err.msg || err.message || '未知错误'))
894 897 })
895 898 },
  899 + // 导出会员权益
  900 + exportMemberRights() {
  901 + // 显示加载状态
  902 + this.exportRightsLoading = true
  903 + const loading = this.$loading({
  904 + lock: true,
  905 + text: '正在导出会员权益数据,请稍候...',
  906 + spinner: 'el-icon-loading',
  907 + background: 'rgba(255, 255, 255, 0.85)',
  908 + customClass: 'modern-loading-mask'
  909 + })
  910 + request({
  911 + url: `/api/Extend/LqKhxx/Actions/ExportAllMemberRemainingItems`,
  912 + method: 'GET'
  913 + }).then(res => {
  914 + loading.close()
  915 + this.exportRightsLoading = false
  916 + if (!res.data.url) {
  917 + this.$message.warning('导出失败,没有可导出的数据')
  918 + return
  919 + }
  920 + window.location.href = this.define.comUrl + res.data.url
  921 + this.$message.success('导出成功')
  922 + }).catch(err => {
  923 + loading.close()
  924 + this.exportRightsLoading = false
  925 + this.$message.error('导出失败:' + (err.msg || err.message || '未知错误'))
  926 + })
  927 + },
896 928 search() {
897 929 this.listQuery = {
898 930 currentPage: 1,
... ... @@ -3145,4 +3177,30 @@ export default {
3145 3177 }
3146 3178 }
3147 3179 }
3148   -</style>
3149 3180 \ No newline at end of file
  3181 +</style>
  3182 +
  3183 +<style lang="scss">
  3184 +/* 现代化加载界面样式 - Glassmorphism风格 */
  3185 +.modern-loading-mask {
  3186 + background: rgba(255, 255, 255, 0.92) !important;
  3187 + backdrop-filter: blur(12px);
  3188 + -webkit-backdrop-filter: blur(12px);
  3189 + transition: opacity 0.3s ease;
  3190 +
  3191 + .el-loading-spinner {
  3192 + .el-loading-text {
  3193 + color: #262626 !important;
  3194 + font-size: 15px !important;
  3195 + font-weight: 500 !important;
  3196 + letter-spacing: 0.5px !important;
  3197 + margin-top: 20px !important;
  3198 + text-shadow: 0 1px 2px rgba(255, 255, 255, 0.8);
  3199 + }
  3200 +
  3201 + i {
  3202 + font-size: 42px !important;
  3203 + color: #1890ff !important;
  3204 + }
  3205 + }
  3206 +}
  3207 +</style>
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqKhxx/MemberRemainingItemsExportOutput.cs 0 → 100644
  1 +using System;
  2 +
  3 +namespace NCC.Extend.Entitys.Dto.LqKhxx
  4 +{
  5 + /// <summary>
  6 + /// 会员剩余品项导出输出
  7 + /// </summary>
  8 + public class MemberRemainingItemsExportOutput
  9 + {
  10 + /// <summary>
  11 + /// 会员ID
  12 + /// </summary>
  13 + public string memberId { get; set; }
  14 +
  15 + /// <summary>
  16 + /// 会员姓名
  17 + /// </summary>
  18 + public string memberName { get; set; }
  19 +
  20 + /// <summary>
  21 + /// 手机号
  22 + /// </summary>
  23 + public string phone { get; set; }
  24 +
  25 + /// <summary>
  26 + /// 归属门店
  27 + /// </summary>
  28 + public string storeName { get; set; }
  29 +
  30 + /// <summary>
  31 + /// 开单品项ID
  32 + /// </summary>
  33 + public string billingItemId { get; set; }
  34 +
  35 + /// <summary>
  36 + /// 品项ID
  37 + /// </summary>
  38 + public string itemId { get; set; }
  39 +
  40 + /// <summary>
  41 + /// 品项名称
  42 + /// </summary>
  43 + public string itemName { get; set; }
  44 +
  45 + /// <summary>
  46 + /// 品项单价
  47 + /// </summary>
  48 + public decimal itemPrice { get; set; }
  49 +
  50 + /// <summary>
  51 + /// 来源类型
  52 + /// </summary>
  53 + public string sourceType { get; set; }
  54 +
  55 + /// <summary>
  56 + /// 总购买数量
  57 + /// </summary>
  58 + public decimal totalPurchased { get; set; }
  59 +
  60 + /// <summary>
  61 + /// 已耗卡数量
  62 + /// </summary>
  63 + public decimal consumedCount { get; set; }
  64 +
  65 + /// <summary>
  66 + /// 已退卡数量
  67 + /// </summary>
  68 + public decimal refundedCount { get; set; }
  69 +
  70 + /// <summary>
  71 + /// 已储扣数量
  72 + /// </summary>
  73 + public decimal deductCount { get; set; }
  74 +
  75 + /// <summary>
  76 + /// 剩余数量
  77 + /// </summary>
  78 + public decimal remainingCount { get; set; }
  79 +
  80 + /// <summary>
  81 + /// 备注
  82 + /// </summary>
  83 + public string remark { get; set; }
  84 + }
  85 +}
... ...
netcore/src/Modularity/Extend/NCC.Extend.Entitys/Dto/LqStoreDashboard/StoreDashboardStatisticsOutput.cs
... ... @@ -94,6 +94,21 @@ namespace NCC.Extend.Entitys.Dto.LqStoreDashboard
94 94 /// 人均项目数(项目数/人头数)
95 95 /// </summary>
96 96 public decimal AvgProjectPerHead { get; set; }
  97 +
  98 + /// <summary>
  99 + /// 生美业绩(消耗业绩)
  100 + /// </summary>
  101 + public decimal LifeBeautyPerformance { get; set; }
  102 +
  103 + /// <summary>
  104 + /// 医美业绩(消耗业绩)
  105 + /// </summary>
  106 + public decimal MedicalBeautyPerformance { get; set; }
  107 +
  108 + /// <summary>
  109 + /// 科美业绩(消耗业绩)
  110 + /// </summary>
  111 + public decimal TechBeautyPerformance { get; set; }
97 112 }
98 113 }
99 114  
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqKhxxService.cs
... ... @@ -2,6 +2,7 @@
2 2 using System.Collections.Generic;
3 3 using System.IO;
4 4 using System.Linq;
  5 +using System.Text;
5 6 using System.Threading.Tasks;
6 7 using Mapster;
7 8 using Microsoft.AspNetCore.Mvc;
... ... @@ -40,6 +41,9 @@ using SqlSugar;
40 41 using Yitter.IdGenerator;
41 42 using NCC.Extend.Entitys.lq_kd_deductinfo;
42 43 using Microsoft.AspNetCore.Authorization;
  44 +using NPOI.XSSF.UserModel;
  45 +using NPOI.SS.UserModel;
  46 +using System.Reflection;
43 47  
44 48 namespace NCC.Extend.LqKhxx
45 49 {
... ... @@ -1249,6 +1253,389 @@ namespace NCC.Extend.LqKhxx
1249 1253 }
1250 1254 #endregion
1251 1255  
  1256 + #region 导出所有会员剩余品项
  1257 + /// <summary>
  1258 + /// 导出所有会员剩余品项
  1259 + /// </summary>
  1260 + /// <remarks>
  1261 + /// 导出所有会员的剩余品项信息到Excel文件
  1262 + ///
  1263 + /// 示例请求:
  1264 + /// ```http
  1265 + /// GET /api/Extend/LqKhxx/Actions/ExportAllMemberRemainingItems
  1266 + /// ```
  1267 + ///
  1268 + /// 返回数据说明:
  1269 + /// - 返回Excel文件下载链接
  1270 + /// - Excel文件包含所有会员的剩余品项信息
  1271 + /// </remarks>
  1272 + /// <returns>Excel文件下载信息</returns>
  1273 + /// <response code="200">成功返回Excel文件下载链接</response>
  1274 + /// <response code="500">服务器错误</response>
  1275 + [HttpGet("Actions/ExportAllMemberRemainingItems")]
  1276 + public async Task<dynamic> ExportAllMemberRemainingItems()
  1277 + {
  1278 + try
  1279 + {
  1280 + _logger.LogInformation("开始导出所有会员剩余品项");
  1281 +
  1282 + // 1. 查询所有有效会员
  1283 + var members = await _db.Queryable<LqKhxxEntity>()
  1284 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  1285 + .Select(x => new { x.Id, x.Khmc, x.Sjh, x.Gsmd })
  1286 + .ToListAsync();
  1287 +
  1288 + if (members == null || !members.Any())
  1289 + {
  1290 + throw NCCException.Oh("没有找到会员数据");
  1291 + }
  1292 +
  1293 + // 2. 批量查询门店信息
  1294 + var storeIds = members.Where(x => !string.IsNullOrEmpty(x.Gsmd)).Select(x => x.Gsmd).Distinct().ToList();
  1295 + var storeDict = new Dictionary<string, string>();
  1296 + if (storeIds.Any())
  1297 + {
  1298 + var stores = await _db.Queryable<LqMdxxEntity>()
  1299 + .Where(x => storeIds.Contains(x.Id))
  1300 + .Select(x => new { x.Id, x.Dm })
  1301 + .ToListAsync();
  1302 + storeDict = stores.ToDictionary(x => x.Id, x => x.Dm ?? "");
  1303 + }
  1304 +
  1305 + var memberIds = members.Select(x => x.Id).ToList();
  1306 +
  1307 + // 3. 批量查询所有开单品项数据
  1308 + var baseItems = await _db.Queryable<LqKdPxmxEntity>()
  1309 + .Where(x => memberIds.Contains(x.MemberId))
  1310 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  1311 + .Select(x => new
  1312 + {
  1313 + x.Id,
  1314 + x.MemberId,
  1315 + x.Px,
  1316 + x.Pxmc,
  1317 + x.Pxjg,
  1318 + x.SourceType,
  1319 + x.ProjectNumber,
  1320 + x.Remark
  1321 + })
  1322 + .ToListAsync();
  1323 +
  1324 + if (!baseItems.Any())
  1325 + {
  1326 + throw NCCException.Oh("没有找到品项数据");
  1327 + }
  1328 +
  1329 + var billingItemIds = baseItems.Select(x => x.Id).ToList();
  1330 +
  1331 + // 4. 批量查询消费数据
  1332 + var consumedData = await _db.Queryable<LqXhPxmxEntity>()
  1333 + .Where(x => billingItemIds.Contains(x.BillingItemId))
  1334 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  1335 + .GroupBy(x => x.BillingItemId)
  1336 + .Select(x => new
  1337 + {
  1338 + BillingItemId = x.BillingItemId,
  1339 + TotalConsumed = SqlFunc.AggregateSum(x.OriginalProjectNumber)
  1340 + })
  1341 + .ToListAsync();
  1342 +
  1343 + // 5. 批量查询退卡数据
  1344 + var refundedData = await _db.Queryable<LqHytkMxEntity>()
  1345 + .Where(x => billingItemIds.Contains(x.BillingItemId))
  1346 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  1347 + .GroupBy(x => x.BillingItemId)
  1348 + .Select(x => new
  1349 + {
  1350 + BillingItemId = x.BillingItemId,
  1351 + TotalRefunded = SqlFunc.AggregateSum(x.ProjectNumber)
  1352 + })
  1353 + .ToListAsync();
  1354 +
  1355 + // 6. 批量查询储扣数据
  1356 + var deductData = await _db.Queryable<LqKdDeductinfoEntity>()
  1357 + .Where(x => billingItemIds.Contains(x.DeductId))
  1358 + .Where(x => x.IsEffective == StatusEnum.有效.GetHashCode())
  1359 + .GroupBy(x => x.DeductId)
  1360 + .Select(x => new
  1361 + {
  1362 + BillingItemId = x.DeductId,
  1363 + TotalDeduct = SqlFunc.AggregateSum(x.ProjectNumber)
  1364 + })
  1365 + .ToListAsync();
  1366 +
  1367 + // 7. 构建字典以提高查询效率
  1368 + var memberDict = members.ToDictionary(x => x.Id, x => x);
  1369 +
  1370 + // 8. 组装导出数据
  1371 + var exportData = new List<MemberRemainingItemsExportOutput>();
  1372 + foreach (var item in baseItems)
  1373 + {
  1374 + if (!memberDict.ContainsKey(item.MemberId)) continue;
  1375 +
  1376 + var member = memberDict[item.MemberId];
  1377 + var consumed = consumedData.FirstOrDefault(c => c.BillingItemId == item.Id)?.TotalConsumed ?? 0m;
  1378 + var refunded = refundedData.FirstOrDefault(r => r.BillingItemId == item.Id)?.TotalRefunded ?? 0m;
  1379 + var deduct = deductData.FirstOrDefault(d => d.BillingItemId == item.Id)?.TotalDeduct ?? 0m;
  1380 + var remaining = item.ProjectNumber - consumed - refunded - deduct;
  1381 +
  1382 + // 只导出剩余数量不等于0的品项
  1383 + if (remaining != 0)
  1384 + {
  1385 + exportData.Add(new MemberRemainingItemsExportOutput
  1386 + {
  1387 + memberId = item.MemberId,
  1388 + memberName = member.Khmc ?? "",
  1389 + phone = member.Sjh ?? "",
  1390 + storeName = !string.IsNullOrEmpty(member.Gsmd) && storeDict.ContainsKey(member.Gsmd) ? storeDict[member.Gsmd] : "",
  1391 + billingItemId = item.Id,
  1392 + itemId = item.Px ?? "",
  1393 + itemName = item.Pxmc ?? "",
  1394 + itemPrice = item.Pxjg,
  1395 + sourceType = item.SourceType ?? "",
  1396 + totalPurchased = item.ProjectNumber,
  1397 + consumedCount = consumed,
  1398 + refundedCount = refunded,
  1399 + deductCount = deduct,
  1400 + remainingCount = remaining,
  1401 + remark = item.Remark ?? ""
  1402 + });
  1403 + }
  1404 + }
  1405 +
  1406 + // 9. 配置Excel导出
  1407 + ExcelConfig excelconfig = new ExcelConfig();
  1408 + excelconfig.FileName = "所有会员剩余品项_" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".xlsx";
  1409 + excelconfig.HeadFont = "微软雅黑";
  1410 + excelconfig.HeadPoint = 10;
  1411 + excelconfig.IsAllSizeColumn = true;
  1412 + excelconfig.ColumnModel = new List<ExcelColumnModel>
  1413 + {
  1414 + new ExcelColumnModel { Column = "memberId", ExcelColumn = "会员ID" },
  1415 + new ExcelColumnModel { Column = "memberName", ExcelColumn = "会员姓名" },
  1416 + new ExcelColumnModel { Column = "phone", ExcelColumn = "手机号" },
  1417 + new ExcelColumnModel { Column = "storeName", ExcelColumn = "归属门店" },
  1418 + new ExcelColumnModel { Column = "billingItemId", ExcelColumn = "开单品项ID" },
  1419 + new ExcelColumnModel { Column = "itemId", ExcelColumn = "品项ID" },
  1420 + new ExcelColumnModel { Column = "itemName", ExcelColumn = "品项名称" },
  1421 + new ExcelColumnModel { Column = "itemPrice", ExcelColumn = "品项单价" },
  1422 + new ExcelColumnModel { Column = "sourceType", ExcelColumn = "来源类型" },
  1423 + new ExcelColumnModel { Column = "totalPurchased", ExcelColumn = "总购买数量" },
  1424 + new ExcelColumnModel { Column = "consumedCount", ExcelColumn = "已耗卡数量" },
  1425 + new ExcelColumnModel { Column = "refundedCount", ExcelColumn = "已退卡数量" },
  1426 + new ExcelColumnModel { Column = "deductCount", ExcelColumn = "已储扣数量" },
  1427 + new ExcelColumnModel { Column = "remainingCount", ExcelColumn = "剩余数量" },
  1428 + new ExcelColumnModel { Column = "remark", ExcelColumn = "备注" }
  1429 + };
  1430 +
  1431 + // 10. 查找项目根目录并创建ExportFiles文件夹
  1432 + var baseDir = AppContext.BaseDirectory;
  1433 + var projectRoot = baseDir;
  1434 + var dir = new DirectoryInfo(baseDir);
  1435 + while (dir != null && dir.Parent != null)
  1436 + {
  1437 + try
  1438 + {
  1439 + if (dir.GetDirectories(".git").Any() || dir.GetFiles("*.sln").Any())
  1440 + {
  1441 + projectRoot = dir.FullName;
  1442 + break;
  1443 + }
  1444 + }
  1445 + catch
  1446 + {
  1447 + // 忽略访问错误,继续向上查找
  1448 + }
  1449 + dir = dir.Parent;
  1450 + }
  1451 +
  1452 + // 如果没找到 .git 或 .sln 目录,再查找包含 .sln 文件的目录
  1453 + if (projectRoot == baseDir)
  1454 + {
  1455 + dir = new DirectoryInfo(baseDir);
  1456 + while (dir != null && dir.Parent != null)
  1457 + {
  1458 + try
  1459 + {
  1460 + if (dir.GetFiles("*.sln").Any())
  1461 + {
  1462 + projectRoot = dir.FullName;
  1463 + break;
  1464 + }
  1465 + }
  1466 + catch
  1467 + {
  1468 + // 忽略访问错误,继续向上查找
  1469 + }
  1470 + dir = dir.Parent;
  1471 + }
  1472 + }
  1473 +
  1474 + // 在项目根目录下创建 ExportFiles 文件夹
  1475 + var exportFilesPath = Path.Combine(projectRoot, "ExportFiles");
  1476 + if (!Directory.Exists(exportFilesPath))
  1477 + {
  1478 + Directory.CreateDirectory(exportFilesPath);
  1479 + }
  1480 +
  1481 + var addPath = Path.Combine(exportFilesPath, excelconfig.FileName);
  1482 +
  1483 + // 11. 导出Excel文件(支持多sheet,每个sheet最多65535行数据)
  1484 + ExportToExcelWithMultipleSheets(exportData, excelconfig, addPath);
  1485 +
  1486 + var fileName = _userManager.UserId + "|" + addPath + "|xlsx";
  1487 + var output = new
  1488 + {
  1489 + name = excelconfig.FileName,
  1490 + url = "/api/File/Download?encryption=" + DESCEncryption.Encrypt(fileName, "NCC")
  1491 + };
  1492 +
  1493 + _logger.LogInformation("导出所有会员剩余品项完成,共{Count}条记录", exportData.Count);
  1494 +
  1495 + return output;
  1496 + }
  1497 + catch (Exception ex)
  1498 + {
  1499 + _logger.LogError(ex, "导出所有会员剩余品项失败");
  1500 + throw NCCException.Oh($"导出所有会员剩余品项失败:{ex.Message}");
  1501 + }
  1502 + }
  1503 +
  1504 + /// <summary>
  1505 + /// 导出Excel文件(支持多sheet,每个sheet最多65535行数据)
  1506 + /// </summary>
  1507 + private void ExportToExcelWithMultipleSheets(List<MemberRemainingItemsExportOutput> exportData, ExcelConfig excelConfig, string filePath)
  1508 + {
  1509 + const int maxRowsPerSheet = 65535; // 每个sheet最多65535行数据(不包括表头)
  1510 + var workbook = new XSSFWorkbook();
  1511 + var type = typeof(MemberRemainingItemsExportOutput);
  1512 + var properties = type.GetProperties();
  1513 +
  1514 + // 创建样式
  1515 + var headStyle = workbook.CreateCellStyle();
  1516 + var headFont = workbook.CreateFont();
  1517 + headFont.FontHeightInPoints = (short)excelConfig.HeadPoint;
  1518 + headFont.FontName = excelConfig.HeadFont;
  1519 + headFont.Boldweight = (short)700; // 粗体
  1520 + headStyle.SetFont(headFont);
  1521 + headStyle.Alignment = HorizontalAlignment.Left;
  1522 +
  1523 + var dateStyle = workbook.CreateCellStyle();
  1524 + var format = workbook.CreateDataFormat();
  1525 + dateStyle.DataFormat = format.GetFormat("yyyy-MM-dd HH:mm:ss");
  1526 +
  1527 + // 计算列宽
  1528 + var columnWidths = new int[excelConfig.ColumnModel.Count];
  1529 + for (int i = 0; i < excelConfig.ColumnModel.Count; i++)
  1530 + {
  1531 + var columnName = excelConfig.ColumnModel[i].ExcelColumn;
  1532 + columnWidths[i] = Encoding.UTF8.GetBytes(columnName).Length + 2;
  1533 + }
  1534 +
  1535 + int dataIndex = 0;
  1536 + int sheetIndex = 0;
  1537 +
  1538 + while (dataIndex < exportData.Count)
  1539 + {
  1540 + // 创建新的sheet
  1541 + var sheetName = sheetIndex == 0 ? "Sheet1" : $"Sheet{sheetIndex + 1}";
  1542 + var sheet = workbook.CreateSheet(sheetName);
  1543 +
  1544 + // 创建表头
  1545 + var headerRow = sheet.CreateRow(0);
  1546 + for (int col = 0; col < excelConfig.ColumnModel.Count; col++)
  1547 + {
  1548 + var cell = headerRow.CreateCell(col);
  1549 + cell.SetCellValue(excelConfig.ColumnModel[col].ExcelColumn);
  1550 + cell.CellStyle = headStyle;
  1551 + sheet.SetColumnWidth(col, (columnWidths[col] + 1) * 256);
  1552 + }
  1553 +
  1554 + // 填充数据
  1555 + int rowIndex = 1;
  1556 + while (dataIndex < exportData.Count && rowIndex <= maxRowsPerSheet)
  1557 + {
  1558 + var item = exportData[dataIndex];
  1559 + var dataRow = sheet.CreateRow(rowIndex);
  1560 +
  1561 + for (int col = 0; col < excelConfig.ColumnModel.Count; col++)
  1562 + {
  1563 + var column = excelConfig.ColumnModel[col];
  1564 + var property = properties.FirstOrDefault(p => p.Name == column.Column);
  1565 + if (property != null)
  1566 + {
  1567 + var cell = dataRow.CreateCell(col);
  1568 + var value = property.GetValue(item);
  1569 + SetCellValue(cell, value, property.PropertyType, dateStyle);
  1570 + }
  1571 + }
  1572 +
  1573 + rowIndex++;
  1574 + dataIndex++;
  1575 + }
  1576 +
  1577 + sheetIndex++;
  1578 + }
  1579 +
  1580 + // 保存文件
  1581 + using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
  1582 + {
  1583 + workbook.Write(fileStream);
  1584 + }
  1585 + }
  1586 +
  1587 + /// <summary>
  1588 + /// 设置单元格值
  1589 + /// </summary>
  1590 + private void SetCellValue(ICell cell, object value, Type propertyType, ICellStyle dateStyle)
  1591 + {
  1592 + if (value == null)
  1593 + {
  1594 + cell.SetCellValue("");
  1595 + return;
  1596 + }
  1597 +
  1598 + var typeName = propertyType.IsGenericType && propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)
  1599 + ? propertyType.GetGenericArguments()[0].ToString()
  1600 + : propertyType.ToString();
  1601 +
  1602 + switch (typeName)
  1603 + {
  1604 + case "System.String":
  1605 + cell.SetCellValue(value.ToString());
  1606 + break;
  1607 + case "System.DateTime":
  1608 + if (value is DateTime dateTime)
  1609 + {
  1610 + cell.SetCellValue(dateTime);
  1611 + cell.CellStyle = dateStyle;
  1612 + }
  1613 + else
  1614 + {
  1615 + cell.SetCellValue("");
  1616 + }
  1617 + break;
  1618 + case "System.Boolean":
  1619 + cell.SetCellValue((bool)value);
  1620 + break;
  1621 + case "System.Int16":
  1622 + case "System.Int32":
  1623 + case "System.Int64":
  1624 + case "System.Byte":
  1625 + cell.SetCellValue(Convert.ToDouble(value));
  1626 + break;
  1627 + case "System.Decimal":
  1628 + case "System.Double":
  1629 + case "System.Single":
  1630 + cell.SetCellValue(Convert.ToDouble(value));
  1631 + break;
  1632 + default:
  1633 + cell.SetCellValue(value.ToString());
  1634 + break;
  1635 + }
  1636 + }
  1637 + #endregion
  1638 +
1252 1639 #region 获取会员类型枚举内容
1253 1640 /// <summary>
1254 1641 /// 获取会员类型枚举内容
... ...
netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs
... ... @@ -200,6 +200,25 @@ namespace NCC.Extend
200 200 // 15. 计算人均项目数(项目数/人头数)
201 201 var avgProjectPerHead = headCount > 0 ? projectCount / (decimal)headCount : 0m;
202 202  
  203 + // 16. 计算各分类消耗业绩(生美、医美、科美)
  204 + var categoryPerformanceSql = $@"
  205 + SELECT
  206 + COALESCE(SUM(CASE WHEN COALESCE(xmzl.qt2, '其他') = '生美' THEN CAST(COALESCE(xhpx.F_TotalPrice, xhpx.pxjg * xhpx.F_ProjectNumber, 0) AS DECIMAL(18,2)) ELSE 0 END), 0) as LifeBeautyPerformance,
  207 + COALESCE(SUM(CASE WHEN COALESCE(xmzl.qt2, '其他') = '医美' THEN CAST(COALESCE(xhpx.F_TotalPrice, xhpx.pxjg * xhpx.F_ProjectNumber, 0) AS DECIMAL(18,2)) ELSE 0 END), 0) as MedicalBeautyPerformance,
  208 + COALESCE(SUM(CASE WHEN COALESCE(xmzl.qt2, '其他') = '科美' THEN CAST(COALESCE(xhpx.F_TotalPrice, xhpx.pxjg * xhpx.F_ProjectNumber, 0) AS DECIMAL(18,2)) ELSE 0 END), 0) as TechBeautyPerformance
  209 + FROM lq_xh_pxmx xhpx
  210 + INNER JOIN lq_xh_hyhk xh ON xhpx.F_ConsumeInfoId = xh.F_Id AND xh.F_IsEffective = 1
  211 + LEFT JOIN lq_xmzl xmzl ON xhpx.px = xmzl.F_Id AND xmzl.F_IsEffective = 1
  212 + WHERE xhpx.F_IsEffective = 1
  213 + AND xh.md = '{input.StoreId}'
  214 + AND xh.hksj >= '{startDate:yyyy-MM-dd 00:00:00}'
  215 + AND xh.hksj <= '{endDateTime:yyyy-MM-dd HH:mm:ss}'";
  216 + var categoryPerformanceResult = await _db.Ado.SqlQueryAsync<dynamic>(categoryPerformanceSql);
  217 + var categoryPerformance = categoryPerformanceResult?.FirstOrDefault();
  218 + var lifeBeautyPerformance = categoryPerformance != null ? Convert.ToDecimal(categoryPerformance.LifeBeautyPerformance ?? 0) : 0m;
  219 + var medicalBeautyPerformance = categoryPerformance != null ? Convert.ToDecimal(categoryPerformance.MedicalBeautyPerformance ?? 0) : 0m;
  220 + var techBeautyPerformance = categoryPerformance != null ? Convert.ToDecimal(categoryPerformance.TechBeautyPerformance ?? 0) : 0m;
  221 +
203 222 var result = new StoreDashboardStatisticsOutput
204 223 {
205 224 BillingPerformance = billingAmount,
... ... @@ -219,7 +238,10 @@ namespace NCC.Extend
219 238 ProjectCount = Convert.ToInt32(projectCount),
220 239 AvgAmountPerPerson = avgAmountPerPerson,
221 240 AvgAmountPerProject = avgAmountPerProject,
222   - AvgProjectPerHead = avgProjectPerHead
  241 + AvgProjectPerHead = avgProjectPerHead,
  242 + LifeBeautyPerformance = lifeBeautyPerformance,
  243 + MedicalBeautyPerformance = medicalBeautyPerformance,
  244 + TechBeautyPerformance = techBeautyPerformance
223 245 };
224 246  
225 247 _logger.LogInformation("门店驾驶舱统计数据查询完成,门店ID:{StoreId},开单业绩:{BillingPerformance},消耗业绩:{ConsumePerformance},完成率:{CompletionRate}%,净业绩:{NetPerformance},开单次数:{BillingCount},消耗次数:{ConsumeCount},退卡次数:{RefundCount}",
... ...
netcore/src/Modularity/System/NCC.System.Interfaces/Permission/IAuthorizeService.cs
... ... @@ -46,6 +46,14 @@ namespace NCC.System.Interfaces.Permission
46 46 Task<List<ModuleDataAuthorizeSchemeEntity>> GetCurrentUserResourceAuthorize(string userId, bool isAdmin);
47 47  
48 48 /// <summary>
  49 + /// 当前用户App模块权限
  50 + /// </summary>
  51 + /// <param name="userId">用户ID</param>
  52 + /// <param name="isAdmin">是否超管</param>
  53 + /// <returns></returns>
  54 + Task<List<ModuleEntity>> GetCurrentUserAppModuleAuthorize(string userId, bool isAdmin);
  55 +
  56 + /// <summary>
49 57 /// 获取权限项ids
50 58 /// </summary>
51 59 /// <param name="roleId">角色id</param>
... ...
netcore/src/Modularity/System/NCC.System/Service/Permission/AuthorizeService.cs
... ... @@ -864,6 +864,63 @@ namespace NCC.System.Service.Permission
864 864 }
865 865  
866 866 /// <summary>
  867 + /// 当前用户App模块权限
  868 + /// </summary>
  869 + /// <param name="userId">用户ID</param>
  870 + /// <param name="isAdmin">是否超管</param>
  871 + /// <returns></returns>
  872 + [NonAction]
  873 + public async Task<List<ModuleEntity>> GetCurrentUserAppModuleAuthorize(string userId, bool isAdmin)
  874 + {
  875 + var output = new List<ModuleEntity>();
  876 + if (!isAdmin)
  877 + {
  878 + // 获取用户角色
  879 + var user = _userRepository.FirstOrDefault(u => u.Id == userId && u.DeleteMark == null);
  880 + if (user == null || string.IsNullOrEmpty(user.RoleId)) return output;
  881 +
  882 + var roleArray = user.RoleId.Split(',');
  883 + // 验证角色是否启用且未删除
  884 + var roleId = await _roleRepository.Entities.In(r => r.Id, roleArray)
  885 + .Where(r => r.EnabledMark.Equals(1) && r.DeleteMark == null)
  886 + .Select(r => r.Id)
  887 + .ToListAsync();
  888 +
  889 + if (roleId.Count == 0) return output;
  890 +
  891 + // 获取角色拥有的模块权限项ID
  892 + var items = await _authorizeRepository.Entities
  893 + .In(a => a.ObjectId, roleId)
  894 + .Where(a => a.ItemType == "module")
  895 + .GroupBy(it => new { it.ItemId })
  896 + .Select(it => new { it.ItemId })
  897 + .ToListAsync();
  898 +
  899 + if (items.Count == 0) return output;
  900 +
  901 + // 获取模块信息,并过滤Category为"App"的模块
  902 + output = await _moduleRepository.Entities
  903 + .In(m => m.Id, items.Select(it => it.ItemId).ToArray())
  904 + .Where(a => a.EnabledMark.Equals(1)
  905 + && a.DeleteMark == null
  906 + && a.Category == "App")
  907 + .OrderBy(o => o.SortCode)
  908 + .ToListAsync();
  909 + }
  910 + else
  911 + {
  912 + // 管理员返回所有App模块
  913 + output = await _moduleRepository
  914 + .Where(a => a.EnabledMark.Equals(1)
  915 + && a.DeleteMark == null
  916 + && a.Category == "App")
  917 + .OrderBy(o => o.SortCode)
  918 + .ToListAsync();
  919 + }
  920 + return output;
  921 + }
  922 +
  923 + /// <summary>
867 924 /// 当前用户模块权限资源
868 925 /// </summary>
869 926 /// <param name="userId">用户ID</param>
... ...
netcore/src/Modularity/System/NCC.System/Service/Permission/UsersCurrentService.cs
... ... @@ -156,6 +156,46 @@ namespace NCC.System.Service.Permission
156 156 }
157 157  
158 158 /// <summary>
  159 + /// 获取当前用户App权限
  160 + /// </summary>
  161 + /// <remarks>
  162 + /// 获取当前登录用户的App权限列表,根据用户的角色获取对应的App模块权限
  163 + ///
  164 + /// 权限获取逻辑:
  165 + /// 1. 获取用户关联的角色ID列表
  166 + /// 2. 验证角色是否启用且未删除
  167 + /// 3. 根据角色ID从权限表中获取模块权限项
  168 + /// 4. 过滤Category为"App"的模块
  169 + /// 5. 返回树形结构的App权限列表
  170 + ///
  171 + /// 注意事项:
  172 + /// - 管理员用户返回所有App权限
  173 + /// - 普通用户只返回其角色拥有的App权限
  174 + /// - 只返回启用且未删除的模块
  175 + /// </remarks>
  176 + /// <returns>App权限树形列表</returns>
  177 + /// <response code="200">成功返回App权限列表</response>
  178 + [HttpGet("AppAuthorize")]
  179 + public async Task<dynamic> GetAppAuthorize()
  180 + {
  181 + var userId = _userManager.UserId;
  182 + var isAdmin = _userManager.IsAdministrator;
  183 +
  184 + // 获取App模块权限列表
  185 + var appModuleList = await _authorizeService.GetCurrentUserAppModuleAuthorize(userId, isAdmin);
  186 +
  187 + if (appModuleList.Count == 0)
  188 + {
  189 + return new List<UsersCurrentAuthorizeMoldel>();
  190 + }
  191 +
  192 + // 转换为树形结构
  193 + var appModuleTree = appModuleList.Adapt<List<UsersCurrentAuthorizeMoldel>>().ToTree("-1");
  194 +
  195 + return appModuleTree;
  196 + }
  197 +
  198 + /// <summary>
159 199 /// 获取系统日志
160 200 /// </summary>
161 201 /// <param name="input">参数</param>
... ...