Browse Source

更新导入导出

pull/1/head
wanggang 1 year ago
parent
commit
ddcda67d02
  1. 29
      docs/demo/src/WTA.Shared/Controllers/GenericController.cs
  2. 2
      docs/demo/src/WTA.Shared/Data/BaseDbContext.cs
  3. 52
      docs/demo/src/WTA.Shared/ExportImport/ClosedXmlExportImportService.cs
  4. 0
      docs/demo/src/WTA.Shared/Resources/font.ttf
  5. 8
      docs/demo/src/WTA.Shared/WTA.Shared.csproj
  6. 86
      docs/demo/src/WTA/wwwroot/components/list/index.js
  7. 7
      docs/demo/src/WTA/wwwroot/request/index.js
  8. 6
      docs/demo/src/WTA/wwwroot/utils/index.js

29
docs/demo/src/WTA.Shared/Controllers/GenericController.cs

@ -1,12 +1,15 @@
using System.Globalization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using WTA.Shared.Application;
using WTA.Shared.Attributes;
using WTA.Shared.Data;
using WTA.Shared.Domain;
using WTA.Shared.ExportImport;
using WTA.Shared.Extensions;
using WTA.Shared.Mappers;
@ -41,10 +44,6 @@ public class GenericController<TEntity, TModel, TListModel, TSearchModel, TImpor
{
var isTree = typeof(TEntity).IsAssignableTo(typeof(BaseTreeEntity<TEntity>));
var query = BuildQuery(model, isTree);
foreach (var item in model.Filters)
{
query = query.Where(string.Format(CultureInfo.InvariantCulture, item.Operator, item.Property.ToPascalCase()), item.Value);
}
model.TotalCount = query.Count();
if (!string.IsNullOrEmpty(model.OrderBy))
{
@ -73,11 +72,14 @@ public class GenericController<TEntity, TModel, TListModel, TSearchModel, TImpor
{
query = query.Where(model: model.Query);
}
foreach (var item in model.Filters)
{
query = query.Where(string.Format(CultureInfo.InvariantCulture, item.Operator, item.Property.ToPascalCase()), item.Value);
}
if (isTree)
{
model.OrderBy ??= $"{nameof(BaseTreeEntity<TEntity>.ParentId)},{nameof(BaseEntity.Order)},{nameof(BaseEntity.CreatedOn)}";
}
return query;
}
@ -178,6 +180,20 @@ public class GenericController<TEntity, TModel, TListModel, TSearchModel, TImpor
}
}
[HttpGet,AllowAnonymous, Multiple, Order(-2), HtmlClass("el-button--primary")]
public virtual IActionResult Import()
{
try
{
var exportImportService = this.HttpContext.RequestServices.GetRequiredService<IExportImportService>();
return exportImportService.GetImportTemplate<TImportModel>();
}
catch (Exception ex)
{
return Problem(ex.Message);
}
}
[HttpPost, Multiple, Order(-2), HtmlClass("el-button--primary")]
public virtual IActionResult Import(IFormFile importexcelfile)
{
@ -206,7 +222,8 @@ public class GenericController<TEntity, TModel, TListModel, TSearchModel, TImpor
{
this.Repository.DisableSoftDeleteFilter();
}
return Json(query.ToList());
var exportImportService = this.HttpContext.RequestServices.GetRequiredService<IExportImportService>();
return exportImportService.Export(query.ToList().Select(o => o.ToObject<TExportModel>()).ToList());
}
catch (Exception ex)
{

2
docs/demo/src/WTA.Shared/Data/BaseDbContext.cs

@ -112,7 +112,7 @@ public abstract class BaseDbContext<T> : DbContext where T : DbContext
if (entityType.IsAssignableTo(typeof(BaseEntity)))
{
//软删除、租户过滤
this.GetType().GetMethod(nameof(this.CreateQueryFilter))?.MakeGenericMethod(entityType).Invoke(this, new object[] { modelBuilder });
//this.GetType().GetMethod(nameof(this.CreateQueryFilter))?.MakeGenericMethod(entityType).Invoke(this, new object[] { modelBuilder });
//
//基类
entityTypeBuilder.HasKey(nameof(BaseEntity.Id));

52
docs/demo/src/WTA.Shared/ExportImport/ClosedXmlExportImportService.cs

@ -5,8 +5,8 @@ using ClosedXML.Excel;
using ClosedXML.Graphics;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.OpenApi.Extensions;
using WTA.Shared.Attributes;
using WTA.Shared.Extensions;
namespace WTA.Shared.ExportImport;
@ -18,7 +18,7 @@ public class ClosedXmlExportImportService : IExportImportService
static ClosedXmlExportImportService()
{
using var fallbackFontStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{WebApp.Current.Prefix}.Infrastructure.Resources.calibril.ttf");
using var fallbackFontStream = Assembly.GetExecutingAssembly().GetManifestResourceStream($"{typeof(WTA.Shared.Resources.Resource).Namespace}.font.ttf");
LoadOptions.DefaultGraphicEngine = DefaultGraphicEngine.CreateWithFontsAndSystemFonts(fallbackFontStream);
}
@ -32,12 +32,31 @@ public class ClosedXmlExportImportService : IExportImportService
try
{
using var workbook = new XLWorkbook();
var name = typeof(TExportModel).GetCustomAttribute<DisplayAttribute>()?.Name ?? typeof(TExportModel).Name;
var name = typeof(TExportModel).GetDisplayName();
var fileName = $"{name}_导出.xlsx";
var ws = workbook.Worksheets.Add(name);
ws.Style.Font.FontName = "宋体";
//
//Internal
var type = typeof(TExportModel);
var propertyList = GetPropertiesForImportModel(type);
var rowIndex = 1;
for (var i = 0; i < propertyList.Length; i++)
{
var property = propertyList[i];
var columnIndex = i + 1;
var cell = ws.Cell(1, columnIndex);
cell.Value = property.GetDisplayName();
}
list.ForEach(model =>
{
rowIndex++;
for (var i = 0; i < propertyList.Length; i++)
{
var property = propertyList[i];
var columnIndex = i + 1;
var cell = ws.Cell(rowIndex, columnIndex);
cell.Value = property.GetValue(model)?.ToString();
}
});
//
var stream = new MemoryStream();
workbook.SaveAs(stream);
@ -57,8 +76,27 @@ public class ClosedXmlExportImportService : IExportImportService
public FileContentResult GetImportTemplate<TImportModel>()
{
//TModelType=>File
throw new NotImplementedException();
using var workbook = new XLWorkbook();
var type = typeof(TImportModel);
var name = type.GetDisplayName();
var fileName = $"{name}_导入模板.xlsx";
var ws = workbook.Worksheets.Add(name);
var properties = GetPropertiesForImportModel(type);
for (var i = 0; i < properties.Length; i++)
{
var property = properties[i];
var headerName = property.GetDisplayName();
var cell = ws.Cell(1, i + 1);
cell.Value = headerName;
}
var stream = new MemoryStream();
workbook.SaveAs(stream);
stream.Seek(0, SeekOrigin.Begin);
var result = new FileContentResult(stream.ToArray(), ContentType)
{
FileDownloadName = fileName
};
return result;
}
public IList<TImportModel> Import<TImportModel>(byte[] bytes)

0
docs/demo/src/WTA.Shared/Resources/calibril.ttf → docs/demo/src/WTA.Shared/Resources/font.ttf

8
docs/demo/src/WTA.Shared/WTA.Shared.csproj

@ -1,5 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk.Razor">
<ItemGroup>
<None Remove="Resources\font.ttf" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\font.ttf" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autofac" />
<PackageReference Include="Autofac.Configuration" />

86
docs/demo/src/WTA/wwwroot/components/list/index.js

@ -190,7 +190,12 @@ export default {
</span>
</template>
</el-drawer>
<el-dialog v-model="dialogVisible" align-center destroy-on-close>
<el-dialog
v-model="dialogVisible"
align-center
destroy-on-close
style="width:auto;min-width:300px;max-width:700px;"
>
<template #header> <span class="el-dialog__title"> {{editFormTitle}} </span> </template>
<el-row v-loading="editFormloading">
<el-col style="max-height:calc(100vh - 180px );min-height:100%;">
@ -219,18 +224,27 @@ export default {
</el-form>
</template>
<template v-else-if="editFormMode==='import'">
<el-form :model="exportModel" style="height:100%;">
<el-form :model="importModel" style="height:100%;">
<el-form-item :label="$t('部分成功')">
<el-switch v-model="exportModel.includeAll" />
<el-switch v-model="importModel.partal" />
</el-form-item>
<el-form-item :label="$t('只更新')">
<el-switch v-model="exportModel.includeDeleted" />
<el-form-item :label="$t('全部替换')">
<el-switch v-model="importModel.replace" />
</el-form-item>
<el-form-item :label="$t('导入模板')">
<el-link type="primary" @click="getImportTemplate">{{$t('下载')}}</el-link>
</el-form-item>
<el-form-item :label="$t('文件')">
<el-upload drag v-model="importModel.file">
<el-icon class="el-icon--upload"><ep-upload-filled /></el-icon>
<div class="el-upload__text">{{$t('拖放文件到此处或')}} <em>{{$t('点击上传')}}</em></div>
</el-upload>
</el-form-item>
</el-form>
</template>
<template v-else-if="editFormMode==='filter'">
<el-form :model="queryList" inline>
<el-row v-for="(item,index) in queryList" style="padding:10px;">
<el-form :model="queryList" inline class="filter">
<el-row v-for="(item,index) in queryList">
<el-col :span="6">
<el-select v-model="item.property" :placeholder="$t('字段')">
<el-option
@ -256,14 +270,14 @@ export default {
<el-col :span="6">
<el-input v-model="item.value" :placeholder="$t('值')" />
</el-col>
<el-col :span="4">
<!-- <el-col :span="4">
<el-select v-model="item.logic" :placeholder="$t('关系')">
<el-option value="and" :label="$t('且')" />
<el-option value="or" :label="$t('或')" />
</el-select>
</el-col>
</el-col> -->
<el-col :span="2">
<el-button circle @click="queryList.splice(index, 1)" style="margin-left:10px;">
<el-button circle @click="queryList.splice(index, 1)">
<template #icon>
<ep-close />
</template>
@ -272,7 +286,7 @@ export default {
</el-row>
<el-row>
<el-col>
<el-button circle @click="pushQueryList" style="margin-left:10px;">
<el-button circle @click="pushQueryList">
<template #icon>
<ep-plus />
</template>
@ -295,7 +309,8 @@ export default {
</el-dialog>
`,
styles: html`<style>
.el-overlay {
.el-form.filter .el-col {
padding: 5px;
}
</style>`,
props: ["modelValue", "schema", "controller", "query", "buttons"],
@ -335,6 +350,12 @@ export default {
includeAll: false,
includeDeleted: false,
});
const importModel = reactive({
partial: true,
replace: false,
template: null,
file: null,
});
const getSortModel = (model) => {
model.orderBy
.split(",")
@ -439,11 +460,12 @@ export default {
await load(indexUrl);
} else if (item.path === "export") {
//export
editFormTitle.value = `${t(item.path)}${schema.value?.title}`;
dialogVisible.value = true;
} else if (item.path === "import") {
//import
const url = `${baseUrl}/${item.path}`;
editFormTitle.value = `${t("import")}${schema.value?.title}`;
editFormTitle.value = `${t(item.path)}${schema.value?.title}`;
dialogVisible.value = true;
} else if (item === "filter") {
editFormTitle.value = t("自定义查询");
@ -473,16 +495,25 @@ export default {
editFormloading.value = false;
}
} else if (editFormMode.value === "details") {
await load(indexUrl);
editFormMode.value = null;
dialogVisible.value = false;
editFormMode.value = null;
} else if (editFormMode.value === "export") {
const url = `${baseUrl}/${item.path}?${qs.stringify(exportModel)}`;
// await load(exportUrl);
const postData = JSON.parse(JSON.stringify(data.value));
postData.filters = queryList.value.filter((o) => o.property && o.value);
delete postData.query["items"];
delete postData.query["id"];
const url = `${baseUrl}/${editFormMode.value}?${qs.stringify(exportModel)}`;
const response = await post(url, postData);
const downloadUrl = window.URL.createObjectURL(response.data);
const filename = response.filename;
let link = document.createElement("a");
link.href = downloadUrl;
link.download = filename;
link.click();
window.URL.revokeObjectURL(downloadUrl);
dialogVisible.value = false;
} else if (editFormMode.value === "filter") {
alert(JSON.stringify(queryList.value));
//const url = `${baseUrl}/${item.path}?${qs.stringify(exportModel)}`;
// await load(exportUrl);
await load(indexUrl);
dialogVisible.value = false;
}
};
@ -513,9 +544,20 @@ export default {
property: "",
operator: "{0}=@0",
value: "",
logic: "or",
logic: "and",
});
};
const getImportTemplate = async () => {
const url = `${baseUrl}/${editFormMode.value}`;
const response = await get(url);
const downloadUrl = window.URL.createObjectURL(response.data);
const filename = response.filename;
let link = document.createElement("a");
link.href = downloadUrl;
link.download = filename;
link.click();
window.URL.revokeObjectURL(downloadUrl);
};
onMounted(async () => {
pushQueryList();
const vm = (await get(indexUrl)).data;
@ -549,6 +591,7 @@ export default {
getClass,
sortChange,
getProp,
getImportTemplate,
editFormRef,
editFormloading,
editFormMode,
@ -556,6 +599,7 @@ export default {
editFormSchema,
editFormModel,
exportModel,
importModel,
onPageSizeChange,
onPageIndexChange,
handleSelectionChange,

7
docs/demo/src/WTA/wwwroot/request/index.js

@ -1,6 +1,7 @@
import qs from "../lib/qs/shim.js";
import { isLogin } from "../api/user.js";
import { useAppStore } from "../store/index.js";
import { getFileName } from "../utils/index.js";
const requestSettings = {
baseURL: "/api",
@ -44,7 +45,13 @@ const getResult = async (response) => {
message: messages.get(response.status),
};
if (response.status == 200) {
const contentType = response.headers.get("Content-Type");
if (contentType.indexOf("application/json") > -1) {
result.data = await response.json();
} else if (contentType === "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet") {
result.data = await response.blob();
result.filename = getFileName(response.headers.get("Content-Disposition"));
}
} else if (response.status === 400 || response.status === 500) {
result.errors = await response.json();
}

6
docs/demo/src/WTA/wwwroot/utils/index.js

@ -116,5 +116,9 @@ function getProp(instance, propPath) {
return get(instance, propPath);
}
function getFileName(contentDisposition) {
return decodeURIComponent(/filename\*=UTF-8''([\w%\-\.]+)(?:; ?|$)/i.exec(contentDisposition)[1]);
}
export default html;
export { persentFormat, bytesFormat, format, schemaToModel, listToTree, treeToList, getProp };
export { persentFormat, bytesFormat, format, schemaToModel, listToTree, treeToList, getProp, getFileName };

Loading…
Cancel
Save