mirror of
				https://github.com/nsnail/NetAdmin.git
				synced 2025-10-31 03:19:26 +08:00 
			
		
		
		
	| @@ -30,6 +30,18 @@ public abstract class RepositoryService<TEntity, TPrimary, TLogger>(BasicReposit | ||||
|         set => Rpo.DbContextOptions.EnableCascadeSave = value; | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     ///     导出实体 | ||||
|     /// </summary> | ||||
|     protected async Task<IActionResult> ExportAsync<TQuery, TExport>( // | ||||
|         Func<QueryReq<TQuery>, ISelectGrouping<TEntity, TEntity>> selector, QueryReq<TQuery> query, string fileName | ||||
|       , Expression<Func<ISelectGroupingAggregate<TEntity, TEntity>, object>> listExp) | ||||
|         where TQuery : DataAbstraction, new() | ||||
|     { | ||||
|         var list = await selector(query).Take(Numbers.MAX_LIMIT_EXPORT).ToListAsync(listExp).ConfigureAwait(false); | ||||
|         return await GetExportFileStreamAsync<TExport>(fileName, list).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
|     ///     导出实体 | ||||
|     /// </summary> | ||||
| @@ -48,26 +60,7 @@ public abstract class RepositoryService<TEntity, TPrimary, TLogger>(BasicReposit | ||||
|             ? await select.ToListAsync().ConfigureAwait(false) | ||||
|             : await select.ToListAsync(listExp).ConfigureAwait(false); | ||||
|  | ||||
|         var listTyped = list.Adapt<List<TExport>>(); | ||||
|         var stream    = new MemoryStream(); | ||||
|         var writer    = new StreamWriter(stream); | ||||
|         var csv       = new CsvWriter(writer, CultureInfo.InvariantCulture); | ||||
|         csv.WriteHeader<TExport>(); | ||||
|         await csv.NextRecordAsync().ConfigureAwait(false); | ||||
|  | ||||
|         foreach (var item in listTyped) { | ||||
|             csv.WriteRecord(item); | ||||
|             await csv.NextRecordAsync().ConfigureAwait(false); | ||||
|         } | ||||
|  | ||||
|         await csv.FlushAsync().ConfigureAwait(false); | ||||
|         _ = stream.Seek(0, SeekOrigin.Begin); | ||||
|  | ||||
|         App.HttpContext.Response.Headers.ContentDisposition | ||||
|             = new ContentDispositionHeaderValue(Chars.FLG_HTTP_HEADER_VALUE_ATTACHMENT) { | ||||
|                   FileNameStar = $"{fileName}_{DateTime.Now:yyyy.MM.dd-HH.mm.ss}.csv" | ||||
|               }.ToString(); | ||||
|         return new FileStreamResult(stream, Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_OCTET_STREAM); | ||||
|         return await GetExportFileStreamAsync<TExport>(fileName, list).ConfigureAwait(false); | ||||
|     } | ||||
|  | ||||
|     /// <summary> | ||||
| @@ -117,6 +110,31 @@ public abstract class RepositoryService<TEntity, TPrimary, TLogger>(BasicReposit | ||||
|     } | ||||
|     #endif | ||||
|  | ||||
|     private static async Task<IActionResult> GetExportFileStreamAsync<TExport>(string fileName, object list) | ||||
|     { | ||||
|         var listTyped = list.Adapt<List<TExport>>(); | ||||
|         var stream    = new MemoryStream(); | ||||
|         var writer    = new StreamWriter(stream); | ||||
|         var csv       = new CsvWriter(writer, CultureInfo.InvariantCulture); | ||||
|         csv.WriteHeader<TExport>(); | ||||
|         await csv.NextRecordAsync().ConfigureAwait(false); | ||||
|  | ||||
|         foreach (var item in listTyped) { | ||||
|             csv.WriteRecord(item); | ||||
|             await csv.NextRecordAsync().ConfigureAwait(false); | ||||
|         } | ||||
|  | ||||
|         await csv.FlushAsync().ConfigureAwait(false); | ||||
|         _ = stream.Seek(0, SeekOrigin.Begin); | ||||
|  | ||||
|         App.HttpContext.Response.Headers.ContentDisposition | ||||
|             = new ContentDispositionHeaderValue(Chars.FLG_HTTP_HEADER_VALUE_ATTACHMENT) { | ||||
|                   FileNameStar = $"{fileName}_{DateTime.Now:yyyy.MM.dd-HH.mm.ss}.csv" | ||||
|               }.ToString(); | ||||
|  | ||||
|         return new FileStreamResult(stream, Chars.FLG_HTTP_HEADER_VALUE_APPLICATION_OCTET_STREAM); | ||||
|     } | ||||
|  | ||||
|     private IUpdate<TEntity> BuildUpdate(        // | ||||
|         TEntity             entity               // | ||||
|       , IEnumerable<string> includeFields        // | ||||
|   | ||||
| @@ -19,6 +19,8 @@ public record CreateLoginLogReq : Sys_LoginLog, IRegister | ||||
|     { | ||||
|         var              body      = s.Detail.ResponseBody.ToObject<RestfulInfo<LoginRsp>>(); | ||||
|         ContextUserToken userToken = null; | ||||
|  | ||||
|         // ReSharper disable once InvertIf | ||||
|         if (body.Data?.AccessToken != null) { | ||||
|             try { | ||||
|                 userToken = ContextUserToken.Create(body.Data.AccessToken); | ||||
|   | ||||
| @@ -10,7 +10,7 @@ | ||||
|     }, | ||||
|     "dependencies": { | ||||
|         "@element-plus/icons-vue": "^2.3.1", | ||||
|         "ace-builds": "^1.35.2", | ||||
|         "ace-builds": "^1.35.4", | ||||
|         "aieditor": "^1.0.13", | ||||
|         "axios": "^1.7.2", | ||||
|         "clipboard": "^2.0.11", | ||||
| @@ -18,7 +18,7 @@ | ||||
|         "cropperjs": "^1.6.2", | ||||
|         "crypto-js": "^4.2.0", | ||||
|         "echarts": "^5.5.1", | ||||
|         "element-plus": "^2.7.7", | ||||
|         "element-plus": "^2.7.8", | ||||
|         "json-bigint": "^1.0.0", | ||||
|         "json5-to-table": "^0.1.8", | ||||
|         "markdown-it": "^14.1.0", | ||||
| @@ -28,7 +28,7 @@ | ||||
|         "qrcodejs2": "^0.0.2", | ||||
|         "sortablejs": "^1.15.2", | ||||
|         "vkbeautify": "^0.99.3", | ||||
|         "vue": "^3.4.31", | ||||
|         "vue": "^3.4.34", | ||||
|         "vue-i18n": "^9.13.1", | ||||
|         "vue-router": "^4.4.0", | ||||
|         "vue3-ace-editor": "^2.2.4", | ||||
| @@ -37,12 +37,12 @@ | ||||
|         "vuex": "^4.1.0" | ||||
|     }, | ||||
|     "devDependencies": { | ||||
|         "@vitejs/plugin-vue": "^5.0.5", | ||||
|         "@vitejs/plugin-vue": "^5.1.1", | ||||
|         "prettier": "^3.3.3", | ||||
|         "prettier-plugin-organize-attributes": "^1.0.0", | ||||
|         "sass": "^1.77.8", | ||||
|         "terser": "^5.31.3", | ||||
|         "vite": "^5.3.4" | ||||
|         "vite": "^5.3.5" | ||||
|     }, | ||||
|     "browserslist": [ | ||||
|         "> 1%", | ||||
|   | ||||
| @@ -1,30 +0,0 @@ | ||||
| <template> | ||||
|     <p>{{ ip ?? '-' }}</p> | ||||
|     <p style="overflow: hidden">{{ region ?? '-' }}</p> | ||||
| </template> | ||||
| <script> | ||||
| import http from '@/utils/request' | ||||
|  | ||||
| export default { | ||||
|     emits: [], | ||||
|     props: ['ip'], | ||||
|     data() { | ||||
|         return { | ||||
|             region: null, | ||||
|         } | ||||
|     }, | ||||
|     mounted() {}, | ||||
|     created() { | ||||
|         if (this.ip) { | ||||
|             this.region = '...' | ||||
|             http.get(`http://ip.line92.xyz/?ip=${this.ip}`).then((x) => { | ||||
|                 this.region = x.region | ||||
|             }) | ||||
|         } | ||||
|     }, | ||||
|     components: {}, | ||||
|     computed: {}, | ||||
|     methods: {}, | ||||
| } | ||||
| </script> | ||||
| <style scoped></style> | ||||
| @@ -2,7 +2,7 @@ | ||||
|     <form @keyup.enter="search" @submit.prevent="search" class="right-panel-search"> | ||||
|         <el-date-picker | ||||
|             v-if="hasDate" | ||||
|             v-model="form.dy.createdTime" | ||||
|             v-model="form.dy[this.dateField]" | ||||
|             :end-placeholder="$t('结束日期')" | ||||
|             :format="dateFormat" | ||||
|             :range-separator="$t('至')" | ||||
| @@ -83,6 +83,7 @@ import vkbeautify from 'vkbeautify/index' | ||||
| export default { | ||||
|     emits: ['search', 'reset'], | ||||
|     props: { | ||||
|         dateField: { type: String, default: 'createdTime' }, | ||||
|         hasDate: { type: Boolean, default: true }, | ||||
|         dateType: { type: String, default: 'daterange' }, | ||||
|         dateFormat: { type: String, default: 'YYYY-MM-DD' }, | ||||
| @@ -114,8 +115,8 @@ export default { | ||||
|                     text: this.$t('后退一日'), | ||||
|                     value: () => { | ||||
|                         try { | ||||
|                             const start = new Date(new Date(this.form.dy.createdTime[0]) - 3600 * 1000 * 24) | ||||
|                             const end = new Date(new Date(this.form.dy.createdTime[1]) - 3600 * 1000 * 24) | ||||
|                             const start = new Date(new Date(this.form.dy[this.dateField][0]) - 3600 * 1000 * 24) | ||||
|                             const end = new Date(new Date(this.form.dy[this.dateField][1]) - 3600 * 1000 * 24) | ||||
|                             return [start, end] | ||||
|                         } catch {} | ||||
|                     }, | ||||
| @@ -356,8 +357,8 @@ export default { | ||||
|                     text: this.$t('后退一时'), | ||||
|                     value: () => { | ||||
|                         try { | ||||
|                             const start = new Date(new Date(this.form.dy.createdTime[0]) - 3600 * 1000) | ||||
|                             const end = new Date(new Date(this.form.dy.createdTime[1]) - 3600 * 1000) | ||||
|                             const start = new Date(new Date(this.form.dy[this.dateField][0]) - 3600 * 1000) | ||||
|                             const end = new Date(new Date(this.form.dy[this.dateField][1]) - 3600 * 1000) | ||||
|                             return [start, end] | ||||
|                         } catch {} | ||||
|                     }, | ||||
|   | ||||
| @@ -24,7 +24,12 @@ | ||||
|         </div> | ||||
|         <div class="sc-statistic-content"> | ||||
|             <span v-if="prefix" class="sc-statistic-content-prefix">{{ prefix }}</span> | ||||
|             <span class="sc-statistic-content-value">{{ cmtValue }}</span> | ||||
|             <span class="sc-statistic-content-value"> | ||||
|                 <slot v-if="$slots.content" name="content"></slot> | ||||
|                 <template v-else> | ||||
|                     {{ cmtValue }} | ||||
|                 </template> | ||||
|             </span> | ||||
|             <span v-if="suffix" class="sc-statistic-content-suffix">{{ suffix }}</span> | ||||
|         </div> | ||||
|         <div v-if="description || $slots.default" class="sc-statistic-description"> | ||||
|   | ||||
| @@ -15,6 +15,7 @@ | ||||
|                             label: 'name', | ||||
|                         }" | ||||
|                         @node-click="click" | ||||
|                         default-expand-all | ||||
|                         node-key="id" | ||||
|                         ref="dic"> | ||||
|                         <template #default="{ _, data }"> | ||||
|   | ||||
| @@ -121,7 +121,7 @@ export default { | ||||
|             this.apis = [] | ||||
|             const ips = data.data.rows?.map((x) => x.createdClientIp) ?? [] | ||||
|             const res = await Promise.all([ | ||||
|                 ips && ips.length > 0 ? http.get(`http://ip.line92.xyz/?ip=${ips.join('&ip=')}`) : new Promise((x) => x({ data: [] })), | ||||
|                 ips && ips.length > 0 ? http.get(`https://ip.tools92.top/?ip=${ips.join('&ip=')}`) : new Promise((x) => x({ data: [] })), | ||||
|             ]) | ||||
|             this.ips = res[0] | ||||
|         }, | ||||
|   | ||||
| @@ -239,7 +239,7 @@ export default { | ||||
|                       }) | ||||
|                     : new Promise((x) => x({ data: [] })), | ||||
|  | ||||
|                 ips && ips.length > 0 ? http.get(`http://ip.line92.xyz/?ip=${ips.join('&ip=')}`) : new Promise((x) => x({ data: [] })), | ||||
|                 ips && ips.length > 0 ? http.get(`https://ip.tools92.top/?ip=${ips.join('&ip=')}`) : new Promise((x) => x({ data: [] })), | ||||
|             ]) | ||||
|             this.owners = res[0].data | ||||
|             this.apis = res[1].data | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 GitHub
						GitHub