如何从Express路由将数据传入页面的script标签?
首先得说,你遇到的问题其实分两部分:异步操作导致数据还没准备好就渲染模板,以及Handlebars模板中直接在script里使用数组的正确方式,咱们一步步解决。
第一步:先修复异步查询的顺序问题
看你的路由代码,三个数据库查询(User.findOne、Tasks.findAndCountAll、Tasks.findAll)是并行执行的,但你直接在Tasks.findAll的回调里调用了res.render——这时候前两个异步操作大概率还没完成,所以countList和categoryList还是空数组,传去模板自然没用。
解决办法是用Promise.all等待所有异步操作完成,再统一渲染:
app.get('/:username/tasks', function (req, res) { if (req.session.user === req.params.username) { // 把每个数据库查询包装成Promise const getUserPhoto = db.User.findOne({ where: { username: req.session.user } }); const getTaskStats = db.Tasks.findAndCountAll({ // 修正attributes配置,正确拿到每个分类的数量 attributes: ['category', [db.fn('count', 'category'), 'taskCount']], where: { UserUsername: req.session.user }, group: 'category' }); const getAllTasks = db.Tasks.findAll({ where: { UserUsername: req.params.username } }); // 等待所有Promise执行完毕 Promise.all([getUserPhoto, getTaskStats, getAllTasks]) .then(([userInfo, statsResult, tasksData]) => { // 整理需要的数据 const photo = userInfo.dataValues.picURL; const categoryList = statsResult.rows.map(row => row.dataValues.category); const countList = statsResult.rows.map(row => row.dataValues.taskCount); console.log(categoryList); // 应该能拿到['Health', 'Other', ...] console.log(countList); // 对应[5,1,...] // 所有数据准备就绪,渲染模板 res.render('index', { data: tasksData, photo: photo, countList: countList, categoryList: categoryList }); }) .catch(err => { console.error('查询出错:', err); res.redirect('/'); }); } else { res.redirect('/'); } });
这里修正了findAndCountAll的attributes配置,因为默认分组后不会自动返回每个组的数量,需要手动用db.fn('count')统计,这样拿到的数据才是正确的。
第二步:在Script标签中正确使用Handlebars传递的数据
现在数据已经正确传到模板了,但直接写labels: [categoryList]没用,因为Handlebars默认会把数组转成字符串(比如变成"Health,Other,Recreational,Work"),而Chart.js需要的是JS数组。
有两种简单的解决方式:
方式一:直接在Script中输出JSON格式的数组
用Handlebars的三重大括号{{{}}}(避免HTML转义)配合JSON.stringify,直接把数组转成JS能识别的格式:
<script> var ctx = document.getElementById("myChart").getContext('2d'); var myChart = new Chart(ctx, { type: 'pie', data: { labels: {{{JSON.stringify categoryList}}}, datasets: [{ label: '# of Tasks', data: {{{JSON.stringify countList}}}, backgroundColor: [ 'rgba(255, 99, 132, 0.2)', 'rgba(54, 162, 235, 0.2)', 'rgba(255, 206, 86, 0.2)', 'rgba(75, 192, 192, 0.2)', ], borderColor: [ 'rgba(255,99,132,1)', 'rgba(54, 162, 235, 1)', 'rgba(255, 206, 86, 1)', 'rgba(75, 192, 192, 1)', ], borderWidth: 1 }] }, }); </script>
三重大括号会直接输出JSON.stringify后的结果(比如["Health","Other",...]),这样JS就能直接把它当作数组使用。
方式二:通过HTML的data属性传递数据
如果你不想在script里直接写Handlebars表达式,可以把数据存在隐藏元素的data属性里,再在JS中读取解析:
<!-- 在HTML中添加隐藏元素存储数据 --> <div id="chart-data" style="display:none" data-categories='{{{JSON.stringify categoryList}}}' data-counts='{{{JSON.stringify countList}}}'></div> <script> // 读取并解析数据 const chartDataEl = document.getElementById('chart-data'); const categoryList = JSON.parse(chartDataEl.dataset.categories); const countList = JSON.parse(chartDataEl.dataset.counts); // 初始化Chart var ctx = document.getElementById("myChart").getContext('2d'); var myChart = new Chart(ctx, { type: 'pie', data: { labels: categoryList, datasets: [{ label: '# of Tasks', data: countList, // 其他配置不变 }] }, }); </script>
这种方式更灵活,适合数据量较大或者需要在多个地方复用数据的场景。
最后验证
做完上面两步,重启Express服务,访问页面后打开浏览器控制台,确认categoryList和countList已经正确输出为数组,Chart.js就能正常生成饼图了。
内容的提问来源于stack exchange,提问作者Joshua Wood

