feat: initialize Evergreen Health backend

This commit is contained in:
fuenmao
2026-09-21 14:08:22 +08:00
commit 8553df5dd2
493 changed files with 75649 additions and 0 deletions
@@ -0,0 +1,10 @@
{
"DbConnection": {
"ConnectionConfigs": [
{
"DbType": "MySqlConnector",
"ConnectionString": "Server=127.0.0.1;Port=3306;Database=evergreen_health;User ID=root;Password=CHANGE_ME;CharSet=utf8mb4;SslMode=None;Pooling=true;Connection Timeout=30"
}
]
}
}
@@ -0,0 +1,29 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Evergreen.Health.Api.Controllers
{
[AllowAnonymous]
public class HomeController : Controller
{
//private readonly ISystemService _systemService;
//public HomeController(ISystemService systemService)
//{
// _systemService = systemService;
//}
public IActionResult Index()
{
//ViewBag.Description = _systemService.GetDescription();
return View();
}
}
}
+18
View File
@@ -0,0 +1,18 @@
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 5005
COPY . .
# 设置语言/区域设置环境变量
ENV LANG zh-Hans
# 使用阿里云的镜像源进行更新
# .NET6使用
RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/' /etc/apt/sources.list
# .NET8使用
# RUN sed -i 's/deb.debian.org/mirrors.aliyun.com/' /etc/apt/sources.list.d/debian.sources
# 更新包管理器并安装free命令
RUN apt-get update && apt-get install -y procps
ENTRYPOINT ["dotnet", "Evergreen.Health.Api.dll"]
@@ -0,0 +1,67 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<SatelliteResourceLanguages>zh-Hans</SatelliteResourceLanguages>
<PublishReadyToRunComposite>true</PublishReadyToRunComposite>
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
<UserSecretsId>ad9369d1-f29b-4f8f-a7df-8b4d7aa0726b</UserSecretsId>
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
<GenerateSatelliteAssembliesForCore>true</GenerateSatelliteAssembliesForCore>
<Copyright>Admin.NET</Copyright>
<Description>常青树健康管理系统</Description>
<AssemblyVersion>1.0.0</AssemblyVersion>
<FileVersion>1.0.0</FileVersion>
<Version>1.0.0</Version>
</PropertyGroup>
<ItemGroup>
<Compile Remove="logs\**" />
<Compile Remove="publish\**" />
<EmbeddedResource Remove="logs\**" />
<EmbeddedResource Remove="publish\**" />
<None Remove="logs\**" />
<None Remove="publish\**" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\avatar\**" CopyToPublishDirectory="Never" />
<Content Update="wwwroot\upload\**" CopyToPublishDirectory="Never" />
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\template\**">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<None Update="GeoLite2-City.mmdb">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="ip2region.db">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="UpdateScripts\1.0.0.sql">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="sensitive-words.txt">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Content Update="wwwroot\upload\logo.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Admin.NET.Web.Core\Admin.NET.Web.Core.csproj" />
</ItemGroup>
</Project>
+14
View File
@@ -0,0 +1,14 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace Evergreen.Health.Api;
/// <summary>
/// 供集成测试使用
/// </summary>
public class FakeStartup
{
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 60 MiB

+33
View File
@@ -0,0 +1,33 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
Serve.Run(RunOptions.Default.AddWebComponent<WebComponent>());
public class WebComponent : IWebComponent
{
public void Load(WebApplicationBuilder builder, ComponentContext componentContext)
{
// 本机数据库配置。该文件已加入 .gitignore,仅用于保存真实连接信息。
builder.Configuration.AddJsonFile(
"Configuration/Database.local.json",
optional: true,
reloadOnChange: true);
// 设置日志过滤
builder.Logging.AddFilter((provider, category, logLevel) =>
{
return !new[] { "Microsoft.Hosting", "Microsoft.AspNetCore" }.Any(u => category.StartsWith(u)) && logLevel >= LogLevel.Information;
});
// 设置接口超时时间和上传大小-Kestrel
builder.WebHost.ConfigureKestrel(u =>
{
u.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(30);
u.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(30);
u.Limits.MaxRequestBodySize = null;
});
}
}
@@ -0,0 +1,32 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:5005"
// "sslPort": 44325
}
},
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"Evergreen.Health.Api": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "",
"applicationUrl": "http://localhost:5005",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
+129
View File
@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Admin.NET 通用权限开发平台" xml:space="preserve">
<value>Admin NET General Permission Development Platform</value>
</data>
<data name="API 接口" xml:space="preserve">
<value>API Interfaces</value>
</data>
<data name="源码地址" xml:space="preserve">
<value>Source Address</value>
</data>
</root>
@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="Admin NET General Permission Development Platform" xml:space="preserve">
<value>Admin.NET 通用权限开发平台</value>
</data>
<data name="API Interfaces" xml:space="preserve">
<value>API 接口</value>
</data>
<data name="Source Address" xml:space="preserve">
<value>源码地址</value>
</data>
</root>
+43
View File
@@ -0,0 +1,43 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Furion;
using System.Reflection;
namespace Evergreen.Health.Api;
/// <summary>
/// 解决单文件发布问题
/// </summary>
public class SingleFilePublish : ISingleFilePublish
{
/// <summary>
/// 解决单文件不能扫描的程序集
/// </summary>
/// <remarks>和 <see cref="IncludeAssemblyNames"/>可同时配置</remarks>
/// <returns></returns>
public Assembly[] IncludeAssemblies()
{
// 需要 Furion 框架扫描哪些程序集就写上去即可
return Array.Empty<Assembly>();
}
/// <summary>
/// 解决单文件不能扫描的程序集名称
/// </summary>
/// <remarks>和 <see cref="IncludeAssemblies"/>可同时配置</remarks>
/// <returns></returns>
public string[] IncludeAssemblyNames()
{
// 需要 Furion 框架扫描哪些程序集就写上去即可
return new[]
{
"Evergreen.Health.Application",
"Admin.NET.Core",
"Admin.NET.Web.Core",
};
}
}
@@ -0,0 +1,3 @@
-- 1.0.0.sql
-- update
-- 2025-05-24 11:00:00
@@ -0,0 +1,7 @@
@{
ViewData["Title"] = ViewBag.Description;
}
<div style="text-align:center;margin-top:50px;">
<img src="~/images/logo.png" style="height:100px;" />
</div>
@@ -0,0 +1,11 @@
<!DOCTYPE html>
<html lang="zh-cmn-Hans">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>@ViewData["Title"] - Furion</title>
</head>
<body>
@RenderBody()
</body>
</html>
@@ -0,0 +1,2 @@
@using Evergreen.Health.Api
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}
@@ -0,0 +1,5 @@
{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
"ConfigurationScanDirectories": [ "Configuration", "" ]
}
+5
View File
@@ -0,0 +1,5 @@
{
"$schema": "https://gitee.com/dotnetchina/Furion/raw/v4/schemas/v4/furion-schema.json",
"ConfigurationScanDirectories": [ "Configuration", "" ] // 扫描配置文件json文件夹(自动合并该文件夹里面所有json文件)
}
+4
View File
@@ -0,0 +1,4 @@
装X|特么的|SB|屌爆了|你妹|马勒戈壁|蛋疼|买了个表|妈蛋|日了狗
吃翔
装13
屁民
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.webServer>
<security>
<requestFiltering>
<requestLimits maxAllowedContentLength="1073741824" maxQueryString="1073741824" />
</requestFiltering>
</security>
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="InProcess">
<handlerSettings>
<handlerSetting name="experimentalEnableShadowCopy" value="true" />
<handlerSetting name="shadowCopyDirectory" value="../ShadowCopyDirectory/" />
<!-- Only enable handler logging if you encounter issues-->
<!--<handlerSetting name="debugFile" value=".\logs\aspnetcore-debug.log" />-->
<!--<handlerSetting name="debugLevel" value="FILE,TRACE" />-->
</handlerSettings>
</aspNetCore>
</system.webServer>
</configuration>
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

@@ -0,0 +1,30 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
namespace @(Model.NameSpace);
/// <summary>
/// @(Model.BusName)输出参数
/// </summary>
public class @(Model.ClassName)Dto
{
@foreach (var column in Model.TableField){
if(column.EffectType == "ForeignKey"){
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
@:public string @(column.PropertyName)FkColumn { get; set; }
@:
}
}
@foreach (var column in Model.TableField){
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
@:public @column.NetType @column.PropertyName { get; set; }
@:
}
}
@@ -0,0 +1,46 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
@if(Model.BaseClassName!=""){
@:using Admin.NET.Core;
@:using SqlSugar;
}
namespace @(Model.NameSpace);
/// <summary>
/// @(Model.Description)
/// </summary>
[Tenant("@(Model.ConfigId)")]
[SugarTable("@(Model.TableName)", "@(Model.Description)")]
public partial class @(Model.EntityName) @Model.BaseClassName
{
@foreach (var column in Model.TableField) {
var propSuffix = "";
if (column.IsPrimarykey && (Model.BaseClassName == "" || Model.BaseClassName != "" && column.DbColumnName.ToLower() != "id")) {
propSuffix = $", IsPrimaryKey = true, IsIdentity = {column.IsIdentity.ToString().ToLower()}";
}
if (column.DataType.TrimEnd('?') == "string") {
propSuffix += $", Length = {column.Length}";
} else if (column.DataType.TrimEnd('?') == "decimal") {
propSuffix += $", Length = {column.Length}, DecimalDigits={column.DecimalDigits}";
}
if(!string.IsNullOrWhiteSpace(column.DefaultValue)){
propSuffix +=$", DefaultValue = \"{column.DefaultValue}\"";
}
@:/// <summary>
@:/// @column.ColumnDescription
@:/// </summary>
if(!column.IsNullable){
@:[Required]
}
@:[SugarColumn(ColumnName = "@column.DbColumnName", ColumnDescription = "@column.ColumnDescription"@propSuffix)]
@:public virtual @column.DataType @column.PropertyName { get; set; }
@:
}
}
@@ -0,0 +1,200 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core;
using System.ComponentModel.DataAnnotations;
using Magicodes.ExporterAndImporter.Core;
using Magicodes.ExporterAndImporter.Excel;
namespace @(Model.NameSpace);
/// <summary>
/// @(Model.BusName)基础输入参数
/// </summary>
public class @(Model.ClassName)BaseInput
{
@foreach (var column in Model.PrimaryKeyFieldList.Concat(Model.AddUpdateFieldList)){
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
if (column.EffectType is "EnumSelector" or "DictSelector") {
@:[Dict(@(column.EffectType == "EnumSelector" ? $"nameof({column.DictTypeCode})" : $"\"{column.DictTypeCode}\""), AllowNullValue=true)]
}
if (column.WhetherRequired == "Y") {
@:[Required(ErrorMessage = "@(column.ColumnComment)不能为空")]
}
@:public virtual @Model.GetNullableNetType(column.NetType) @column.PropertyName { get; set; }
@:
}
}
/// <summary>
/// @(Model.BusName)分页查询输入参数
/// </summary>
public class Page@(Model.ClassName)Input : BasePageInput
{
@foreach (var column in Model.TableField.Where(u => u.WhetherQuery == "Y")){
if(column.NetType?.TrimEnd('?') == "DateTime" && column.QueryType == "~"){
@:/// <summary>
@:/// @(column.ColumnComment)范围
@:/// </summary>
@: public DateTime?[] @(column.PropertyName)Range { get; set; }
} else {
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
if (column.EffectType is "EnumSelector" or "DictSelector") {
@:[Dict(@(column.EffectType == "EnumSelector" ? $"nameof({column.DictTypeCode})" : $"\"{column.DictTypeCode}\""), AllowNullValue=true)]
}
@:public @Model.GetNullableNetType(column.NetType) @column.PropertyName { get; set; }
}
@:
}
@if (Model.ImportFieldList.Count > 0){
var primaryKey = Model.PrimaryKeyFieldList.First();
@:/// <summary>
@:/// 选中主键列表
@:/// </summary>
@: public List<@(primaryKey.NetType)> SelectKeyList { get; set; }
}
}
/// <summary>
/// @(Model.BusName)增加输入参数
/// </summary>
public class Add@(Model.ClassName)Input
{
@foreach (var column in Model.AddUpdateFieldList){
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
if (column.EffectType is "EnumSelector" or "DictSelector") {
@:[Dict(@(column.EffectType == "EnumSelector" ? $"nameof({column.DictTypeCode})" : $"\"{column.DictTypeCode}\""), AllowNullValue=true)]
}
if (column.WhetherRequired == "Y") {
@:[Required(ErrorMessage = "@(column.ColumnComment)不能为空")]
}
if (column.NetType.TrimEnd('?').EndsWith("string") && column.ColumnLength > 0){
@:[MaxLength(@column.ColumnLength, ErrorMessage = "@(column.ColumnComment)字符长度不能超过@(column.ColumnLength)")]
}
@:public @Model.GetNullableNetType(column.NetType) @column.PropertyName { get; set; }
@:
}
}
/// <summary>
/// @(Model.BusName)删除输入参数
/// </summary>
public class Delete@(Model.ClassName)Input
{
@foreach (var column in Model.PrimaryKeyFieldList) {
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
@:[Required(ErrorMessage = "@(column.ColumnComment)不能为空")]
@:public @Model.GetNullableNetType(column.NetType) @column.PropertyName { get; set; }
@:
}
}
/// <summary>
/// @(Model.BusName)更新输入参数
/// </summary>
public class Update@(Model.ClassName)Input
{
@foreach (var column in Model.PrimaryKeyFieldList.Concat(Model.AddUpdateFieldList)){
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
if (column.EffectType is "EnumSelector" or "DictSelector") {
@:[Dict(@(column.EffectType == "EnumSelector" ? $"nameof({column.DictTypeCode})" : $"\"{column.DictTypeCode}\""), AllowNullValue=true)]
}
if (column.WhetherRequired == "Y" || column.ColumnKey == "True") {
@:[Required(ErrorMessage = "@(column.ColumnComment)不能为空")]
}
if (column.NetType.TrimEnd('?').EndsWith("string") && column.ColumnLength > 0){
@:[MaxLength(@column.ColumnLength, ErrorMessage = "@(column.ColumnComment)字符长度不能超过@(column.ColumnLength)")]
}
@:public @Model.GetNullableNetType(column.NetType) @column.PropertyName { get; set; }
@:
}
}
/// <summary>
/// @(Model.BusName)主键查询输入参数
/// </summary>
public class QueryById@(Model.ClassName)Input : Delete@(Model.ClassName)Input
{
}
@if (Model.DropdownFieldList.Count > 0) {
@:/// <summary>
@:/// 下拉数据输入参数
@:/// </summary>
@:public class DropdownData@(Model.ClassName)Input
@:{
@:/// <summary>
@:/// 是否用于分页查询
@:/// </summary>
@:public bool FromPage { get; set; }
@:}
@:
}
@if (Model.HasSetStatus) {
@:/// <summary>
@:/// 设置状态输入参数
@:/// </summary>
@:public class Set@(Model.ClassName)StatusInput : BaseStatusInput
@:{
@foreach (var column in Model.PrimaryKeyFieldList.Where(u => u.PropertyName != "Id")) {
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
@:[Required(ErrorMessage = "@(column.ColumnComment)不能为空")]
@:public @Model.GetNullableNetType(column.NetType) @column.PropertyName { get; set; }
@:
}
@:}
@:
}
@if (Model.ImportFieldList.Count > 0){
@:/// <summary>
@:/// @(Model.BusName)数据导入实体
@:/// </summary>
@:[ExcelImporter(SheetIndex = 1, IsOnlyErrorRows = true)]
@:public class Import@(Model.ClassName)Input : BaseImportInput
@:{
foreach (var column in Model.ImportFieldList){
var headerName = (column.WhetherRequired == "Y" ? "*" : "") + column.ColumnComment;
if(column.EffectType == "ForeignKey" || column.EffectType == "ApiTreeSelector" || column.EffectType == "DictSelector") {
@:/// <summary>
@:/// @column.ColumnComment 关联值
@:/// </summary>
@:[ImporterHeader(IsIgnore = true)]
@:[ExporterHeader(IsIgnore = true)]
@:public @Model.GetNullableNetType(column.NetType) @column.PropertyName { get; set; }
@:
@:/// <summary>
@:/// @column.ColumnComment 文本
@:/// </summary>
if (column.EffectType == "DictSelector") {
@:[Dict(@($"\"{column.DictTypeCode}\""))]
}
@:[ImporterHeader(Name = "@(headerName)")]
@:[ExporterHeader("@(headerName)", Format = "@", Width = 25, IsBold = true)]
@:public string @column.ExtendedPropertyName { get; set; }
} else {
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
@:[ImporterHeader(Name = "@(headerName)")]
@:[ExporterHeader("@(headerName)", Format = "@", Width = 25, IsBold = true)]
@:public @Model.GetNullableNetType(column.NetType) @column.PropertyName { get; set; }
}
@:
}
@:}
}
@@ -0,0 +1,71 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Magicodes.ExporterAndImporter.Core;
namespace @(Model.NameSpace);
/// <summary>
/// @(Model.BusName)输出参数
/// </summary>
public class @(Model.ClassName)Output
{
@foreach (var column in Model.TableField){
@:/// <summary>
@:/// @column.ColumnComment
@:/// </summary>
@:public @column.NetType @(column.PropertyName) { get; set; }
if(column.EffectType == "ForeignKey" || column.EffectType == "ApiTreeSelector") {
@:
@:/// <summary>
@:/// @(column.ColumnComment) 描述
@:/// </summary>
@:public string @(column.ExtendedPropertyName) { get; set; }
}else if(column.EffectType == "Upload" || column.EffectType == "Upload_SingleFile"){
@:
@:/// <summary>
@:/// @(column.ColumnComment) 附件信息
@:/// </summary>
@:public SysFile @(column.ExtendedPropertyName) { get; set; }
}
@:
}
}
@if (Model.ApiTreeFieldList.Count > 0) {
foreach(var column in Model.ApiTreeFieldList) {
@:/// <summary>
@:/// @column.ColumnComment 树选择器输出参数
@:/// </summary>
@:public class Tree@(column.PropertyNameTrimEndId)Output : @(Model.ClassName)
@:{
@:/// <summary>
@:/// 节点值
@:/// </summary>
@:public @column.NetType Value { get; set; }
@:
@:/// <summary>
@:/// 节点文本
@:/// </summary>
@:public string Label { get; set; }
@:
@:/// <summary>
@:/// 子集数据
@:/// </summary>
@:public List<Tree@(column.PropertyNameTrimEndId)Output> Children { get; set; }
@:}
@:
}
}
@if (Model.ImportFieldList.Count > 0) {
@:
@:/// <summary>
@:/// @(Model.BusName)数据导入模板实体
@:/// </summary>
@:public class Export@(Model.ClassName)Output : Import@(Model.ClassName)Input
@:{
@: [ImporterHeader(IsIgnore = true)]
@: [ExporterHeader(IsIgnore = true)]
@: public override string Error { get; set; }
@:}
}
@@ -0,0 +1,29 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core;
using @Model.EntityNameSpace;
namespace @(Model.NameSpace);
/// <summary>
/// @(Model.Description) 表种子数据
/// </summary>
public class @(Model.SeedDataName) : ISqlSugarEntitySeedData<@(Model.EntityName)>
{
/// <summary>
/// 种子数据
/// </summary>
/// <returns></returns>
public IEnumerable<@(Model.EntityName)> HasData()
{
return new List<@(Model.EntityName)> {
@foreach (var record in Model.RecordList) {
@:new() { @record },
}
};
}
}
@@ -0,0 +1,421 @@
// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证(版本 2.0)进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动!任何基于本项目二次开发而产生的一切法律纠纷和责任,我们不承担任何责任!
using Admin.NET.Core.Service;
using Microsoft.AspNetCore.Http;
using Furion.DatabaseAccessor;
using Furion.FriendlyException;
using Mapster;
using SqlSugar;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using @(Model.NameSpace).Entity;
namespace @(Model.NameSpace);
/// <summary>
/// @(Model.BusName)服务 🧩
/// </summary>
[ApiDescriptionSettings(@(Model.ProjectLastName)Const.GroupName, Order = 100)]
public partial class @(Model.ClassName)Service : IDynamicApiController, ITransient
{
private readonly SqlSugarRepository<@(Model.ClassName)> _@(Model.LowerClassName)Rep;
@foreach(var kv in Model.InjectServiceMap) {
@:private readonly @(kv.Key) _@(kv.Value);
}
public @(Model.ClassName)Service(SqlSugarRepository<@(Model.ClassName)> @(Model.LowerClassName)Rep@(Model.InjectServiceArgs))
{
_@(Model.LowerClassName)Rep = @(Model.LowerClassName)Rep;
@foreach(var kv in Model.InjectServiceMap) {
@:_@(kv.Value) = @(kv.Value);
}
}
/// <summary>
/// 分页查询@(Model.BusName) 🔖
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("分页查询@(Model.BusName)")]
[ApiDescriptionSettings(Name = "Page"), HttpPost]
public async Task<SqlSugarPagedList<@(Model.ClassName)Output>> Page(Page@(Model.ClassName)Input input)
{
input.Keyword = input.Keyword?.Trim();
var query = _@(Model.LowerClassName)Rep.AsQueryable()
@{
string joinTableName = "u";
var queryFields = Model.TableField.Where(u => u.WhetherQuery == "Y");
// 关键字模糊查询
if (queryFields.Any(u => u.QueryType == "like")) {
@:.WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => @string.Join(" || ", queryFields.Where(u => u.QueryType == "like").Select(col => $"u.{col.PropertyName}.Contains(input.Keyword)")))
}
// 单字段模糊查询
foreach(var column in queryFields.Where(u => u.QueryType == "like")) {
@:.WhereIF(!string.IsNullOrWhiteSpace(input.@(column.PropertyName)), u => u.@(column.PropertyName).Contains(input.@(column.PropertyName).Trim()))
}
// 字段组合查询
foreach(var column in queryFields.Where(u => u.QueryType != "like")) {
if (column.NetType.TrimEnd('?') == "string") {
@:.WhereIF(!string.IsNullOrWhiteSpace(input.@(column.PropertyName)), u => u.@(column.PropertyName) == input.@(column.PropertyName))
} else if (column.NetType.TrimEnd('?') == "int" || column.NetType.TrimEnd('?') == "long") {
@:.WhereIF(input.@(column.PropertyName) != null, u => u.@(column.PropertyName) @(column.QueryType) input.@(column.PropertyName))
} else if (column.NetType.TrimEnd('?').EndsWith("Enum")) {
@:.WhereIF(input.@(column.PropertyName).HasValue, u => u.@(column.PropertyName) == input.@(column.PropertyName))
} else if (column.NetType.TrimEnd('?') == "DateTime" && column.QueryType == "~") {
@:.WhereIF(input.@(column.PropertyName)Range?.Length == 2, u => u.@(column.PropertyName) >= input.@(column.PropertyName)Range[0] && u.@(column.PropertyName) <= input.@(column.PropertyName)Range[1])
} else if (column.NetType.TrimEnd('?').EndsWith("bool")) {
@:.WhereIF(input.@(column.PropertyName).HasValue, u => u.@(column.PropertyName) == input.@(column.PropertyName))
}
}
// 联表
if (Model.HasJoinTable) {
foreach (var column in Model.TableField.Where(u => u.EffectType == "ForeignKey" || u.EffectType == "ApiTreeSelector")){
joinTableName += ", " + column.LowerPropertyNameTrimEndId;
@:.LeftJoin<@column.FkEntityName>((@joinTableName) => u.@(column.PropertyName) == @(column.LowerPropertyNameTrimEndId).@(column.FkLinkColumnName))
}
// 查询列表
@:.Select((@joinTableName) => new @(Model.ClassName)Output
@:{
foreach (var column in Model.TableField) {
if(column.NetType.TrimEnd('?').EndsWith("Enum")) {
@:@(column.PropertyName) = (@(column.NetType))u.@(column.PropertyName),
}else{
@:@(column.PropertyName) = u.@(column.PropertyName),
}
if (column.EffectType == "ForeignKey" || column.EffectType == "ApiTreeSelector") {
@:@(column.ExtendedPropertyName) = @column.GetDisplayColumn(column.LowerPropertyNameTrimEndId),
}
}
@:});
} else {
// 无联表
@:.Select<@(Model.ClassName)Output>();
}
}
return await query.OrderBuilder(input).ToPagedListAsync(input.Page, input.PageSize);
}
/// <summary>
/// 获取@(Model.BusName)详情 ℹ️
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("获取@(Model.BusName)详情")]
[ApiDescriptionSettings(Name = "Detail"), HttpGet]
public async Task<@(Model.ClassName)> Detail([FromQuery] QueryById@(Model.ClassName)Input input)
{
return await _@(Model.LowerClassName)Rep.GetFirstAsync(u => @Model.PrimaryKeysFormat(" && ", "u.{0} == input.{0}"));
}
/// <summary>
/// 增加@(Model.BusName) ➕
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("增加@(Model.BusName)")]
[ApiDescriptionSettings(Name = "Add"), HttpPost]
public async Task<long> Add(Add@(Model.ClassName)Input input)
{
var entity = input.Adapt<@(Model.ClassName)>();
@foreach (var config in Model.TableUniqueConfigList) {
@:if (await _@(Model.LowerClassName)Rep.IsAnyAsync(u => @(string.Join(" && ", @config.Columns.Select(x => $"u.{x} != null && u.{x} == input.{x}"))))) throw Oops.Oh("@(config.Message)已存在");
}
return await _@(Model.LowerClassName)Rep.InsertAsync(entity) ? entity.Id : 0;
}
/// <summary>
/// 更新@(Model.BusName) ✏️
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("更新@(Model.BusName)")]
[ApiDescriptionSettings(Name = "Update"), HttpPost]
public async Task Update(Update@(Model.ClassName)Input input)
{
@{
var primaryKeyWhere = Model.PrimaryKeysFormat(" && ", "u.{0} != input.{0}");
foreach (var config in Model.TableUniqueConfigList) {
@:if (await _@(Model.LowerClassName)Rep.IsAnyAsync(u => @primaryKeyWhere && @config.Format(" && ", "u.{0} != null && u.{0} == input.{0}"))) throw Oops.Oh("@(config.Message)已存在");
}
}
var entity = input.Adapt<@(Model.ClassName)>();
await _@(Model.LowerClassName)Rep.AsUpdateable(entity)
@if (Model.IgnoreUpdateFieldList.Count > 0) {
@:.IgnoreColumns(u => new {
foreach (var column in Model.IgnoreUpdateFieldList) {
@:u.@(column.PropertyName),
}
@:})
}
.ExecuteCommandAsync();
}
/// <summary>
/// 删除@(Model.BusName) ❌
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("删除@(Model.BusName)")]
[ApiDescriptionSettings(Name = "Delete"), HttpPost]
public async Task Delete(Delete@(Model.ClassName)Input input)
{
var entity = await _@(Model.LowerClassName)Rep.GetFirstAsync(u => @Model.PrimaryKeysFormat(" && ", "u.{0} == input.{0}")) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
@{
@if(Model.TableField.Where(x => x.ColumnName == "IsDelete").Any())
{
@:await _@(Model.LowerClassName)Rep.FakeDeleteAsync(entity); //假删除
}
else{
@:await _@(Model.LowerClassName)Rep.DeleteAsync(entity); //真删除
}
}
}
/// <summary>
/// 批量删除@(Model.BusName) ❌
/// </summary>
/// <param name="input"></param>
/// <returns></returns>
[DisplayName("批量删除@(Model.BusName)")]
[ApiDescriptionSettings(Name = "BatchDelete"), HttpPost]
public async Task<int> BatchDelete([Required(ErrorMessage = "主键列表不能为空")]List<Delete@(Model.ClassName)Input> input)
{
var exp = Expressionable.Create<@(Model.ClassName)>();
foreach (var row in input) exp = exp.Or(it => @Model.PrimaryKeysFormat(" && ", "it.{0} == row.{0}"));
var list = await _@(Model.LowerClassName)Rep.AsQueryable().Where(exp.ToExpression()).ToListAsync();
@{
@if(Model.TableField.Where(x => x.ColumnName == "IsDelete").Any())
{
@:return await _@(Model.LowerClassName)Rep.FakeDeleteAsync(list); //假删除
}
else{
@:return await _@(Model.LowerClassName)Rep.Context.Deleteable(list).ExecuteCommandAsync(); //真删除--返回受影响的行数
}
}
}
@if (Model.HasSetStatus) {
@:
@:/// <summary>
@:/// 设置@(Model.BusName)状态 🚫
@:/// </summary>
@:/// <param name="input"></param>
@:/// <returns></returns>
@:[DisplayName("设置@(Model.BusName)状态")]
@:[ApiDescriptionSettings(Name = "SetStatus"), HttpPost]
@:public async Task Set@(Model.ClassName)Status(Set@(Model.ClassName)StatusInput input)
@:{
@:await _@(Model.LowerClassName)Rep.AsUpdateable().SetColumns(u => u.Status, input.Status).Where(u => @Model.PrimaryKeysFormat(" && ", "u.{0} == input.{0}")).ExecuteCommandAsync();
@:}
}
@foreach (var column in Model.UploadFieldList) {
@:
@:/// <summary>
@:/// 上传@(column.ColumnComment) ⬆️
@:/// </summary>
@:/// <param name="file"></param>
@:/// <returns></returns>
@:[DisplayName("上传@(column.ColumnComment)")]
@:[ApiDescriptionSettings(Name = "Upload@(column.PropertyName)"), HttpPost]
@:public async Task<SysFile> Upload@(column.PropertyName)([Required] IFormFile file)
@:{
@:return await _sysFileService.UploadFile(new UploadFileInput { File = file },"upload/@(Model.ClassName)/@(column.PropertyName)");
@:}
}
@if (Model.DropdownFieldList.Count > 0) {
@:
@:/// <summary>
@:/// 获取下拉列表数据 🔖
@:/// </summary>
@:/// <returns></returns>
@:[DisplayName("获取下拉列表数据")]
@:[ApiDescriptionSettings(Name = "DropdownData"), HttpPost]
@:public async Task<Dictionary<string, dynamic>> DropdownData(DropdownData@(Model.ClassName)Input input)
@:{
foreach (var column in Model.DropdownFieldList) {
@:var @(column.LowerPropertyName)Data = await _@(Model.LowerClassName)Rep.Context.Queryable<@(column.FkEntityName)>()
if (column.EffectType != "ApiTreeSelector") {
@:.InnerJoinIF<@Model.ClassName>(input.FromPage, (u, r) => u.@(column.FkLinkColumnName) == r.@(column.PropertyName))
}
if (column.EffectType != "ApiTreeSelector") {
@:.Select(u => new {
@:Value = u.@(column.FkLinkColumnName),
@:Label = @column.GetDisplayColumn("u")
@:}).ToListAsync();
} else {
@:.Select(u => new Tree@(column.PropertyNameTrimEndId)Output {
@:Value = u.@(column.FkLinkColumnName),
@:Label = @column.GetDisplayColumn("u")
@:}, true).ToTreeAsync(u => u.Children, u => u.@(column.PidColumn), @(column.WhetherRequired == "Y" ? "0" : "null"));
}
}
@:return new Dictionary<string, dynamic>
@:{
foreach (var column in Model.DropdownFieldList) {
@:{ "@(column.LowerPropertyName)", @(column.LowerPropertyName)Data },
}
@:};
@:}
}
@if (Model.ImportFieldList.Count > 0) {
@:
@:/// <summary>
@:/// 导出@(Model.BusName)记录 🔖
@:/// </summary>
@:/// <param name="input"></param>
@:/// <returns></returns>
@:[DisplayName("导出@(Model.BusName)记录")]
@:[ApiDescriptionSettings(Name = "Export"), HttpPost, NonUnify]
@:public async Task<IActionResult> Export(Page@(Model.ClassName)Input input)
@:{
@:var list = (await Page(input)).Items?.Adapt<List<Export@(Model.ClassName)Output>>() ?? new();
@:if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.@(Model.PrimaryKeyFieldList.First().PropertyName))).ToList();
var dictFields = Model.TableField.Where(x => x.WhetherImport == "Y" && x.EffectType == "DictSelector") ?? default;
foreach (var column in dictFields) {
@:var @(column.LowerPropertyName)DictMap = _sysDictTypeService.GetDataList(new GetDataDictTypeInput { Code = "@(column.DictTypeCode)" }).Result.ToDictionary(x => x.Value, x => x.Label);
}
if (dictFields.Count() > 0) {
@:list.ForEach(e => {
foreach (var column in dictFields) {
@:e.@(column.ExtendedPropertyName) = @(column.LowerPropertyName)DictMap.GetValueOrDefault(e.@(column.PropertyName) ?? "", e.@(column.PropertyName));
}
@:});
}
@:return ExcelHelper.ExportTemplate(list, "@(Model.BusName)导出记录");
@:}
@:
@:/// <summary>
@:/// 下载@(Model.BusName)数据导入模板 ⬇️
@:/// </summary>
@:/// <returns></returns>
@:[DisplayName("下载@(Model.BusName)数据导入模板")]
@:[ApiDescriptionSettings(Name = "Import"), HttpGet, NonUnify]
@:public IActionResult DownloadTemplate()
@:{
var fieldsList = Model.ImportFieldList.Where(u => u.EffectType == "ForeignKey" || u.EffectType == "ApiTreeSelector").ToList();
if (fieldsList.Any()) {
@:return ExcelHelper.ExportTemplate(new List<Export@(Model.ClassName)Output>(), "@(Model.BusName)导入模板", (_, info) =>
@:{
foreach (var column in fieldsList) {
var columnList = column.FkDisplayColumnList.Select(n => $"{{u.{n}}}").ToList();
@:if (nameof(Export@(Model.ClassName)Output.@column.ExtendedPropertyName) == info.Name) return _@(Model.LowerClassName)Rep.Context.Queryable<@(column.FkEntityName)>().Select(u => $"@(string.Join("-", columnList))").Distinct().ToList();
}
@:return null;
@:});
} else {
@:return ExcelHelper.ExportTemplate(new List<Export@(Model.ClassName)Output>(), "@(Model.BusName)导入模板");
}
@:}
@:
@:private static readonly object _@(Model.LowerClassName)ImportLock = new object();
@:/// <summary>
@:/// 导入@(Model.BusName)记录 💾
@:/// </summary>
@:/// <returns></returns>
@:[DisplayName("导入@(Model.BusName)记录")]
@:[ApiDescriptionSettings(Name = "Import"), HttpPost, NonUnify, UnitOfWork]
@:public IActionResult ImportData([Required] IFormFile file)
@:{
@:lock (_@(Model.LowerClassName)ImportLock)
@:{
var dictTableField = Model.TableField.Where(x => x.WhetherImport == "Y" && x.EffectType == "DictSelector") ?? default;
foreach (var column in dictTableField){
@:var @(column.LowerPropertyName)DictMap = _sysDictTypeService.GetDataList(new GetDataDictTypeInput { Code = "@(column.DictTypeCode)" }).Result.ToDictionary(x => x.Label!, x => x.Value);
}
@:var stream = ExcelHelper.ImportData<Import@(Model.ClassName)Input, @(Model.ClassName)>(file, (list, markerErrorAction) =>
@:{
@:_sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>
@:{
foreach (var column in Model.ImportFieldList.Where(u => u.EffectType == "ForeignKey" || u.EffectType == "ApiTreeSelector")) {
@:// 链接 @(column.ColumnComment)
@:var @(column.LowerPropertyName)LabelList = pageItems.Where(x => x.@column.ExtendedPropertyName != null).Select(x => x.@column.ExtendedPropertyName).Distinct().ToList();
@:if (@(column.LowerPropertyName)LabelList.Any()) {
var columnList = column.FkDisplayColumnList.Select(n => $"{{u.{n}}}").ToList();
@:var @(column.LowerPropertyName)LinkMap = _@(Model.LowerClassName)Rep.Context.Queryable<@(column.FkEntityName)>().Where(u => @(column.LowerPropertyName)LabelList.Contains($"@(string.Join("-", columnList))")).ToList().ToDictionary(u => $"@(string.Join("-", columnList))", u => u.@(column.FkLinkColumnName) as @(column.NetType.TrimEnd('?') == "long" ? "long?": column.NetType));
@:pageItems.ForEach(e => {
@:e.@(column.PropertyName) = @(column.LowerPropertyName)LinkMap.GetValueOrDefault(e.@column.ExtendedPropertyName ?? "");
@:if (e.@(column.PropertyName) == null) e.Error = "@(column.ColumnComment)链接失败";
@:});
@:}
}
if (dictTableField.Any()) {
@:
@:// 映射字典值
@:foreach(var item in pageItems) {
@:System.Text.StringBuilder sbError = new System.Text.StringBuilder();
foreach (var column in dictTableField) {
@:if (!string.IsNullOrWhiteSpace(item.@(column.ExtendedPropertyName))) {
@:item.@(column.PropertyName) = @(column.LowerPropertyName)DictMap.GetValueOrDefault(item.@(column.ExtendedPropertyName));
@:if (item.@(column.PropertyName) == null) sbError.AppendLine("@(column.ColumnComment)字典映射失败");
@:}
}
@:item.Error = sbError.ToString();
@:}
}
@:
@:// 校验并过滤必填基本类型为null的字段
@:var rows = pageItems.Where(x => {
@:if (!string.IsNullOrWhiteSpace(x.Error)) return false;
foreach (var column in Model.ImportFieldList.Where(x => x.WhetherRequired == "Y" && Regex.IsMatch(x.NetType, "(int|long|double|float|bool|Enum[?]?)"))){
@:if (x.@(column.PropertyName) == null){
@:x.Error = "@(column.ColumnComment)不能为空";
@:return false;
@:}
}
@:return true;
@:}).Adapt<List<@(Model.ClassName)>>();
@{var updateFields = new List<string>();}
@:
@:var storageable = _@(Model.LowerClassName)Rep.Context.Storageable(rows)
foreach (var column in Model.ImportFieldList){
if (column.WhetherImport == "Y"){
updateFields.Add(column.PropertyName);
}
if (column.WhetherRequired == "Y"){
if(column.NetType.TrimEnd('?') == "string"){
@:.SplitError(it => string.IsNullOrWhiteSpace(it.Item.@(column.PropertyName)), "@(column.ColumnComment)不能为空")
} else if(column.NetType.EndsWith('?') == true){
@:.SplitError(it => it.Item.@(column.PropertyName) == null, "@(column.ColumnComment)不能为空")
}}
if (column.NetType?.TrimEnd('?') == "string" && column.ColumnLength > 0){
@:.SplitError(it => it.Item.@(column.PropertyName)?.Length > @(column.ColumnLength), "@(column.ColumnComment)长度不能超过@(column.ColumnLength)个字符")
}}
if(Model.TableUniqueConfigList.Count>0){
foreach (var config in Model.TableUniqueConfigList) {
@:.WhereColumns(it => new { @config.Format(", ", "it.{0}") })
}
@:.SplitInsert(it=> !it.Any())
@:.SplitUpdate(it=> it.Any())
}else{
@:.SplitInsert(_=> true) // 没有设置唯一键代表插入所有数据
}
@:.ToStorage();
@:
@:storageable.AsInsertable.ExecuteCommand();// 不存在插入
@:storageable.AsUpdateable.UpdateColumns(it => new
@:{
@foreach (var field in updateFields)
{
@: it.@(field),
}
@:}).ExecuteCommand();// 存在更新
@:
@:// 标记错误信息
@:markerErrorAction.Invoke(storageable, pageItems, rows);
@:});
@:});
@:
@:return stream;
@:}
@:}
}
}
@@ -0,0 +1,59 @@
import {useBaseApi} from '/@@/api/base';
// @(Model.BusName)接口服务
export const use@(Model.ClassName)Api = () => {
const baseApi = useBaseApi("@(Model.LowerClassName)");
return {
// 分页查询@(Model.BusName)
page: baseApi.page,
// 查看@(Model.BusName)详细
detail: baseApi.detail,
// 新增@(Model.BusName)
add: baseApi.add,
// 更新@(Model.BusName)
update: baseApi.update,
@if (Model.HasSetStatus) {
@:// 设置@(Model.BusName)状态
@:setStatus: baseApi.setStatus,
}
// 删除@(Model.BusName)
delete: baseApi.delete,
// 批量删除@(Model.BusName)
batchDelete: baseApi.batchDelete,
@if (Model.ImportFieldList.Count > 0) {
@:// 导出@(Model.BusName)数据
@:exportData: baseApi.exportData,
@:// 导入@(Model.BusName)数据
@:importData: baseApi.importData,
@:// 下载@(Model.BusName)数据导入模板
@:downloadTemplate: baseApi.downloadTemplate,
}
@if (Model.DropdownFieldList.Count > 0) {
@:// 获取下拉列表数据
@:getDropdownData: (fromPage: Boolean = false, cancel: boolean = false) => baseApi.dropdownData({ fromPage }, cancel),
}
@foreach (var column in Model.UploadFieldList) {
@:// 上传@(column.ColumnComment)
@:upload@(column.PropertyName): (params: any, cancel: boolean = false) => baseApi.uploadFile(params, 'upload@(column.PropertyName)', cancel),
}
}
}
// @(Model.BusName)实体
export interface @(Model.ClassName) {
@{
var typeMap = new Dictionary<string, string>() {
{ "bool", "boolean" },
{ "int", "number" },
{ "long", "number" },
{ "double", "number" },
{ "float", "number" },
{ "decimal", "number" },
{ "byte", "number" }
};
foreach (var column in Model.TableField) {
@:// @(column.ColumnComment)
@:@(column.LowerPropertyName)@(column.WhetherRequired == "Y" ? "?" : ""): @(Regex.IsMatch("@(column.DataType.Trim('?'))", ".*?Enum") ? "number" : typeMap.GetValueOrDefault(column.DataType.Trim('?').ToLower(), "string"));
}
}
}
@@ -0,0 +1,159 @@
import { h } from 'vue';
import { BasicColumn, FormSchema } from '/@@/components/Table';
@foreach (var column in Model.TableField){
if(column.EffectType == "Upload" || column.EffectType == "Upload_SingleFile"){
@:import { uploadFile } from '/@@/api/sys/admin';
}else if(column.EffectType == "ForeignKey"){
@:import { get@(column.FkEntityName)Dropdown } from '/@@/api/main/@(Model.ClassName)';
}else if(column.EffectType == "DictSelector"){
@:import { getDataList } from '/@@/api/sys/admin';
}else if(column.EffectType == "ApiTreeSelector"){
@:import { get@(column.FkEntityName)Tree } from '/@@/api/main/@(Model.ClassName)';
}else if(column.EffectType == "ConstSelector"){
@:import { codeToName, getSelector } from '/@@/utils/helper/constSelectorHelper';
}else if(column.EffectType == "Switch"){
@:import { Switch } from 'ant-design-vue';
}
}
export const columns: BasicColumn[] = [
@foreach (var column in Model.TableField){
if(column.WhetherTable == "Y"){
@:{
@:title: '@column.ColumnComment',
@:dataIndex: '@column.LowerPropertyName',
@:sorter: true,
if(column.EffectType == "Upload" || column.EffectType == "Upload_SingleFile"){
@:slots: { customRender: '@(column.LowerPropertyName)' },
}else if(column.EffectType == "ForeignKey"){
@:customRender: ({ record }) => {
@:return record.fk@(column.PropertyName).@(column.LowerFkDisplayColumnsList?.First());
@:},
}else if(column.EffectType == "Switch"){
@:customRender: ({ record }) => {
@:return h(@(column.EffectType), { checked: record.@(column.LowerPropertyName) });
@:},
}else if(column.EffectType == "ConstSelector"){
@:customRender: ({ record }) => {
@:return codeToName(record.@(column.LowerPropertyName), '@(column.DictTypeCode)');
@:},
}
@:},
}
}
];
export const searchFormSchema: FormSchema[] = [
@foreach (var column in Model.WhetherQueryList){
@:{
@:field: '@column.LowerPropertyName',
@:label: '@column.ColumnComment',
@:colProps: { span: 8 },
if(column.EffectType == "ForeignKey"){
@:component: 'ApiSelect',
@:componentProps: {
@:api: get@(column.FkEntityName)Dropdown,
@:labelField: 'label',
@:valueField: 'value',
@:},
}else if(column.EffectType == "DictSelector"){
@:component: 'ApiSelect',
@:componentProps: {
@:api: getDataList,
@:params: '@(column.DictTypeCode)',
@:fieldNames: {
@:label: 'label',
@:value: 'value',
@:},
@:},
}else if(column.EffectType == "ConstSelector"){
@:component: 'Select',
@:componentProps: {
@:options: getSelector('@(column.DictTypeCode)'),
@:fieldNames: {
@:label: 'name',
@:value: 'code',
@:},
@:},
}else if(column.EffectType == "ApiTreeSelector"){
@:component: '@(column.EffectType)',
@:componentProps: {
@:api: get@(column.FkEntityName)Tree,
@:},
}
else if(column.NetType?.TrimEnd('?') == "DateTime" && column.QueryType == "~"){
@:component: 'RangePicker',
@:componentProps: {
@: valueFormat:"YYYY-MM-DD"
@:},
} else {
@:component: 'Input',
}
@:},
}
];
export const formSchema: FormSchema[] = [
@foreach (var column in Model.TableField){
@:{
@:label: '@column.ColumnComment',
@:field: '@column.LowerPropertyName',
if(column.EffectType == "ForeignKey"){
@:component: 'ApiSelect',
@:componentProps: {
@:api: get@(column.FkEntityName)Dropdown,
@:labelField: 'label',
@:valueField: 'value',
@:},
}else if(column.EffectType == "DictSelector"){
@:component: 'ApiSelect',
@:componentProps: {
@:api: getDataList,
@:params: '@(column.DictTypeCode)',
@:fieldNames: {
@:label: 'label',
@:value: 'value',
@:},
@:},
}else if(column.EffectType == "ConstSelector"){
@:component: 'Select',
@:componentProps: {
@:options: getSelector('@(column.DictTypeCode)'),
@:fieldNames: {
@:label: 'name',
@:value: 'code',
@:},
@:},
}else if(column.EffectType == "ApiTreeSelector"){
@:component: '@(column.EffectType)',
@:componentProps: {
@:api: get@(column.FkEntityName)Tree,
@:},
}else if(column.EffectType == "Switch"){
@:component: '@(column.EffectType)',
@:componentProps: {
@:checkedChildren: '是',
@:unCheckedChildren: '否',
@:},
}else{
@:component: '@column.EffectType',
}
if(column.WhetherRequired == "Y"){
@:required: true,
}else{
@:required: false,
}
if(column.EffectType == "Upload" || column.EffectType == "Upload_SingleFile"){
@:componentProps: {
@:maxNumber: 1,
@:api: uploadFile,
@:},
}
if(column.LowerPropertyName == "id"){
@:show: false,
}
@:colProps: { span: 12 },
@:},
}
];
@@ -0,0 +1,71 @@
<template>
<BasicModal v-bind="$attrs" @@register="registerModal" :width="700" :title="getTitle" @@ok="handleSubmit">
<BasicForm @@register="registerForm" />
</BasicModal>
</template>
<script lang="ts">
import { defineComponent, ref, computed, unref } from 'vue';
import { BasicModal, useModalInner } from '/@@/components/Modal';
import { BasicForm, useForm } from '/@@/components/Form/index';
import { formSchema } from './data.data';
import { add@(Model.ClassName), update@(Model.ClassName) } from '/@@/api/main/@(Model.ClassName)';
export default defineComponent({
components: { BasicModal, BasicForm },
emits: ['success', 'register'],
setup(_, { emit }) {
const isUpdate = ref(true);
const [registerForm, { setFieldsValue, resetFields, validate }] = useForm({
labelWidth: 100,
schemas: formSchema,
showActionButtonGroup: false,
actionColOptions: {
span: 23,
},
});
const [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {
resetFields();
setModalProps({ confirmLoading: false });
isUpdate.value = !!data?.isUpdate;
if (unref(isUpdate)) {
setFieldsValue({
...data.record,
});
@foreach (var column in Model.TableField){
if(column.EffectType == "Upload" || column.EffectType == "Upload_SingleFile"){
@:if (data.record.@(column.LowerPropertyName)) {
@:setFieldsValue({
@:@(column.LowerPropertyName): [data.record.@(column.LowerPropertyName)Attachment],
@:});
@:}
}
}
}
});
const getTitle = computed(() => (!unref(isUpdate) ? '新增@(Model.BusName)' : '编辑@(Model.BusName)'));
async function handleSubmit() {
try {
var values = await validate();
@foreach (var column in Model.TableField){
if(column.EffectType == "Upload" || column.EffectType == "Upload_SingleFile"){
@:if (values.@(column.LowerPropertyName)) {
@:values = { ...values, @(column.LowerPropertyName): values.@(column.LowerPropertyName)[0].id };
@:}
}
}
setModalProps({ confirmLoading: true });
if (!unref(isUpdate)) {
await add@(Model.ClassName)(values);
} else {
await update@(Model.ClassName)(values);
}
closeModal();
emit('success');
} finally {
setModalProps({ confirmLoading: false });
}
}
return { registerModal, registerForm, getTitle, handleSubmit };
},
});
</script>
@@ -0,0 +1,198 @@
<script lang="ts" name="@(Model.LowerClassName)" setup>
import { ref, reactive, onMounted } from "vue";
import { ElMessage } from "element-plus";
import type { FormRules } from "element-plus";
@if(Model.TableField.Any(x => x.EffectType == "DatePicker")) {
@:import { formatDate } from '/@@/utils/formatTime';
}
@if(Model.UploadFieldList.Count > 0) {
@:import { Plus,Document } from "@@element-plus/icons-vue";
@:import { UploadRequestOptions } from "element-plus";
}
@if(Model.HasConstField) {
@:import { useUserInfo } from "/@@/stores/userInfo";
}
import { use@(Model.ClassName)Api } from '/@@/api/@(Model.PagePath)/@(Model.LowerClassName)';
@if(@Model.TableField.Any(x=>x.EffectType == "Editor")){
@:import Editor from '/@@/components/editor/index.vue';
}
//父级传递来的函数,用于回调
const emit = defineEmits(["reloadTable"]);
const @(Model.LowerClassName)Api = use@(Model.ClassName)Api();
const ruleFormRef = ref();
const state = reactive({
title: '',
loading: false,
showDialog: false,
ruleForm: {} as any,
stores: @(Model.HasConstField ? "useUserInfo()" : "{}"),
dropdownData: {} as any,
});
// 自行添加其他规则
const rules = ref<FormRules>({
@foreach (var column in Model.AddUpdateFieldList.Where(col => col.WhetherRequired == "Y")) {
var trigger = column.EffectType == "DatePicker" || column.EffectType == "DictSelector" || column.EffectType == "EnumSelector" || column.EffectType == "ApiTreeSelector" ? "change" : "blur";
@:@column.LowerPropertyName: [{required: true, message: '请选择@(column.ColumnComment)!', trigger: '@trigger',},],
}
});
// 页面加载时
onMounted(async () => {
@if (Model.DropdownFieldList.Count > 0) {
@:const data = await @(Model.LowerClassName)Api.getDropdownData(false).then(res => res.data.result) ?? {};
@foreach (var column in Model.DropdownFieldList) {
@:state.dropdownData.@(column.LowerPropertyName) = data.@(column.LowerPropertyName) ?? [];
}
}
});
// 打开弹窗
const openDialog = async (row: any, title: string) => {
state.title = title;
row = row ?? { @(Model.GetAddDefaultValue()) };
state.ruleForm = row.id ? await @(Model.LowerClassName)Api.detail(row.id).then(res => res.data.result) : JSON.parse(JSON.stringify(row));
state.showDialog = true;
};
// 关闭弹窗
const closeDialog = () => {
emit("reloadTable");
state.showDialog = false;
};
// 提交
const submit = async () => {
ruleFormRef.value.validate(async (isValid: boolean, fields?: any) => {
if (isValid) {
let values = state.ruleForm;
await @(Model.LowerClassName)Api[state.ruleForm.@(Model.PrimaryKeyFieldList.First().LowerPropertyName) ? 'update' : 'add'](values);
closeDialog();
} else {
ElMessage({
message: `表单有${Object.keys(fields).length}处验证失败,请修改后再提交`,
type: "error",
});
}
});
};
@foreach (var column in Model.UploadFieldList) {
@:const upload@(column.PropertyName)Handle = async (options: UploadRequestOptions) => {
@:const res = await @(Model.LowerClassName)Api.upload@(column.PropertyName)(options);
@:state.ruleForm.@(column.LowerPropertyName) = res.data.result?.url;
@:};
@:
}
//将属性或者函数暴露给父组件
defineExpose({ openDialog });
</script>
<template>
<div class="@(Model.LowerClassName)-container">
<el-dialog v-model="state.showDialog" :width="800" draggable :close-on-click-modal="false">
<template #header>
<div style="color: #fff">
<span>{{ state.title }}</span>
</div>
</template>
<el-form :model="state.ruleForm" ref="ruleFormRef" label-width="auto" :rules="rules">
<el-row :gutter="35">
@foreach (var column in Model.PrimaryKeyFieldList) {
@:<el-form-item v-show="false">
@:<el-input v-model="state.ruleForm.@(column.LowerPropertyName)" />
@:</el-form-item>
}
@foreach (var column in Model.AddUpdateFieldList) {
var showStatus = Model.IsStatus(column) ? $"v-if=\"state.ruleForm.{Model.PrimaryKeyFieldList.First().LowerPropertyName}\" " : "";
if (column.EffectType == "InputTextArea"||column.EffectType == "Editor"){
@:<el-col :xs="24" class="mb20" @showStatus>
} else{
@:<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb20" @showStatus>
}
@:<el-form-item label="@column.ColumnComment" prop="@(column.LowerPropertyName)">
if (column.IsSelectorEffectType) {
if (column.EffectType == "ApiTreeSelector") {
@:<el-cascader
@::options="state.dropdownData.@(column.LowerPropertyName)"
@::props="{ checkStrictly: true, emitPath: false }"
@:v-model="state.ruleForm.@(column.LowerPropertyName)"
@:placeholder="请选择@(column.ColumnComment)"
@:clearable
@:filterable
@:class="w100">
@:<template #default="{ node, data }">
@:<span>{{ data.label }}</span>
@:<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
@:</template>
@:</el-cascader>
} else if (column.EffectType == "DictSelector" || column.EffectType == "EnumSelector") {
@:<g-sys-dict v-model="state.ruleForm.@(column.LowerPropertyName)" code="@(column.DictTypeCode)" render-as="select" placeholder="请选择@(column.ColumnComment)" clearable filterable />
} else {
@:<el-select clearable filterable v-model="state.ruleForm.@(column.LowerPropertyName)" placeholder="请选择@(column.ColumnComment)">
if (column.EffectType == "ForeignKey") {
@:<el-option v-for="(item,index) in state.dropdownData.@(column.LowerPropertyName)" :key="index" :value="item.value" :label="item.label" />
} else if (column.EffectType == "ConstSelector") {
@:<el-option v-for="(item, index) in state.stores.getConstDataByTypeCode('@column.DictTypeCode')" :key="index" :label="item.name" :value="item.code" />
}
@:</el-select>
}
} else if (column.EffectType == "Upload") {
@:<el-upload
@:list-type="picture-card"
@::show-file-list="false"
@::http-request="upload@(column.PropertyName)Handle">
@:<img
@:v-if="state.ruleForm.@(column.LowerPropertyName)"
@::src="state.ruleForm.@(column.LowerPropertyName)"
@:@@click="state.ruleForm.@(column.LowerPropertyName)=''"
@:style="width: 100%; height: 100%; object-fit: contain"/>
@:<el-icon v-else><Plus /></el-icon>
@:</el-upload>
}else if (column.EffectType == "Upload_SingleFile") {
@:<el-upload
@::show-file-list="false"
@::http-request="upload@(column.PropertyName)Handle">
@:<template #trigger="">
@:<el-button class="mr10" type="primary" icon="ele-MostlyCloudy">选择文件</el-button>
@:</template>
@:</el-upload>
@:<a
@::href="state.ruleForm.@(column.LowerPropertyName)" target="_blank"
@:v-if="state.ruleForm.@(column.LowerPropertyName)">
@:<el-icon class="mr5"><Document /></el-icon>
@:<span>{{ state.ruleForm.@(column.LowerPropertyName).split('/').pop() }}</span>
@:</a>
}
else if (column.EffectType == "InputNumber") {
@:<el-input-number v-model="state.ruleForm.@(column.LowerPropertyName)" placeholder="请输入@(column.ColumnComment)" clearable />
} else if (column.EffectType == "Switch") {
@:<el-switch v-model="state.ruleForm.@(column.LowerPropertyName)" />
} else if (column.EffectType == "DatePicker") {
@:<el-date-picker v-model="state.ruleForm.@(column.LowerPropertyName)" type="date" placeholder="@(column.ColumnComment)" />
}else if(@column.EffectType == "Editor"){
@:<Editor v-model:get-html="state.ruleForm.@(@column.LowerPropertyName)" />
} else {
var inputType = column.EffectType == "InputTextArea" ? "type=\"textarea\" " : "";
var maxlength = column.ColumnLength > 0 ? $"maxlength=\"{column.ColumnLength}\" " : "";
@:<el-input v-model="state.ruleForm.@(column.LowerPropertyName)" placeholder="请输入@(column.ColumnComment)" @(maxlength)@(inputType)show-word-limit clearable />
}
@:</el-form-item>
@:</el-col>
}
</el-row>
</el-form>
<template #footer>
<span class="dialog-footer">
<el-button @@click="() => state.showDialog = false">取 消</el-button>
<el-button @@click="submit" type="primary" v-reclick="1000">确 定</el-button>
</span>
</template>
</el-dialog>
</div>
</template>
<style lang="scss" scoped>
:deep(.el-select), :deep(.el-input-number) {
width: 100%;
}
</style>
@@ -0,0 +1,326 @@
<script lang="ts" setup name="@(Model.LowerClassName)">
import { ref, reactive, onMounted } from "vue";
import { auth } from '/@@/utils/authFunction';
import { ElMessageBox, ElMessage } from "element-plus";
import { downloadStreamFile } from "/@@/utils/download";
@if(Model.PrintType == "custom") {
@:// 推荐设置操作 width 为 200
@:import { hiprint } from 'vue-plugin-hiprint';
@:import { getAPI } from '/@@/utils/axios-utils';
@:import { SysPrintApi } from '/@@/api-services/api';
@:import { SysPrint } from '/@@/api-services/models';
@:import { formatDate } from '/@@/utils/formatTime';
}
import { use@(Model.ClassName)Api } from '/@@/api/@(Model.PagePath)/@(Model.LowerClassName)';
@if(Model.HasConstField) {
@:import { useUserInfo } from "/@@/stores/userInfo";
}
import editDialog from '/@@/views/@(Model.PagePath)/@(Model.LowerClassName)/component/editDialog.vue'
import printDialog from '/@@/views/system/print/component/hiprint/preview.vue'
import ModifyRecord from '/@@/components/table/modifyRecord.vue';
@if(Model.ImportFieldList.Count > 0) {
@:import ImportData from "/@@/components/table/importData.vue";
}
const @(Model.LowerClassName)Api = use@(Model.ClassName)Api();
const printDialogRef = ref();
const editDialogRef = ref();
@if (Model.ImportFieldList.Count > 0) {
@:const importDataRef = ref();
}
const state = reactive({
exportLoading: false,
tableLoading: false,
stores: @(Model.HasConstField ? "useUserInfo()" : "{}"),
showAdvanceQueryUI: @(Model.HasLikeQuery ? "false" : "true"),
dropdownData: {} as any,
selectData: [] as any[],
tableQueryParams: {} as any,
tableParams: {
page: 1,
pageSize: 20,
total: 0,
field: 'createTime', // 默认的排序字段
order: 'descending', // 排序方向
descStr: 'descending', // 降序排序的关键字符
},
tableData: [],
});
// 页面加载时
onMounted(async () => {
@if (Model.DropdownFieldList.Count > 0) {
@:const data = await @(Model.LowerClassName)Api.getDropdownData(true).then(res => res.data.result) ?? {};
@foreach (var column in Model.DropdownFieldList) {
@:state.dropdownData.@(column.LowerPropertyName) = data.@(column.LowerPropertyName);
}
}
});
// 查询操作
const handleQuery = async (params: any = {}) => {
state.tableLoading = true;
state.tableParams = Object.assign(state.tableParams, params);
const result = await @(Model.LowerClassName)Api.page(Object.assign(state.tableQueryParams, state.tableParams)).then(res => res.data.result);
state.tableParams.total = result?.total;
state.tableData = result?.items ?? [];
state.tableLoading = false;
};
// 列排序
const sortChange = async (column: any) => {
state.tableParams.field = column.prop;
state.tableParams.order = column.order;
await handleQuery();
};
// 删除
const del@(Model.ClassName) = (row: any) => {
ElMessageBox.confirm(`确定要删除吗?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
await @(Model.LowerClassName)Api.delete({ @(Model.PrimaryKeysFormat(", ", "{0}: row.{0}", true)) });
handleQuery();
ElMessage.success("删除成功");
}).catch(() => {});
};
// 批量删除
const batchDel@(Model.ClassName) = () => {
ElMessageBox.confirm(`确定要删除${state.selectData.length}条记录吗?`, "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}).then(async () => {
await @(Model.LowerClassName)Api.batchDelete(state.selectData.map(u => ({ @(Model.PrimaryKeysFormat(", ", "{0}: u.{0}", true)) }) )).then(res => {
ElMessage.success(`成功批量删除${res.data.result}条记录`);
handleQuery();
});
}).catch(() => {});
};
@if(Model.PrintType == "custom") {
@:
@:// 打开打印页面
@:const openPrint@(Model.ClassName) = async (row: any) => {
@:var res = await getAPI(SysPrintApi).apiSysPrintPrintNameGet('@Model.PrintName');
@:var printTemplate = res.data.result as SysPrint;
@:var template = JSON.parse(printTemplate.template);
@:row['printDate'] = formatDate(new Date(), 'YYYY-mm-dd HH:MM:SS')
@:printDialogRef.value.showDialog(new hiprint.PrintTemplate({template: template}), row, template.panels[0].width);
@:}
}
@if (Model.HasSetStatus) {
@:
@:// 设置状态
@:const change@(Model.ClassName)Status = async (row: any) => {
@:await @(Model.LowerClassName)Api.setStatus({ @(Model.PrimaryKeysFormat(", ", "{0}: row.{0}", true)), status: row.status }).then(() => ElMessage.success('状态设置成功'));
@:};
}
@if (Model.ImportFieldList.Count > 0) {
@:
@:// 导出数据
@:const export@(Model.ClassName)Command = async (command: string) => {
@:try {
@:state.exportLoading = true;
@:if (command === 'select') {
@:const params = Object.assign({}, state.tableQueryParams, state.tableParams, { selectKeyList: state.selectData.map(u => u.@(Model.PrimaryKeyFieldList.First().LowerPropertyName)) });
@:await @(Model.LowerClassName)Api.exportData(params).then(res => downloadStreamFile(res));
@:} else if (command === 'current') {
@:const params = Object.assign({}, state.tableQueryParams, state.tableParams);
@:await @(Model.LowerClassName)Api.exportData(params).then(res => downloadStreamFile(res));
@:} else if (command === 'all') {
@:const params = Object.assign({}, state.tableQueryParams, state.tableParams, { page: 1, pageSize: 99999999 });
@:await @(Model.LowerClassName)Api.exportData(params).then(res => downloadStreamFile(res));
@:}
@:} finally {
@:state.exportLoading = false;
@:}
@:}
}
handleQuery();
</script>
<template>
<div class="@(Model.LowerClassName)-container" v-loading="state.exportLoading">
<el-card shadow="hover" :body-style="{ paddingBottom: '0' }">
<el-form :model="state.tableQueryParams" ref="queryForm" labelWidth="90">
<el-row>
@if(Model.QueryWhetherList.Count > 0) {
if(Model.HasLikeQuery) {
@:<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="4" class="mb10">
@:<el-form-item label="关键字">
@:<el-input v-model="state.tableQueryParams.keyword" clearable placeholder="请输入模糊查询关键字"/>
@:</el-form-item>
@:</el-col>
}
foreach (var column in Model.QueryWhetherList) {
@:<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="4" class="mb10" v-if="state.showAdvanceQueryUI">
@:<el-form-item label="@column.ColumnComment">
if(column.EffectType == "Input" || column.EffectType == "InputTextArea"){
@:<el-input v-model="state.tableQueryParams.@(column.LowerPropertyName)" clearable placeholder="请输入@(column.ColumnComment)"/>
}else if(column.EffectType == "InputTextArea"){
@:<el-input-number v-model="state.tableQueryParams.@(column.LowerPropertyName)" clearable placeholder="请输入@(column.ColumnComment)"/>
}else if(column.EffectType == "InputNumber"){
@:<el-input-number v-model="state.tableQueryParams.@(column.LowerPropertyName)" clearable placeholder="请输入@(column.ColumnComment)"/>
}else if(column.IsSelectorEffectType || column.EffectType == "ForeignKey") {
if (column.EffectType == "DictSelector" || column.EffectType == "EnumSelector") {
@:<g-sys-dict v-model="state.tableQueryParams.@(column.LowerPropertyName)" code="@(column.DictTypeCode)" render-as="select" placeholder="请选择@(column.ColumnComment)" clearable filterable />
} else {
@:<el-select clearable filterable v-model="state.tableQueryParams.@(column.LowerPropertyName)" placeholder="请选择@(column.ColumnComment)">
if (column.EffectType == "ForeignKey") {
@:<el-option v-for="(item,index) in state.dropdownData.@(column.LowerPropertyName) ?? []" :key="index" :value="item.value" :label="item.label" />
} else if (column.EffectType == "ConstSelector") {
@:<el-option v-for="(item, index) in state.stores.getConstDataByTypeCode('@column.DictTypeCode')" :key="index" :label="item.name" :value="item.code" />
}
@:</el-select>
}
}else if(column.EffectType == "ApiTreeSelector"){
@:<el-cascader
@::options="state.dropdownData.@(column.LowerPropertyName) ?? []"
@:@:props="{ checkStrictly: true, emitPath: false }"
@:placeholder="请选择@(column.ColumnComment)"
@:clearable
@:filterable
@:class="w100"
@:v-model="state.tableQueryParams.@(column.LowerPropertyName)"
@:>
@:<template #default="{ node, data }">
@:<span>{{ data.label }}</span>
@:<span v-if="!node.isLeaf"> ({{ data.children.length }}) </span>
@:</template>
@:</el-cascader>
} else if (column.EffectType == "DatePicker"){
if (column.QueryType == "~") {
@:<el-date-picker type="daterange" v-model="state.tableQueryParams.@(column.LowerPropertyName)Range" value-format="YYYY-MM-DD HH:mm:ss" start-placeholder="开始日期" end-placeholder="结束日期" :default-time="[new Date('1 00:00:00'), new Date('1 23:59:59')]" />
} else {
@:<el-date-picker placeholder="请选择@(column.ColumnComment)" value-format="YYYY/MM/DD" v-model="state.tableQueryParams.@(column.LowerPropertyName)" />
}
} else if (column.EffectType == "Switch"){
@:<el-select clearable filterable v-model="state.tableQueryParams.@(column.LowerPropertyName)" placeholder="请选择@(column.ColumnComment)">
@:<el-option value="true" label="是" />
@:<el-option value="false" label="否" />
@:</el-select>
} else {
@:<el-input v-model="state.tableQueryParams.@(column.LowerPropertyName)" clearable placeholder="请输入@(column.ColumnComment)"/>
}
@:</el-form-item>
@:</el-col>
}
}
<el-col :xs="24" :sm="12" :md="12" :lg="8" :xl="4" class="mb10">
<el-form-item @(Model.QueryWhetherList.Count > 0 ? "" : "label-width=\"0px\"")>
<el-button-group style="display: flex; align-items: center;">
<el-button type="primary" icon="ele-Search" @@click="handleQuery" v-auth="'@(Model.LowerClassName):page'" v-reclick="1000"> @(Model.QueryWhetherList.Count > 0 ? "查询" : "刷新") </el-button>
@if (Model.QueryWhetherList.Count > 0) {
@:<el-button icon="ele-Refresh" @@click="() => state.tableQueryParams = {}"> 重置 </el-button>
@if (Model.HasLikeQuery) {
@:<el-button icon="ele-ZoomIn" @@click="() => state.showAdvanceQueryUI = true" v-if="!state.showAdvanceQueryUI" style="margin-left:5px;"> 高级查询 </el-button>
@:<el-button icon="ele-ZoomOut" @@click="() => state.showAdvanceQueryUI = false" v-if="state.showAdvanceQueryUI" style="margin-left:5px;"> 隐藏 </el-button>
}
}
<el-button type="danger" style="margin-left:5px;" icon="ele-Delete" @@click="batchDel@(Model.ClassName)" :disabled="state.selectData.length == 0" v-auth="'@(Model.LowerClassName):batchDelete'"> 删除 </el-button>
<el-button type="primary" style="margin-left:5px;" icon="ele-Plus" @@click="editDialogRef.openDialog(null, '新增@(Model.BusName)')" v-auth="'@(Model.LowerClassName):add'"> 新增 </el-button>
@if (Model.ImportFieldList.Count > 0) {
@:<el-dropdown :show-timeout="70" :hide-timeout="50" @@command="export@(Model.ClassName)Command">
@:<el-button type="primary" style="margin-left:5px;" icon="ele-FolderOpened" v-reclick="20000" v-auth="'@(Model.LowerClassName):export'"> 导出 </el-button>
@:<template #dropdown>
@:<el-dropdown-menu>
@:<el-dropdown-item command="select" :disabled="state.selectData.length == 0">导出选中</el-dropdown-item>
@:<el-dropdown-item command="current">导出本页</el-dropdown-item>
@:<el-dropdown-item command="all">导出全部</el-dropdown-item>
@:</el-dropdown-menu>
@:</template>
@:</el-dropdown>
@:<el-button type="warning" style="margin-left:5px;" icon="ele-MostlyCloudy" @@click="importDataRef.openDialog()" v-auth="'@(Model.LowerClassName):import'"> 导入 </el-button>
}
</el-button-group>
</el-form-item>
</el-col>
</el-row>
@* 操作区另起一行
@:<el-row>
@:<el-col>
@:<el-button-group style="margin-left:20px;margin-bottom:5px;">
@:<el-button type="primary" icon="ele-Plus" @@click="editDialogRef.openDialog(null, '新增@(Model.BusName)')" v-auth="'@(Model.LowerClassName):add'"> 新增 </el-button>
</el-button-group>
@:</el-col>
@:</el-row>
*@
</el-form>
</el-card>
<el-card class="full-table" shadow="hover" style="margin-top: 5px">
<el-table :data="state.tableData" @@selection-change="(val: any[]) => { state.selectData = val; }" style="width: 100%" v-loading="state.tableLoading" tooltip-effect="light" row-key="@Model.PrimaryKeyFieldList.First().LowerPropertyName" @@sort-change="sortChange" border>
<el-table-column type="selection" width="40" align="center" v-if="auth('@(Model.LowerClassName):batchDelete') || auth('@(Model.LowerClassName):export')" />
<el-table-column type="index" label="序号" width="55" align="center"/>
@foreach (var column in Model.TableField.Where(u => u.WhetherTable == "Y")){
if(column.EffectType == "DictSelector" || column.EffectType == "EnumSelector" || column.EffectType == "Upload" || @column.EffectType == "Switch") {
@:<el-table-column @(Model.GetElTableColumnCustomProperty(column)) show-overflow-tooltip>
@:<template #default="scope">
if (column.EffectType == "Upload") {
@:<el-image
@:v-if="scope.row.@column.LowerPropertyName"
@:style="width: 60px; height: 60px"
@::src="scope.row.@column.LowerPropertyName"
@::lazy="true"
@::hide-on-click-modal="true"
@::preview-src-list="[scope.row.@column.LowerPropertyName]"
@::initial-index="0"
@:fit="scale-down"
@:preview-teleported />
} else if (column.EffectType == "Switch") {
@:<el-tag v-if="scope.row.@(column.LowerPropertyName)"> 是 </el-tag>
@:<el-tag type="danger" v-else> 否 </el-tag>
} else if (Model.IsStatus(column)) {
@:<el-switch v-model="scope.row.@column.LowerPropertyName" :active-value="1" :inactive-value="2" size="small" @@change="change@(Model.ClassName)Status(scope.row)" />
} else {
@:<g-sys-dict v-model="scope.row.@column.LowerPropertyName" code="@(column.DictTypeCode)" />
}
@:</template>
@:</el-table-column>
} else if (column.EffectType == "ConstSelector" || column.EffectType == "ForeignKey" || column.EffectType == "ApiTreeSelector") {
var formatter = column.EffectType == "ConstSelector" ? $"state.stores.getConstItemNameByType('{column.DictTypeCode}', row.{column.LowerPropertyName})" : $"row.{column.LowerExtendedPropertyName}";
@:<el-table-column @(Model.GetElTableColumnCustomProperty(column)) :formatter="(row: any) => @(formatter)" show-overflow-tooltip />
} else {
@:<el-table-column @(Model.GetElTableColumnCustomProperty(column)) show-overflow-tooltip />
}
}
<el-table-column label="修改记录" width="100" align="center" show-overflow-tooltip>
<template #default="scope">
<ModifyRecord :data="scope.row" />
</template>
</el-table-column>
<el-table-column label="操作" width="@(Model.PrintType == "custom" ? "200" : "140")" align="center" fixed="right" show-overflow-tooltip v-if="auth('@(Model.LowerClassName):update') || auth('@(Model.LowerClassName):delete')">
<template #default="scope">
@if (Model.PrintType == "custom") {
@:<el-button icon="ele-Printer" size="small" text type="primary" @@click="openPrint@(Model.ClassName)(scope.row)" v-auth="'@(Model.LowerClassName):print'"> 打印 </el-button>
}
<el-button icon="ele-Edit" size="small" text type="primary" @@click="editDialogRef.openDialog(scope.row, '编辑@(Model.BusName)')" v-auth="'@(Model.LowerClassName):update'"> 编辑 </el-button>
<el-button icon="ele-Delete" size="small" text type="primary" @@click="del@(Model.ClassName)(scope.row)" v-auth="'@(Model.LowerClassName):delete'"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<el-pagination
v-model:currentPage="state.tableParams.page"
v-model:page-size="state.tableParams.pageSize"
@@size-change="(val: any) => handleQuery({ pageSize: val })"
@@current-change="(val: any) => handleQuery({ page: val })"
layout="total, sizes, prev, pager, next, jumper"
:page-sizes="[10, 20, 50, 100, 200, 500]"
:total="state.tableParams.total"
size="small"
background />
@if (Model.ImportFieldList.Count > 0) {
@:<ImportData ref="importDataRef" :import="@(Model.LowerClassName)Api.importData" :download="@(Model.LowerClassName)Api.downloadTemplate" v-auth="'@(Model.LowerClassName):import'" @@refresh="handleQuery"/>
}
<printDialog ref="printDialogRef" :title="'打印@(Model.BusName)'" @@reloadTable="handleQuery" />
<editDialog ref="editDialogRef" @@reloadTable="handleQuery" />
</el-card>
</div>
</template>
<style scoped>
:deep(.el-input), :deep(.el-select), :deep(.el-input-number) {
width: 100%;
}
</style>