Compare commits

...

15 Commits
V0.1 ... master

18 changed files with 376 additions and 204 deletions

3
.gitignore vendored
View File

@ -4,6 +4,9 @@
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
#取消报表模板源代码管理
ReportService/wwwroot/report/
# User-specific files
*.rsuser
*.suo

View File

@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "3.1.5",
"commands": [
"dotnet-ef"
]
}
}
}

View File

@ -3,10 +3,10 @@ using System.Collections.Generic;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.Logging;
using ReportService.Database;
using ReportService.Models;
using ReportService.Models.ReportApi;
namespace ReportService.Controllers.api
{
@ -15,8 +15,8 @@ namespace ReportService.Controllers.api
public class ReportApiController:Controller
{
public IWebHostEnvironment Env { get; private set; }
public RSDbContext Db { get; set; }
public ILogger Logger { get; set; }
public DbContextFactory DbFactory { get; set; }
/// <summary>
/// 报表存放位置
@ -27,10 +27,10 @@ namespace ReportService.Controllers.api
}
}
public ReportApiController(ILogger<ReportApiController> logger,IWebHostEnvironment env,RSDbContext db) {
public ReportApiController(ILogger<ReportApiController> logger,IWebHostEnvironment env,DbContextFactory dbFactory) {
this.Logger = logger;
this.Env = env;
this.Db = db;
this.DbFactory = dbFactory;
}
/// <summary>
@ -59,6 +59,9 @@ namespace ReportService.Controllers.api
var cPath = Path.Combine(this.ReportPath,rootPath ?? "");
var dirs = Directory.GetDirectories(cPath);
foreach(var d in dirs) {
if(d.Trim().ToLower().EndsWith("\\hide")) {
continue;
}
var sd = new ReportItem {
Name = d.Substring(d.LastIndexOf('\\') + 1),
Path = rootPath ?? "",
@ -117,8 +120,19 @@ namespace ReportService.Controllers.api
/// <param name="sql">要执行的sql语句</param>
/// <returns>json对象</returns>
public object GetResult(string sql) {
var result = this.Db.SqlJsonQuery(sql);
this.Logger.LogInformation($"GetResult:\n{sql}\n{result}");
return GetData(new GetDataData {
DbName = "ReportService",
Sql = sql,
});
}
/// <summary>
/// 从数据请求数据。
/// </summary>
/// <param name="data">请求数据模型</param>
/// <returns>返回json格式数据</returns>
public object GetData([FromBody]GetDataData data) {
var result = this.DbFactory.RunRawSql(data.DbName,data.Sql);
this.Logger.LogInformation($"GetData:\n{data.Sql}\n{result}");
return Content(result,"application/json; charset=utf-8");
}

View File

@ -0,0 +1,61 @@
using System;
using System.Collections.Generic;
using Falcon.StoredProcedureRunner;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
namespace ReportService.Database
{
/// <summary>
/// 数据库上下文工厂
/// </summary>
public class DbContextFactory
{
/// <summary>
/// 数据库上下文集合
/// </summary>
public Dictionary<string,DbContextOptions> List { get; set; } = new();
/// <summary>
/// 数据库执行器
/// </summary>
public IRunner DbRunner { get; set; }
/// <summary>
/// 通过提供配置节点实例化数据库上下文工厂
/// </summary>
/// <param name="config">配置节点</param>
/// <param name="runner">数据库语句执行器</param>
public DbContextFactory(IConfigurationSection config,IRunner runner) {
foreach(var item in config.GetChildren()) {
DbContextOptionsBuilder builder = new();
builder.UseSqlServer(item.Value);
this.List.Add(item.Key,builder.Options);
}
this.DbRunner = runner;
}
/// <summary>
/// 获取数据库上下文
/// </summary>
/// <param name="name">节点定义的数据库名称</param>
/// <returns>数据上下文</returns>
public DbContext GetContext(string name) {
if(List.TryGetValue(name,out var option)) {
return new DbContext(option);
}
throw new Exception("使用的数据库名称错误!");
}
/// <summary>
/// 在指定数据库上执行原始sql语句并返回json格式数据。注意防止sql注入攻击
/// </summary>
/// <param name="dbName">数据库名称</param>
/// <param name="sql">要执行的sql语句</param>
/// <returns>json格式的数据库返回数据</returns>
public string RunRawSql(string dbName,string sql) {
if(this.DbRunner == null) {
throw new Exception("执行器注册错误没有实例化IRunner执行器");
}
return this.DbRunner.RunRaw(GetContext(dbName),sql);
}
}
}

View File

@ -0,0 +1,27 @@
using Falcon.StoredProcedureRunner;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace ReportService.Database
{
/// <summary>
/// 数据上下文工厂服务集合扩展
/// </summary>
public static class DbContextFactoryServiceCollectionEx
{
/// <summary>
/// 注册数据库工厂
/// </summary>
/// <param name="services">服务集合</param>
/// <param name="sectionKey">配置文件节点</param>
/// <returns>服务集合</returns>
public static IServiceCollection AddDbContextFactory(this IServiceCollection services,string sectionKey) {
services.AddSingleton(sp => {
var section = sp.GetService<IConfiguration>().GetSection(sectionKey);
var runner = sp.GetService<IRunner>();
return new DbContextFactory(section,runner);
});
return services;
}
}
}

View File

@ -1,63 +0,0 @@
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace ReportService.Database
{
/// <summary>
/// 报表服务数据库
/// </summary>
public partial class RSDbContext:DbContext
{
public RSDbContext(DbContextOptions options) : base(options) {
}
}
public static class RSDbContextExend
{
/// <summary>
/// 执行sql查询返回json格式数据集
/// </summary>
/// <param name="db">执行sql语句的数据库上下文</param>
/// <param name="sql">要执行的sql语句</param>
/// <returns>Json格式sql语句执行结果</returns>
public static string SqlJsonQuery(this DbContext db,string sql) => db.Database.SqlJsonQuery(sql);
/// <summary>
/// 执行sql查询返回json格式数据集
/// </summary>
/// <param name="db">执行sql语句的数据库上下文</param>
/// <param name="sql">要执行的sql语句</param>
/// <returns>Json格式sql语句执行结果</returns>
public static string SqlJsonQuery(this DatabaseFacade db,string sql) {
var connection = db.GetDbConnection();
using(var cmd = connection.CreateCommand()) {
cmd.CommandText = sql;
cmd.CommandType = System.Data.CommandType.Text;
connection.Open();
var dr = cmd.ExecuteReader();
var result = new StringBuilder();
if(!dr.CanGetColumnSchema())
return "";
while(dr.Read()) {
var item = new StringBuilder();
var columnSchema = dr.GetColumnSchema();
for(var i = 0;i < columnSchema.Count;i++) {
var name = dr.GetName(i);
var value = dr.IsDBNull(i) ? null : dr.GetValue(i);
item.Append($"\"{name}\":\"{value}\",");
}
result.Append($"{{{item.ToString().TrimEnd(',')}}},");
}
connection.Close();
return "[" + result.ToString().TrimEnd(',') + "]";
}
}
}
}

View File

@ -0,0 +1,17 @@
namespace ReportService.Models.ReportApi
{
/// <summary>
/// 请求数据模型
/// </summary>
public class GetDataData
{
/// <summary>
/// 要请求数据的数据库
/// </summary>
public string DbName { get; set; }
/// <summary>
/// 要执行的查询语句
/// </summary>
public string Sql { get; set; }
}
}

View File

@ -8,7 +8,7 @@
}
},
"profiles": {
"IIS Express": {
"FalconIIS": {
"commandName": "IISExpress",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"

View File

@ -1,24 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web" ToolsVersion="Current">
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<TargetFramework>net5</TargetFramework>
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
<FileUpgradeFlags>40</FileUpgradeFlags>
<UpgradeBackupLocation>D:\Applications\ReportService\Backup\</UpgradeBackupLocation>
<OldToolsVersion>2.0</OldToolsVersion>
<Version>1.0.0</Version>
<UserSecretsId>5be4fd95-6f72-4658-a0a9-e46921749643</UserSecretsId>
</PropertyGroup>
<ItemGroup>
<Content Remove="wwwroot\report\上传记录.html" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
<None Include="wwwroot\report\模板\上传记录.html" />
<None Include="wwwroot\report\报表1.html" />
<None Include="wwwroot\report\报表1.rpt.html" />
<None Include="wwwroot\report\报表2.html" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="5.0.6" />
<PackageReference Include="Microsoft.Extensions.Caching.Redis" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="3.1.4" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.4" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.9.1" />
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="5.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="5.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="5.0.6" />
<PackageReference Include="NLog.Web.AspNetCore" Version="4.12.0" />
<PackageReference Include="Falcon.StoredProcedureRunner" Version="1.4.0" />
</ItemGroup>
</Project>
<ItemGroup>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@ -1,10 +1,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Falcon.StoredProcedureRunner;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
@ -24,10 +21,11 @@ namespace ReportService
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) {
services.AddDbContext<RSDbContext>(b => {
var connStr = this.Configuration.GetValue<string>("Database:ReportService");
b.UseSqlServer(connStr);
});
//注册数据上下文工厂
services.AddDbContextFactory("Database");
//引入存储过程执行器
services.AddFalconSPRunner();
services.AddControllersWithViews();
}

View File

@ -1,12 +1,5 @@
<style type="text/css">
#rpPage {
border: 1px solid #000;
padding: 3px;
}
</style>
<div id="reportMain" class="row">
<div id="accd" class="col-3">
<div id="reportMain" class="row">
<div id="accd" class="col-2">
<div class="card" v-for="g in reportList.s" v-if="g.t==1">
<div class="card-header">
<a class="card-link" data-toggle="collapse" :href="'#id'+g.n">{{g.n}}</a>
@ -22,7 +15,7 @@
</div>
</div>
</div>
<div class="col" v-show="showPage">
<div class="col-10" v-show="showPage">
<ol class="breadcrumb">
<li class="breadcrumb-item">{{bread.p}}</li>
<li class="breadcrumb-item">{{bread.n}}</li>

View File

@ -18,7 +18,9 @@
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light border-bottom box-shadow mb-3 headbar">
<div class="container">
<img alt="" src="~/images/logo75.png" class="logo" />
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">自定义报表服务</a>
<h1>
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">自定义报表服务</a>
</h1>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>

View File

@ -8,7 +8,8 @@
},
"AllowedHosts": "*",
"Database": {
"ReportService": "Server=.\\SQLSERVER2008R2;Database=ReportService;User ID=sa;Password=111"
"ReportService": "Server=.\\SQLSERVER2008R2;Database=EASY_HEALTHRECORDS;User ID=sa;Password=111",
"test": "Server=.\\SQLSERVER2008R2;Database=EASY_HEALTHRECORDS;User ID=sa;Password=111"
},
"urls": "http://*:9000;https://*:9001"
}

View File

@ -106,3 +106,20 @@ body {
.reportTable td {
}
/*bootstrap样式重写*/
.container {
max-width: 100%;
}
.table-LayoutFix{
table-layout:fixed;
}
.nowrap{
white-space:nowrap;
}
.fix{
table-layout:fixed;
}

View File

@ -6,6 +6,7 @@ var myjs = {
fileContentUrl: "/api/ReportApi/GetHtml",
reportUrl: "/api/ReportApi/GetPrint",
getresult: "/api/ReportApi/GetResult",
getData: "/api/ReportApi/GetData",
GetServerTime: "/api/ReportApi/GetServerTime",
},
//定义从对象获取请求参数的方法
@ -20,7 +21,7 @@ var myjs = {
createSql(e) {
var pn = "";
var paras = "";
var ec = $(e).find("input");
var ec = $(e).find("input,select,textarea");
for (var i = 0; i < ec.length; i++) {
var e = $(ec[i]);
var n = e.attr("name");
@ -56,6 +57,27 @@ var myjs = {
.then(sc)
.catch(ec);
},
//从数据库获取数据。
getData(dbName, sql, sc, ec) {
if (!ec) {
ec = function (e) {
console.log(e);
alert(e);
};
}
if (!sc) {
sc = function (d) { console.log(d); };
}
var url = this.urls.getData;
var rd = { DbName: dbName, Sql: sql, };
fetch(url, {
method: 'POST',
body: JSON.stringify(rd),
headers: new Headers({
'Content-Type': 'application/json'
})
}).then(res => res.json()).catch(ec).then(sc);
},
//提示消息并记录
showmsg(msg) {
console.log(msg);
@ -115,4 +137,32 @@ var myjs = {
window.open(this.uri + this.base64(this.format(this.template, ctx)));
},
},
getUrlKey: function (name) {
return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.href) || [, ""])[1].replace(/\+/g, '%20')) || null
},
runSql: function (sqlStr, success) {
var option = {
method: "post",
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ sql: sqlStr, })
};
fetch("/RunSql", option)
.then(function (res) { return res.json(); })
.then(function (d) { success(d) })
.catch(function (r) { });
},
formatTimeToStr: function (times, pattern) {
if (times == null || times == "") {
return "";
}
if (pattern) {
var d = new Date(times).Format(pattern);
} else {
var d = new Date(times).Format("yyyy-MM-dd hh:mm:ss");
}
return d.toLocaleString();
},
};

View File

@ -3,12 +3,12 @@
<input type="hidden" name="ProcedureName" v-model="ProcedureName" />
<input type="hidden" name="reportName" v-model="reportName" />
<input type="hidden" name="sqlStr" v-model="sqlStr" />
<input type="hidden" name="uid" v-model="uid" />
<input type="hidden" name="uName" v-model="uName" />
<!--<input type="hidden" name="uid" v-model="uid" />
<input type="hidden" name="uName" v-model="uName" />-->
<!--以下部分为查询条件,根据实际需要修改-->
<label>姓名:<input name="name" value="" placeholder="输入姓名或留空" /></label>
<label>地址:<input name="address" value="" placeholder="输入地址数据" /></label>
<label>地址:<input name="marry" value="" placeholder="是否结婚" /></label>
<label>开始时间:<input name="Kssj" type="date" value="" placeholder="输入开始时间" /></label>
<label>结束时间:<input name="Jssj" type="date" value="" placeholder="输入结束时间" /></label>
<button class="btn btn-primary" v-on:click="find">查询</button>
<button class="btn btn-primary" v-on:click="print">打印</button>
<button class="btn btn-primary" v-on:click="down" v-if="result.length > 0">下载EXCEL</button>
@ -19,20 +19,32 @@
<table class="table reportTable" id="resultTable">
<thead>
<tr>
<th>工号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>登录用户编号</th>
<th>登录用户名</th>
<th>高血压中医干预</th>
<th>糖尿病中医干预</th>
<th>老年人中医干预</th>
<th>孕产妇中医干预</th>
<th>七岁以下儿童中医干预</th>
<th>中医体质辨识</th>
<th>总量</th>
<th>开始时间</th>
<th>结束时间</th>
</tr>
</thead>
<tbody>
<tr v-for="i in result">
<td>{{i.name}}</td>
<td>{{i.age}}</td>
<td>{{i.sex}}</td>
<td>{{i.uid}}</td>
<td>{{i.Uname}}</td>
<td>{{i.工号}}</td>
<td>{{i.姓名}}</td>
<td>{{i.高血压中医干预}}</td>
<td>{{i.糖尿病中医干预}}</td>
<td>{{i.老年人中医干预}}</td>
<td>{{i.孕产妇中医干预}}</td>
<td>{{i.七岁以下儿童中医干预}}</td>
<td>{{i.中医体质辨识}}</td>
<td>{{i.总量}}</td>
<td>{{i.开始时间}}</td>
<td>{{i.结束时间}}</td>
</tr>
</tbody>
</table>
@ -45,9 +57,9 @@
el: "#findPerson",
data: {
//存储过程名称
ProcedureName: "example",
ProcedureName: "CS_zdy_zygygzltj",
//报表名称
reportName: "报表1",
reportName: "中医/中医工作量查询",
//除非你知道以下数据含义,否则不要修改
sqlStr: "",

View File

@ -0,0 +1,101 @@
<!DOCTYPE html>
<html>
<head>
<title>中医工作量查询(克区)</title>
<link rel="stylesheet" href="../../lib/bootstrap/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="../../css/site.css" />
<script src="../../lib/jquery/dist/jquery.min.js"></script>
<script src="../../lib/bootstrap/dist/js/bootstrap.bundle.min.js"></script>
<script src="../../lib/jquery.cookie/jquery.cookie.min.js"></script>
<script src="../../lib/vue/vue.min.js"></script>
<script src="../../js/site.js" asp-append-version="true"></script>
<style type="text/css">
@page {
size: auto;
}
</style>
</head>
<body>
<div id="findPerson" class="table">
<div id="result" v-if="result.length > 0">
<table class="table">
<thead>
<tr>
<th colspan="5">中医工作量查询(克区)</th>
</tr>
<tr>
<th>工号</th>
<th>姓名</th>
<th>高血压中医干预</th>
<th>糖尿病中医干预</th>
<th>老年人中医干预</th>
<th>孕产妇中医干预</th>
<th>七岁以下儿童中医干预</th>
<th>中医体质辨识</th>
<th>总量</th>
<th>开始时间</th>
<th>结束时间</th>
</tr>
</thead>
<tfoot>
<tr>
<th colspan="5">打印时间:{{printTime}}</th>
</tr>
</tfoot>
<tbody>
<tr v-for="i in result">
<td>{{i.工号}}</td>
<td>{{i.姓名}}</td>
<td>{{i.高血压中医干预}}</td>
<td>{{i.糖尿病中医干预}}</td>
<td>{{i.老年人中医干预}}</td>
<td>{{i.孕产妇中医干预}}</td>
<td>{{i.七岁以下儿童中医干预}}</td>
<td>{{i.中医体质辨识}}</td>
<td>{{i.总量}}</td>
<td>{{i.开始时间}}</td>
<td>{{i.结束时间}}</td>
</tr>
</tbody>
</table>
</div>
</div>
<script type="text/javascript">
var findPersonPrint = new Vue({
el: "#findPerson",
data: {
url: myjs.urls.getresult,
result: [],
printTime: "",
rc: -1,
},
created() {
var _this = this;
var sql = myjs.getSqlStr();
console.log(sql);
this.rc = 1;
myjs.post(this.url, { sql: sql }, function (d) {
findPersonPrint.result = d;
_this.rc--;
});
this.rc++;
myjs.GetServerTime("yyyy年MM月dd日", function (r) {
_this.printTime = r;
_this.rc--;
});
},
methods: {
},
watch: {
rc(newName, oldName) {
if (newName == 0) {
setTimeout("alert('按ctrl+P打印')", 1000);
}
}
}
});
</script>
</body>
</html>

View File

@ -1,85 +0,0 @@
<div id="findPerson">
<div id="para" class="reportFilter">
<input type="hidden" name="ProcedureName" v-model="ProcedureName" />
<input type="hidden" name="reportName" v-model="reportName" />
<input type="hidden" name="sqlStr" v-model="sqlStr" />
<input type="hidden" name="uid" v-model="uid" />
<input type="hidden" name="uName" v-model="uName" />
<!--以下部分为查询条件,根据实际需要修改-->
<label>姓名:<input name="name" value="" placeholder="输入姓名或留空" /></label>
<label>地址:<input name="address" value="" placeholder="输入地址数据" /></label>
<label>地址:<input name="marry" value="" placeholder="是否结婚" /></label>
<div class="btn-group">
<button class="btn btn-primary" v-on:click="find">查询</button>
<button class="btn btn-primary" v-on:click="print">打印</button>
<button class="btn btn-primary" v-on:click="down" v-if="result.length > 0">下载EXCEL</button>
</div>
<!--可修改部分结束-->
</div>
<div id="result" v-if="result.length > 0">
<!--以下部分为存储过程返回数据,根据实际需要修改-->
<table class="table reportTable" id="resultTable">
<thead>
<tr>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>登录用户编号</th>
<th>登录用户名</th>
</tr>
</thead>
<tbody>
<tr v-for="i in result">
<td>{{i.name}}</td>
<td>{{i.age}}</td>
<td>{{i.sex}}</td>
<td>{{i.uid}}</td>
<td>{{i.Uname}}</td>
</tr>
</tbody>
</table>
<!--可修改部分结束-->
</div>
</div>
<script type="text/javascript">
var findPerson = new Vue({
el: "#findPerson",
data: {
//存储过程名称
ProcedureName: "example",
//报表名称
reportName: "报表1",
//除非你知道以下数据含义,否则不要修改
sqlStr: "",
url: myjs.urls.getresult,
result: [],
uid: "",
uName: "",
},
created() {
this.uid = myjs.getUId();
this.uName = myjs.getUName();
},
methods: {
find() {
var sql = myjs.createSql("#para");
this.sqlStr = sql;
console.log(sql);
myjs.post(this.url, { sql: sql }, function (d) {
findPerson.result = d;
});
},
print() {
var sql = myjs.createSql("#para");
this.sqlStr = sql;
myjs.print();
},
down() {
myjs.toExcel.tableToExcel("resultTable", this.reportName);
},
}
});
</script>