Commit d21065a0bb1568c5f091eec8e258426942e45719
1 parent
c57702ff
添加获取当前用户App权限接口
- 在IAuthorizeService接口中添加GetCurrentUserAppModuleAuthorize方法 - 在AuthorizeService中实现根据用户角色获取App权限的逻辑 - 在UsersCurrentService中添加GetAppAuthorize接口(GET /api/permission/Users/Current/AppAuthorize) - 权限获取逻辑:根据用户角色从权限表中获取Category为'App'的模块权限 - 管理员返回所有App权限,普通用户返回角色权限 - 返回树形结构的App权限列表 其他修改: - 更新门店看板相关功能 - 更新会员相关功能 - 更新样式文件
Showing
11 changed files
with
1308 additions
and
75 deletions
antis-ncc-admin/public/index.html
| @@ -8,6 +8,10 @@ | @@ -8,6 +8,10 @@ | ||
| 8 | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> | 8 | <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"> |
| 9 | <link rel="icon" href="<%= BASE_URL %>favicon.ico"> | 9 | <link rel="icon" href="<%= BASE_URL %>favicon.ico"> |
| 10 | <title><%= webpackConfig.name %></title> | 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 | <script src="<%= BASE_URL %>cdn/echarts/4.2.1/echarts.min.js"></script> | 15 | <script src="<%= BASE_URL %>cdn/echarts/4.2.1/echarts.min.js"></script> |
| 12 | 16 | ||
| 13 | </head> | 17 | </head> |
antis-ncc-admin/src/styles/index.scss
| @@ -10,7 +10,7 @@ body { | @@ -10,7 +10,7 @@ body { | ||
| 10 | -moz-osx-font-smoothing: grayscale; | 10 | -moz-osx-font-smoothing: grayscale; |
| 11 | -webkit-font-smoothing: antialiased; | 11 | -webkit-font-smoothing: antialiased; |
| 12 | text-rendering: optimizeLegibility; | 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 | // label { | 16 | // label { |
antis-ncc-admin/src/views/extend/storeDashboard/index.vue
| @@ -74,6 +74,30 @@ | @@ -74,6 +74,30 @@ | ||
| 74 | formatMoney(storeData.Performance.NetPerformance) : '0.00' }}</div> | 74 | formatMoney(storeData.Performance.NetPerformance) : '0.00' }}</div> |
| 75 | <div class="stat-trend" v-if="false">+15.8%</div> | 75 | <div class="stat-trend" v-if="false">+15.8%</div> |
| 76 | </div> | 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 | </div> | 101 | </div> |
| 78 | </div> | 102 | </div> |
| 79 | </div> | 103 | </div> |
| @@ -182,7 +206,7 @@ | @@ -182,7 +206,7 @@ | ||
| 182 | <el-table-column prop="itemName" label="品项名称" min-width="180" /> | 206 | <el-table-column prop="itemName" label="品项名称" min-width="180" /> |
| 183 | <el-table-column prop="billingAmount" label="开单金额" width="140" align="right"> | 207 | <el-table-column prop="billingAmount" label="开单金额" width="140" align="right"> |
| 184 | <template slot-scope="scope"> | 208 | <template slot-scope="scope"> |
| 185 | - <span style="font-weight: 600; color: #67C23A;">¥{{ | 209 | + <span style="font-weight: 600; color: #22c55e;">¥{{ |
| 186 | formatMoney(scope.row.billingAmount) }}</span> | 210 | formatMoney(scope.row.billingAmount) }}</span> |
| 187 | </template> | 211 | </template> |
| 188 | </el-table-column> | 212 | </el-table-column> |
| @@ -235,7 +259,7 @@ | @@ -235,7 +259,7 @@ | ||
| 235 | <el-table-column prop="itemName" label="品项名称" min-width="150" /> | 259 | <el-table-column prop="itemName" label="品项名称" min-width="150" /> |
| 236 | <el-table-column prop="consumeAmount" label="消耗金额" width="120" align="right"> | 260 | <el-table-column prop="consumeAmount" label="消耗金额" width="120" align="right"> |
| 237 | <template slot-scope="scope"> | 261 | <template slot-scope="scope"> |
| 238 | - <span style="font-weight: 600; color: #409EFF;">¥{{ | 262 | + <span style="font-weight: 600; color: #22c55e;">¥{{ |
| 239 | formatMoney(scope.row.consumeAmount) }}</span> | 263 | formatMoney(scope.row.consumeAmount) }}</span> |
| 240 | </template> | 264 | </template> |
| 241 | </el-table-column> | 265 | </el-table-column> |
| @@ -619,7 +643,10 @@ export default { | @@ -619,7 +643,10 @@ export default { | ||
| 619 | RefundAmount: response.data.RefundAmount || 0, | 643 | RefundAmount: response.data.RefundAmount || 0, |
| 620 | RefundCount: response.data.RefundCount || 0, | 644 | RefundCount: response.data.RefundCount || 0, |
| 621 | RemainingRightsAmount: response.data.RemainingRightsAmount || 0, | 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 | Operation: { | 651 | Operation: { |
| 625 | HeadCount: response.data.HeadCount || 0, | 652 | HeadCount: response.data.HeadCount || 0, |
| @@ -1901,17 +1928,49 @@ export default { | @@ -1901,17 +1928,49 @@ export default { | ||
| 1901 | 1928 | ||
| 1902 | <style lang="scss" scoped> | 1929 | <style lang="scss" scoped> |
| 1903 | .store-dashboard { | 1930 | .store-dashboard { |
| 1904 | - padding: 20px; | ||
| 1905 | - background: #f5f7fa; | 1931 | + padding: 16px; |
| 1932 | + background: linear-gradient(135deg, #f5f7fa 0%, #e8ecf1 50%, #f0f4f8 100%); | ||
| 1906 | min-height: calc(100vh - 84px); | 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 | .filter-bar { | 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 | border-radius: 12px; | 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 | .filter-form { | 1975 | .filter-form { |
| 1917 | margin: 0; | 1976 | margin: 0; |
| @@ -1921,9 +1980,38 @@ export default { | @@ -1921,9 +1980,38 @@ export default { | ||
| 1921 | } | 1980 | } |
| 1922 | 1981 | ||
| 1923 | ::v-deep .el-form-item__label { | 1982 | ::v-deep .el-form-item__label { |
| 1924 | - font-weight: 500; | 1983 | + font-weight: 600; |
| 1925 | color: #606266; | 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,11 +2020,15 @@ export default { | ||
| 1932 | display: flex; | 2020 | display: flex; |
| 1933 | justify-content: space-between; | 2021 | justify-content: space-between; |
| 1934 | align-items: center; | 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 | border-radius: 12px; | 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 | .header-left { | 2033 | .header-left { |
| 1942 | .store-info { | 2034 | .store-info { |
| @@ -1945,15 +2037,55 @@ export default { | @@ -1945,15 +2037,55 @@ export default { | ||
| 1945 | gap: 16px; | 2037 | gap: 16px; |
| 1946 | 2038 | ||
| 1947 | .store-avatar { | 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 | display: flex; | 2045 | display: flex; |
| 1953 | align-items: center; | 2046 | align-items: center; |
| 1954 | justify-content: center; | 2047 | justify-content: center; |
| 1955 | color: #fff; | 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 | .store-details { | 2091 | .store-details { |
| @@ -1966,8 +2098,11 @@ export default { | @@ -1966,8 +2098,11 @@ export default { | ||
| 1966 | .store-name { | 2098 | .store-name { |
| 1967 | margin: 0; | 2099 | margin: 0; |
| 1968 | font-size: 24px; | 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,10 +2115,92 @@ export default { | ||
| 1980 | .meta-item { | 2115 | .meta-item { |
| 1981 | display: flex; | 2116 | display: flex; |
| 1982 | align-items: center; | 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 | i { | 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,29 +2215,56 @@ export default { | ||
| 1998 | 2215 | ||
| 1999 | .core-stat-item { | 2216 | .core-stat-item { |
| 2000 | padding: 16px 20px; | 2217 | padding: 16px 20px; |
| 2001 | - border-radius: 10px; | 2218 | + border-radius: 12px; |
| 2002 | min-width: 140px; | 2219 | min-width: 140px; |
| 2003 | text-align: center; | 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 | &.primary { | 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 | &.success { | 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 | &.info { | 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 | &.warning { | 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 | .stat-label { | 2270 | .stat-label { |
| @@ -2032,7 +2276,10 @@ export default { | @@ -2032,7 +2276,10 @@ export default { | ||
| 2032 | .stat-value { | 2276 | .stat-value { |
| 2033 | font-size: 22px; | 2277 | font-size: 22px; |
| 2034 | font-weight: 700; | 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 | margin-bottom: 6px; | 2283 | margin-bottom: 6px; |
| 2037 | } | 2284 | } |
| 2038 | 2285 | ||
| @@ -2057,9 +2304,10 @@ export default { | @@ -2057,9 +2304,10 @@ export default { | ||
| 2057 | .main-content { | 2304 | .main-content { |
| 2058 | display: grid; | 2305 | display: grid; |
| 2059 | grid-template-columns: 1fr 400px; | 2306 | grid-template-columns: 1fr 400px; |
| 2060 | - gap: 20px; | ||
| 2061 | - margin-bottom: 20px; | 2307 | + gap: 16px; |
| 2308 | + margin-bottom: 0; | ||
| 2062 | align-items: start; | 2309 | align-items: start; |
| 2310 | + flex: 1; | ||
| 2063 | 2311 | ||
| 2064 | .content-left { | 2312 | .content-left { |
| 2065 | display: flex; | 2313 | display: flex; |
| @@ -2081,11 +2329,22 @@ export default { | @@ -2081,11 +2329,22 @@ export default { | ||
| 2081 | // 图表卡片 | 2329 | // 图表卡片 |
| 2082 | .chart-card { | 2330 | .chart-card { |
| 2083 | border-radius: 12px; | 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 | ::v-deep .el-card__header { | 2343 | ::v-deep .el-card__header { |
| 2087 | padding: 16px 20px; | 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 | ::v-deep .el-card__body { | 2350 | ::v-deep .el-card__body { |
| @@ -2101,11 +2360,22 @@ export default { | @@ -2101,11 +2360,22 @@ export default { | ||
| 2101 | 2360 | ||
| 2102 | .chart-card-small { | 2361 | .chart-card-small { |
| 2103 | border-radius: 12px; | 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 | ::v-deep .el-card__header { | 2374 | ::v-deep .el-card__header { |
| 2107 | padding: 14px 18px; | 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 | ::v-deep .el-card__body { | 2381 | ::v-deep .el-card__body { |
| @@ -2122,11 +2392,22 @@ export default { | @@ -2122,11 +2392,22 @@ export default { | ||
| 2122 | // 指标卡片 | 2392 | // 指标卡片 |
| 2123 | .metrics-card { | 2393 | .metrics-card { |
| 2124 | border-radius: 12px; | 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 | ::v-deep .el-card__header { | 2406 | ::v-deep .el-card__header { |
| 2128 | padding: 14px 18px; | 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 | ::v-deep .el-card__body { | 2413 | ::v-deep .el-card__body { |
| @@ -2142,13 +2423,17 @@ export default { | @@ -2142,13 +2423,17 @@ export default { | ||
| 2142 | display: flex; | 2423 | display: flex; |
| 2143 | align-items: center; | 2424 | align-items: center; |
| 2144 | padding: 12px; | 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 | border-radius: 8px; | 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 | &:hover { | 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 | .metric-icon { | 2439 | .metric-icon { |
| @@ -2202,12 +2487,195 @@ export default { | @@ -2202,12 +2487,195 @@ export default { | ||
| 2202 | align-items: center; | 2487 | align-items: center; |
| 2203 | font-size: 15px; | 2488 | font-size: 15px; |
| 2204 | font-weight: 600; | 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 | i { | 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 | font-size: 16px; | 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,7 +2699,10 @@ export default { | ||
| 2231 | .rank-number { | 2699 | .rank-number { |
| 2232 | font-size: 36px; | 2700 | font-size: 36px; |
| 2233 | font-weight: 700; | 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 | .rank-total { | 2708 | .rank-total { |
| @@ -2254,7 +2725,7 @@ export default { | @@ -2254,7 +2725,7 @@ export default { | ||
| 2254 | } | 2725 | } |
| 2255 | 2726 | ||
| 2256 | &.good { | 2727 | &.good { |
| 2257 | - background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%); | 2728 | + background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%); |
| 2258 | color: #fff; | 2729 | color: #fff; |
| 2259 | } | 2730 | } |
| 2260 | 2731 | ||
| @@ -2292,11 +2763,22 @@ export default { | @@ -2292,11 +2763,22 @@ export default { | ||
| 2292 | // 经营提示卡片 | 2763 | // 经营提示卡片 |
| 2293 | .tips-card { | 2764 | .tips-card { |
| 2294 | border-radius: 12px; | 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 | ::v-deep .el-card__header { | 2777 | ::v-deep .el-card__header { |
| 2298 | padding: 14px 18px; | 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 | ::v-deep .el-card__body { | 2784 | ::v-deep .el-card__body { |
| @@ -2330,26 +2812,29 @@ export default { | @@ -2330,26 +2812,29 @@ export default { | ||
| 2330 | } | 2812 | } |
| 2331 | 2813 | ||
| 2332 | &.success { | 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 | i { | 2819 | i { |
| 2337 | - color: #67C23A; | 2820 | + color: #22c55e; |
| 2338 | } | 2821 | } |
| 2339 | } | 2822 | } |
| 2340 | 2823 | ||
| 2341 | &.warning { | 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 | i { | 2829 | i { |
| 2346 | - color: #E6A23C; | 2830 | + color: #f59e0b; |
| 2347 | } | 2831 | } |
| 2348 | } | 2832 | } |
| 2349 | 2833 | ||
| 2350 | &.info { | 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 | i { | 2839 | i { |
| 2355 | color: #909399; | 2840 | color: #909399; |
| @@ -2366,11 +2851,22 @@ export default { | @@ -2366,11 +2851,22 @@ export default { | ||
| 2366 | // 快速数据洞察卡片 | 2851 | // 快速数据洞察卡片 |
| 2367 | .insight-card { | 2852 | .insight-card { |
| 2368 | border-radius: 12px; | 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 | ::v-deep .el-card__header { | 2865 | ::v-deep .el-card__header { |
| 2372 | padding: 14px 18px; | 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 | ::v-deep .el-card__body { | 2872 | ::v-deep .el-card__body { |
| @@ -2381,18 +2877,22 @@ export default { | @@ -2381,18 +2877,22 @@ export default { | ||
| 2381 | .insight-item { | 2877 | .insight-item { |
| 2382 | padding: 14px; | 2878 | padding: 14px; |
| 2383 | margin-bottom: 12px; | 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 | border-radius: 8px; | 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 | &:last-child { | 2887 | &:last-child { |
| 2390 | margin-bottom: 0; | 2888 | margin-bottom: 0; |
| 2391 | } | 2889 | } |
| 2392 | 2890 | ||
| 2393 | &:hover { | 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 | transform: translateX(4px); | 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 | .insight-header { | 2898 | .insight-header { |
| @@ -2411,7 +2911,10 @@ export default { | @@ -2411,7 +2911,10 @@ export default { | ||
| 2411 | .insight-value { | 2911 | .insight-value { |
| 2412 | font-size: 20px; | 2912 | font-size: 20px; |
| 2413 | font-weight: 700; | 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 | margin-bottom: 6px; | 2918 | margin-bottom: 6px; |
| 2416 | } | 2919 | } |
| 2417 | 2920 | ||
| @@ -2427,11 +2930,22 @@ export default { | @@ -2427,11 +2930,22 @@ export default { | ||
| 2427 | // 关键指标卡片 | 2930 | // 关键指标卡片 |
| 2428 | .key-metrics-card { | 2931 | .key-metrics-card { |
| 2429 | border-radius: 12px; | 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 | ::v-deep .el-card__header { | 2944 | ::v-deep .el-card__header { |
| 2433 | padding: 14px 18px; | 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 | ::v-deep .el-card__body { | 2951 | ::v-deep .el-card__body { |
| @@ -2472,16 +2986,59 @@ export default { | @@ -2472,16 +2986,59 @@ export default { | ||
| 2472 | .table-section { | 2986 | .table-section { |
| 2473 | .table-card { | 2987 | .table-card { |
| 2474 | border-radius: 12px; | 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 | ::v-deep .el-card__header { | 3000 | ::v-deep .el-card__header { |
| 2478 | padding: 16px 20px; | 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 | ::v-deep .el-card__body { | 3007 | ::v-deep .el-card__body { |
| 2483 | padding: 20px; | 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,6 +128,8 @@ | ||
| 128 | @click="addOrUpdateHandle()">新增会员</el-button> | 128 | @click="addOrUpdateHandle()">新增会员</el-button> |
| 129 | <el-button icon="el-icon-download" :loading="exportLoading" | 129 | <el-button icon="el-icon-download" :loading="exportLoading" |
| 130 | @click="exportMemberItems()">导出数据</el-button> | 130 | @click="exportMemberItems()">导出数据</el-button> |
| 131 | + <el-button icon="el-icon-tickets" :loading="exportRightsLoading" | ||
| 132 | + @click="exportMemberRights()">导出会员权益</el-button> | ||
| 131 | <el-button v-has="'btn_remove'" type="danger" icon="el-icon-delete" plain | 133 | <el-button v-has="'btn_remove'" type="danger" icon="el-icon-delete" plain |
| 132 | @click="handleBatchRemoveDel()">批量删除</el-button> | 134 | @click="handleBatchRemoveDel()">批量删除</el-button> |
| 133 | </el-button-group> | 135 | </el-button-group> |
| @@ -530,6 +532,7 @@ export default { | @@ -530,6 +532,7 @@ export default { | ||
| 530 | list: [], | 532 | list: [], |
| 531 | listLoading: true, | 533 | listLoading: true, |
| 532 | exportLoading: false, | 534 | exportLoading: false, |
| 535 | + exportRightsLoading: false, | ||
| 533 | multipleSelection: [], total: 0, | 536 | multipleSelection: [], total: 0, |
| 534 | listQuery: { | 537 | listQuery: { |
| 535 | currentPage: 1, | 538 | currentPage: 1, |
| @@ -893,6 +896,35 @@ export default { | @@ -893,6 +896,35 @@ export default { | ||
| 893 | this.$message.error('导出失败:' + (err.msg || err.message || '未知错误')) | 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 | search() { | 928 | search() { |
| 897 | this.listQuery = { | 929 | this.listQuery = { |
| 898 | currentPage: 1, | 930 | currentPage: 1, |
| @@ -3145,4 +3177,30 @@ export default { | @@ -3145,4 +3177,30 @@ export default { | ||
| 3145 | } | 3177 | } |
| 3146 | } | 3178 | } |
| 3147 | } | 3179 | } |
| 3148 | -</style> | ||
| 3149 | \ No newline at end of file | 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,6 +94,21 @@ namespace NCC.Extend.Entitys.Dto.LqStoreDashboard | ||
| 94 | /// 人均项目数(项目数/人头数) | 94 | /// 人均项目数(项目数/人头数) |
| 95 | /// </summary> | 95 | /// </summary> |
| 96 | public decimal AvgProjectPerHead { get; set; } | 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,6 +2,7 @@ | ||
| 2 | using System.Collections.Generic; | 2 | using System.Collections.Generic; |
| 3 | using System.IO; | 3 | using System.IO; |
| 4 | using System.Linq; | 4 | using System.Linq; |
| 5 | +using System.Text; | ||
| 5 | using System.Threading.Tasks; | 6 | using System.Threading.Tasks; |
| 6 | using Mapster; | 7 | using Mapster; |
| 7 | using Microsoft.AspNetCore.Mvc; | 8 | using Microsoft.AspNetCore.Mvc; |
| @@ -40,6 +41,9 @@ using SqlSugar; | @@ -40,6 +41,9 @@ using SqlSugar; | ||
| 40 | using Yitter.IdGenerator; | 41 | using Yitter.IdGenerator; |
| 41 | using NCC.Extend.Entitys.lq_kd_deductinfo; | 42 | using NCC.Extend.Entitys.lq_kd_deductinfo; |
| 42 | using Microsoft.AspNetCore.Authorization; | 43 | using Microsoft.AspNetCore.Authorization; |
| 44 | +using NPOI.XSSF.UserModel; | ||
| 45 | +using NPOI.SS.UserModel; | ||
| 46 | +using System.Reflection; | ||
| 43 | 47 | ||
| 44 | namespace NCC.Extend.LqKhxx | 48 | namespace NCC.Extend.LqKhxx |
| 45 | { | 49 | { |
| @@ -1249,6 +1253,389 @@ namespace NCC.Extend.LqKhxx | @@ -1249,6 +1253,389 @@ namespace NCC.Extend.LqKhxx | ||
| 1249 | } | 1253 | } |
| 1250 | #endregion | 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 | #region 获取会员类型枚举内容 | 1639 | #region 获取会员类型枚举内容 |
| 1253 | /// <summary> | 1640 | /// <summary> |
| 1254 | /// 获取会员类型枚举内容 | 1641 | /// 获取会员类型枚举内容 |
netcore/src/Modularity/Extend/NCC.Extend/LqStoreDashboardService.cs
| @@ -200,6 +200,25 @@ namespace NCC.Extend | @@ -200,6 +200,25 @@ namespace NCC.Extend | ||
| 200 | // 15. 计算人均项目数(项目数/人头数) | 200 | // 15. 计算人均项目数(项目数/人头数) |
| 201 | var avgProjectPerHead = headCount > 0 ? projectCount / (decimal)headCount : 0m; | 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 | var result = new StoreDashboardStatisticsOutput | 222 | var result = new StoreDashboardStatisticsOutput |
| 204 | { | 223 | { |
| 205 | BillingPerformance = billingAmount, | 224 | BillingPerformance = billingAmount, |
| @@ -219,7 +238,10 @@ namespace NCC.Extend | @@ -219,7 +238,10 @@ namespace NCC.Extend | ||
| 219 | ProjectCount = Convert.ToInt32(projectCount), | 238 | ProjectCount = Convert.ToInt32(projectCount), |
| 220 | AvgAmountPerPerson = avgAmountPerPerson, | 239 | AvgAmountPerPerson = avgAmountPerPerson, |
| 221 | AvgAmountPerProject = avgAmountPerProject, | 240 | AvgAmountPerProject = avgAmountPerProject, |
| 222 | - AvgProjectPerHead = avgProjectPerHead | 241 | + AvgProjectPerHead = avgProjectPerHead, |
| 242 | + LifeBeautyPerformance = lifeBeautyPerformance, | ||
| 243 | + MedicalBeautyPerformance = medicalBeautyPerformance, | ||
| 244 | + TechBeautyPerformance = techBeautyPerformance | ||
| 223 | }; | 245 | }; |
| 224 | 246 | ||
| 225 | _logger.LogInformation("门店驾驶舱统计数据查询完成,门店ID:{StoreId},开单业绩:{BillingPerformance},消耗业绩:{ConsumePerformance},完成率:{CompletionRate}%,净业绩:{NetPerformance},开单次数:{BillingCount},消耗次数:{ConsumeCount},退卡次数:{RefundCount}", | 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,6 +46,14 @@ namespace NCC.System.Interfaces.Permission | ||
| 46 | Task<List<ModuleDataAuthorizeSchemeEntity>> GetCurrentUserResourceAuthorize(string userId, bool isAdmin); | 46 | Task<List<ModuleDataAuthorizeSchemeEntity>> GetCurrentUserResourceAuthorize(string userId, bool isAdmin); |
| 47 | 47 | ||
| 48 | /// <summary> | 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 | /// 获取权限项ids | 57 | /// 获取权限项ids |
| 50 | /// </summary> | 58 | /// </summary> |
| 51 | /// <param name="roleId">角色id</param> | 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,6 +864,63 @@ namespace NCC.System.Service.Permission | ||
| 864 | } | 864 | } |
| 865 | 865 | ||
| 866 | /// <summary> | 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 | /// </summary> | 925 | /// </summary> |
| 869 | /// <param name="userId">用户ID</param> | 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,6 +156,46 @@ namespace NCC.System.Service.Permission | ||
| 156 | } | 156 | } |
| 157 | 157 | ||
| 158 | /// <summary> | 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 | /// </summary> | 200 | /// </summary> |
| 161 | /// <param name="input">参数</param> | 201 | /// <param name="input">参数</param> |