PiranhaCMS 中的 DevExpress Web 仪表板
Posted
技术标签:
【中文标题】PiranhaCMS 中的 DevExpress Web 仪表板【英文标题】:DevExpress Web Dashboard in PiranhaCMS 【发布时间】:2021-09-30 14:32:38 【问题描述】:我目前正在开发一个基于名为 PiranhaCMS 的 CMS 框架的 .NET Core 应用程序。这个框架允许定义可配置的“块”,基本上是小部件,可以由用户在他们的页面上添加。块的配置页面实现为 Vue.js 组件,然后通过 gulp 以标准 JS 格式编译代码(从 .vue 文件到 Vue.component(...) 语法),供 Piranha 框架使用阅读和渲染。 Piranha 的作者确认这是定义新区块的唯一方法。
在我们的一个自定义块中,我们正在尝试实现 DevExpress Web Dashboard。我尝试按照https://docs.devexpress.com/Dashboard/401150/web-dashboard/dashboard-component-for-vue 中概述的步骤进行操作,但无济于事,因为编译器会抛出异常,指出***声明应该是导出默认值 ... ,而不是导入语句。
我想出了一个解决方法,我在组件的 created() 方法上动态加载所需的脚本和样式,然后以与经典 javascript 案例相同的方式定义仪表板 (https://docs.devexpress.com/Dashboard/119158/web-dashboard/dashboard-control-for-javascript-applications-jquery-knockout-etc/add-web-dashboard-to-a-javascript-application) ;;) 然而,我确信有一个更优雅的解决方案来解决这个问题。
下面是与问题相关的代码。这是自定义块itools-dashboard.vue:
<template>
<div class="form-group block-body">
<div :id="'dashboard-designer-' + uid" class="dashboard-designer">
<div :id="'dashboard_' + uid" style="height: 100%;">
</div>
</div>
<div class="row">
<div class="col-sm-6" style="padding:10px; margin-top: 0px;vertical-align: top;">
<fieldset>
<legend>Dashboard</legend>
<div class="form-group">
<label>Dashboard name</label>
<select class="form-control small" :id="'dashboard-names-' + uid" v-model="model.dashboardName.value">
<option v-for="dash in dashboardNames"> dash </option>
</select>
</div>
<div class="form-group">
<label>Update time</label>
<input class="form-control small" type="number" v-model="model.updateTime.value">
</div>
<div class="form-group">
<label>Width</label>
<input class="form-control small" type="text" v-model="model.width.value">
</div>
<div class="form-group">
<label>Height</label>
<input class="form-control small" type="text" v-model="model.height.value">
</div>
</fieldset>
</div>
<div class="col-sm-6" style="padding:10px; margin-top: 0px; background-color: #fcfcfc; border:1px dotted lightgray; vertical-align: top;">
<itools-base :model="model"></itools-base>
</div>
</div>
</div>
</template>
<script>
export default
props: ["uid", "toolbar", "model"],
data: function ()
return
dashboardNames: [],
dahsboardConfig: null,
updateModes: ["period", "realtime"],
basePath: "../../../../assets/",
// define all the css and js files paths
cssResources: [
"devextreme/dist/css/light.css",
"@devexpress/analytics-core/dist/css/dx-analytics.common.css",
"@devexpress/analytics-core/dist/css/dx-analytics.light.css",
"@devexpress/analytics-core/dist/css/dx-querybuilder.css",
"devexpress-dashboard/dist/css/dx-dashboard.light.min.css"
],
jsResources: [
"js/jquery/jquery-3.3.1.min.js",
"jquery-ui-dist/jquery-ui.js",
"knockout/build/output/knockout-latest.js",
"ace-builds/src-min-noconflict/ace.js",
"ace-builds/src-min-noconflict/ext-language_tools.js",
"ace-builds/src-min-noconflict/theme-dreamweaver.js",
"ace-builds/src-min-noconflict/theme-ambiance.js",
"devextreme/dist/js/dx.all.js",
"devextreme/dist/js/dx.aspnet.mvc.js",
"devextreme-aspnet-data/js/dx.aspnet.data.js",
"@devexpress/analytics-core/dist/js/dx-analytics-core.min.js",
"@devexpress/analytics-core/dist/js/dx-querybuilder.min.js",
"devexpress-dashboard/dist/js/dx-dashboard.min.js"
]
,
created: function ()
// dynamically add the required css
this.cssResources.forEach(x =>
let link = document.createElement("link");
link.setAttribute("href", this.basePath + x);
link.setAttribute("rel", "stylesheet");
document.head.appendChild(link);
);
// dynamically add the js files.
// It needs to be a synchronous ajax call so that the exports are visible in the code
// (eg the new DevExpress call)
this.jsResources.forEach(x =>
$.ajax(
async: false,
url: this.basePath + x,
dataType: "script"
)
);
this.model.width.value = this.model.width.value || "100%";
this.model.height.value = this.model.height.value || "300";
this.model.updateTime.value = this.model.updateTime.value || 5000;
,
mounted: function ()
var h = document.getElementById("dashboard-designer-" + this.uid).clientHeight;
DevExpress.Dashboard.ResourceManager.embedBundledResources();
var dashboardControl = new DevExpress.Dashboard.DashboardControl(document.getElementById("dashboard_" + this.uid),
endpoint: "/api/dashboard",
workingMode: "Designer",
width: "100%",
height: "100%",
initialDashboardId: this.model.dashboardName.value,
);
dashboardControl.render();
,
beforeCreate: function ()
fetch("/api/Dashboards/GetDashboardNames")
.then(response => response.json())
.then(data =>
this.dashboardNames = data;
);
,
</script>
然后通过 gulp 任务编译到
Vue.component("itools-dashboard",
props: ["uid", "toolbar", "model"],
data: function ()
return
dashboardNames: [],
dahsboardConfig: null,
updateModes: ["period", "realtime"],
basePath: "../../../../assets/",
cssResources: ["devextreme/dist/css/light.css", "@devexpress/analytics-core/dist/css/dx-analytics.common.css", "@devexpress/analytics-core/dist/css/dx-analytics.light.css", "@devexpress/analytics-core/dist/css/dx-querybuilder.css", "devexpress-dashboard/dist/css/dx-dashboard.light.min.css"],
jsResources: ["js/jquery/jquery-3.3.1.min.js", "jquery-ui-dist/jquery-ui.js", "knockout/build/output/knockout-latest.js", "ace-builds/src-min-noconflict/ace.js", "ace-builds/src-min-noconflict/ext-language_tools.js", "ace-builds/src-min-noconflict/theme-dreamweaver.js", "ace-builds/src-min-noconflict/theme-ambiance.js", "devextreme/dist/js/dx.all.js", "devextreme/dist/js/dx.aspnet.mvc.js", "devextreme-aspnet-data/js/dx.aspnet.data.js", "@devexpress/analytics-core/dist/js/dx-analytics-core.min.js", "@devexpress/analytics-core/dist/js/dx-querybuilder.min.js", "devexpress-dashboard/dist/js/dx-dashboard.min.js"]
;
,
created: function ()
this.cssResources.forEach(x =>
let link = document.createElement("link");
link.setAttribute("href", this.basePath + x);
link.setAttribute("rel", "stylesheet");
document.head.appendChild(link);
);
this.jsResources.forEach(x =>
$.ajax(
async: false,
url: this.basePath + x,
dataType: "script"
);
);
this.model.width.value = this.model.width.value || "100%";
this.model.height.value = this.model.height.value || "300";
this.model.updateTime.value = this.model.updateTime.value || 5000;
,
mounted: function ()
DevExpress.Dashboard.ResourceManager.embedBundledResources();
var dashboardControl = new DevExpress.Dashboard.DashboardControl(document.getElementById("dashboard_" + this.uid),
endpoint: "/api/dashboard",
workingMode: "Designer",
width: "100%",
height: "100%",
initialDashboardId: this.model.dashboardName.value
);
dashboardControl.render();
,
beforeCreate: function ()
fetch("/api/Dashboards/GetDashboardNames").then(response => response.json()).then(data =>
this.dashboardNames = data;
);
,
template: "\n<div class=\"form-group block-body\">\n <div :id=\"'dashboard-designer-' + uid\" class=\"dashboard-designer\">\n <div :id=\"'dashboard_' + uid\" style=\"height: 100%;\">\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-sm-6\" style=\"padding:10px; margin-top: 0px;vertical-align: top;\">\n <fieldset>\n <legend>Dashboard</legend>\n <div class=\"form-group\">\n <label>Dashboard name</label>\n <select class=\"form-control small\" :id=\"'dashboard-names-' + uid\" v-model=\"model.dashboardName.value\">\n <option v-for=\"dash in dashboardNames\"> dash </option>\n </select>\n </div>\n <div class=\"form-group\">\n <label>Update time</label>\n <input class=\"form-control small\" type=\"number\" v-model=\"model.updateTime.value\">\n </div>\n <div class=\"form-group\">\n <label>Width</label>\n <input class=\"form-control small\" type=\"text\" v-model=\"model.width.value\">\n </div>\n <div class=\"form-group\">\n <label>Height</label>\n <input class=\"form-control small\" type=\"text\" v-model=\"model.height.value\">\n </div>\n </fieldset>\n </div>\n <div class=\"col-sm-6\" style=\"padding:10px; margin-top: 0px; background-color: #fcfcfc; border:1px dotted lightgray; vertical-align: top;\">\n <itools-base :model=\"model\"></itools-base>\n </div>\n </div>\n</div>\n"
);
由 Piranha 定义的负责编译的 gulp 任务是:
var gulp = require('gulp'),
sass = require('gulp-sass'),
cssmin = require("gulp-cssmin"),
uglifyes = require('uglify-es'),
composer = require('gulp-uglify/composer'),
uglify = composer(uglifyes, console),
rename = require("gulp-rename"),
concat = require("gulp-concat");
var path = require('path'),
vueCompiler = require('vue-template-compiler'),
babel = require("@babel/core"),
babelTemplate = require("@babel/template").default,
codeFrameColumns = require('@babel/code-frame').codeFrameColumns,
babelTypes = require("@babel/types"),
through2 = require('through2');
function vueCompile()
return through2.obj(function (file, _, callback)
var relativeFile = path.relative(file.cwd, file.path);
console.log(relativeFile);
var ext = path.extname(file.path);
if (ext === '.vue')
var getComponent;
getComponent = function (ast, sourceCode)
const ta = ast.program.body[0]
if (!babelTypes.isExportDefaultDeclaration(ta))
var msg = 'Top level declaration in file ' + relativeFile + ' must be "export default " \n' + codeFrameColumns(sourceCode, start: ta.loc.start , highlightCode: true );
throw msg;
return ta.declaration;
var compile;
compile = function (componentName, content)
var component = vueCompiler.parseComponent(content, []);
if (component.styles.length > 0)
component.styles.forEach(s =>
const linesToStyle = content.substr(0, s.start).split(/\r?\n/).length;
var msg = 'WARNING: <style> tag in ' + relativeFile + ' is ignored\n' + codeFrameColumns(content, start: line: linesToStyle , highlightCode: true );
console.warn(msg);
);
var ast = babel.parse(component.script.content,
parserOpts:
sourceFilename: file.path
);
var vueComponent = getComponent(ast, component.script.content);
vueComponent.properties.push(babelTypes.objectProperty(babelTypes.identifier('template'), babelTypes.stringLiteral(component.template.content)))
var wrapInComponent = babelTemplate("Vue.component(NAME, COMPONENT);");
var componentAst = wrapInComponent(
NAME: babelTypes.stringLiteral(componentName),
COMPONENT: vueComponent
)
ast.program.body = [componentAst]
babel.transformFromAst(ast, null, null, function (err, result)
if (err)
callback(err, null)
else
file.contents = Buffer.from(result.code);
callback(null, file)
);
var componentName = path.basename(file.path, ext);
if (file.isBuffer())
compile(componentName, file.contents.toString());
else if (file.isStream())
var chunks = [];
file.contents.on('data', function (chunk)
chunks.push(chunk);
);
file.contents.on('end', function ()
compile(componentName, Buffer.concat(chunks).toString());
);
else
callback(null, file);
);
var js =
name: "itools-blocks.js",
path: "wwwroot/assets/js/blocks/*.vue"
//
// Compile & minimize js files
//
gulp.task("min:js", function (done)
gulp.src(js.path, base: "." )
.pipe(vueCompile())
.pipe(concat("wwwroot/assets/js/blocks/" + js.name))
.pipe(gulp.dest("."))
.pipe(uglify().on('error', function (e)
console.log(e);
))
.pipe(rename(
suffix: ".min"
))
.pipe(gulp.dest("."));
done();
);
非常感谢任何形式的帮助
【问题讨论】:
【参考方案1】:您所指的带有“vueCompile”方法的 gulpfile 是专门为满足我们在框架中提供的内部组件的需求而编写的,它绝不是所有 Vue 组件编译的灵丹妙药。但是我理解您的问题,在编写此代码之前,我们拼命搜索了现有的 npm-packages,它们可以为我们提供所需的功能,但这并不容易找到,因为我们只使用了 Vue.js 中可用功能的子集
我们非常乐意收到有关如何完成此操作的反馈或更多信息,因此我们将关注此线程??
【讨论】:
嗨 Håkan,我从版本 6 开始就一直在使用 piranha,在我看来,VUE 的引入已经“加强”了框架,如果没有创建对象的可能性不是很有用吗?绑定到 VUE? 由于我们想要构建的编辑界面几乎不可能仅使用服务器端呈现的 asp.net 创建,因此我们不得不选择一个 JavaScript 框架来添加客户端功能。可以使用 MVC 或带有或不带有 Vue.js 的 Razor Pages 将自定义页面添加到管理器,但是当添加应该与现有编辑视图集成的组件时,例如字段或自定义块,有必要使用 Vue 构建这些。以上是关于PiranhaCMS 中的 DevExpress Web 仪表板的主要内容,如果未能解决你的问题,请参考以下文章