Hugo | Small Fix:剧目对比按钮


2025-11-13
2025-11-21
7759 字
因为评的剧多了,想在评价的时候显示一个对比坐标系,所以有了这个界面。

最近写的音乐剧评价打分比较多,就想做个按钮,点击以后开界面,界面里面有一个各个剧目的各个分数项的对比,下面卡片式显示分数和评语。然后提了需求,让 AI 写了代码,调试了一天,最终效果如下。

代码过程操作:

第一步尝试

剧目基础信息存储 | JSON

用了 json 格式记录数据,操作流程如下:

在个人博客的根目录(和 contentthemesstatic 这些文件夹同级)创建名为 data 的文件夹。因为之后可能还有其他的数据,所以我在 data 下面创建了名为 musicals 的文件夹。之后在文件夹内,以不同剧目的名字创建 json 格式的文件,记录想要对比的基础数据,单项示范如下:文件名 TTB.json

json
 1{
 2  "id": "TTB",
 3  "title": "Tick, Tick, Boom ",
 4  "cover": "/img/posts/theater/202510-TickTickBoom.jpg",
 5  "ratings": {
 6    "综合": 3.3,
 7    "剧本": 3,
 8    "音乐": 3.5,
 9    "舞美": 3.5,
10    "Bonus": null
11  },
12  "reviews": {
13    "剧本": "朴实无华。其实我不喜欢讲创作者题材的剧目我觉得有点 cliche 了,这个戏也没什么新意,但是流畅动情之处也确实动情,所以综合来说及格了。",
14    "音乐": "有一些地方写得很灵,但整体上没有很出彩,4 分,偶尔在旋律和声上有点太经典音乐剧而缺乏美感和新意,很多看出来是重点曲目的歌曲现在听起来有点过时,扣了半分。(有些经典不过时只不过这个有点过时)",
15    "舞美": "空间上上下下的其实也只能说是准确无误地传达了剧情想要的效果,有一些小新意,但美感设计感稍逊。",
16    "Bonus": "没什么额外想说的。"
17  }
18}

短代码数据呈现 | HTML

之后用短代码实现按钮以及页面的编写。在 shortcodes 文件夹下面创建 play-comparison.html,放入代码如下。

go-html-template
  1<button class="comparison-trigger" onclick="togglePlayComparison()">
  2  {{ .Get "text" | default "查看剧目对比" }}
  3</button>
  4
  5<div id="playComparison" class="comparison-modal">
  6  <div class="comparison-container">
  7    <!-- 头部独立出来,不参与滚动 -->
  8    <div class="comparison-header">
  9      <h3>{{ .Get "text" | default "查看剧目对比" }}</h3>
 10      <button class="close-btn" onclick="togglePlayComparison()">×</button>
 11    </div>
 12    <!-- 内容区域,单独滚动 -->
 13    <div class="comparison-scrollable">
 14      <div class="comparison-content">
 15      <div class="comparison-table-container">
 16        <table class="theater-comparison-table">
 17          <thead>
 18          <tr>
 19          <th>剧目</th>
 20          {{ $categories := slice "综合" "剧本" "音乐" "舞美" "Bonus" }}
 21          {{ range $category := $categories }}<th>{{ $category }}</th>      {{ end }}
 22          </tr>
 23          </thead>
 24          
 25          <tbody>
 26          {{ range $play := site.Data.musicals }}
 27          <tr>
 28          <td class="play-name">{{ $play.title }}</td>
 29          {{ $categories := slice "综合" "剧本" "音乐" "舞美" "Bonus" }}
 30            {{ range $category := $categories }}
 31              <td class="rating-cell">
 32                {{ if index $play.ratings $category }}
 33                {{ $rating := index $play.ratings $category }}
 34                {{ $rating }}
 35                {{ else }} -
 36                {{ end }}
 37              </td>
 38            {{ end }}
 39          </tr>
 40          {{ end }}
 41          </tbody>
 42        </table>
 43    </div>
 44  
 45    <div class="reviews-section">
 46      <h4>详细评语</h4>
 47      {{ range $play := site.Data.musicals }}
 48      <div class="theater-review">
 49        <h5>{{ $play.title }}{{ if $play.ratings.综合 }}{{ $play.ratings.综合 }}/5{{ end }}</h5>
 50        <div class="review-content-whole">
 51        {{ if $play.cover }}
 52          <div class="review-cover">
 53          <img src="{{ $play.cover }}" alt="{{ $play.title }}" onerror="this.style.display='none'">
 54          </div>
 55        {{ end }}
 56
 57      <div class="review-scripts">
 58      {{ if $play.reviews.剧本 }}
 59      <div class="review-item">
 60      <strong>剧本:{{ if $play.ratings.剧本 }}{{ $play.ratings.剧本 }}/5,{{ end }}</strong>
 61      <span class="review-content">{{ $play.reviews.剧本 }}</span>
 62      </div>
 63      {{ end }}
 64
 65      {{ if $play.reviews.舞美 }}
 66      <div class="review-item">
 67      <strong>舞美:{{ if $play.ratings.舞美 }}{{ $play.ratings.舞美 }}/5,{{ end }}</strong>
 68      <span class="review-content">{{ $play.reviews.舞美 }}</span>
 69      </div>
 70      {{ end }}
 71      
 72      {{ if $play.reviews.音乐 }}
 73      <div class="review-item">
 74      <strong>音乐:{{ if $play.ratings.音乐 }}{{ $play.ratings.音乐 }}/5,{{ end }}</strong>
 75      <span class="review-content">{{ $play.reviews.音乐 }}</span>
 76      </div>
 77      {{ end }}
 78      
 79      {{ if $play.reviews.Bonus }}
 80      <div class="review-item">
 81      <strong>Bonus:{{ if $play.ratings.Bonus }}{{ $play.ratings.Bonus }}/5,{{ end }}</strong>
 82      <span class="review-content">{{ $play.reviews.Bonus }}</span>
 83      </div>
 84      {{ end }}
 85    </div>
 86    </div>
 87    </div>
 88    {{ end }}
 89    </div>
 90    </div>
 91    </div>
 92  </div>
 93</div>
 94
 95<script>
 96function togglePlayComparison() {
 97  const modal = document.getElementById('playComparison');
 98  if (modal.style.display === 'block') {
 99    modal.style.display = 'none';
100    document.body.style.overflow = 'auto';
101  } else {
102    modal.style.display = 'block';
103    document.body.style.overflow = 'hidden';
104  }
105}
106
107window.onclick = function(event) {
108  const modal = document.getElementById('playComparison');
109  if (event.target === modal) {
110    togglePlayComparison();
111  }
112}
113
114document.addEventListener('keydown', function(event) {
115  if (event.key === 'Escape') {
116    const modal = document.getElementById('playComparison');
117    if (modal.style.display === 'block') {
118      togglePlayComparison();
119    }
120  }
121});
122
123</script>

如果需要放按钮,就在需要摆放的 markdown 正文里放短代码就好。为了防止渲染,在标签前面多加了一个中文句号,自己使用记得删掉~

{{。< play-comparison text=" 其他剧目评分评语参考 " >}}

美化样式 | CSS

用 CSS 进行样式化。

css
  1/*剧目集中展示评分*/
  2
  3/* 触发按钮样式 */
  4.comparison-trigger {
  5  background: linear-gradient(135deg, #55a290 0%, #9055a2 100%);
  6  color: white;
  7  border: none;
  8  display: block;
  9  padding: 10px 20px;
 10  border-radius: 5px;
 11  cursor: pointer;
 12  font-size: 16px;
 13  font-weight: 500;
 14  transition: all 0.3s ease;
 15  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
 16  margin: 10px auto 40px auto;
 17  width: fit-content;
 18}
 19.comparison-trigger:hover {
 20  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
 21}
 22
 23/* 小屏幕调整 */
 24@media (max-width: 768px) {
 25  .comparison-trigger {
 26    width: 100%;
 27  }
 28}
 29/* 模态框样式 */
 30.comparison-modal {
 31  display: none;
 32  position: fixed;
 33  z-index: 10000;
 34  left: 0;
 35  top: 0;
 36  width: 100%;
 37  height: 100%;
 38  background-color: rgba(0, 0, 0, 0.5);
 39  backdrop-filter: blur(5px);
 40}
 41
 42/* 主容器 - 居中且固定高度 */
 43.comparison-container {
 44  position: absolute;
 45  top: 50%;
 46  left: 50%;
 47  transform: translate(-50%, -50%);
 48  width: 95%;
 49  max-width: 1000px;
 50  height: 80vh; /* 视窗高度的80% */
 51  border-radius: 5px;
 52  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
 53  display: flex;
 54  flex-direction: column;
 55  animation: modalSlideIn 0.3s ease;
 56  overflow: hidden; /* 防止内容溢出 */
 57}
 58@keyframes modalSlideIn {
 59  from {
 60    opacity: 0;
 61    transform: translate(-50%, -48%); /* 微调动画起始位置 */
 62  }
 63  to {
 64    opacity: 1;
 65    transform: translate(-50%, -50%);
 66  }
 67}
 68
 69/* 头部样式 - 固定高度,不参与滚动 */
 70.comparison-header {
 71  display: flex;
 72  justify-content: space-between;
 73  align-items: center;
 74  padding: 20px 30px;
 75  background: linear-gradient(135deg, #55a290 0%, #764ba2 100%);
 76  color: white;
 77  flex-shrink: 0; /* 防止头部被压缩 */
 78  z-index: 100;
 79}
 80.comparison-header h3 {
 81  margin: 0;
 82  font-size: 1.5em;
 83  color: white;
 84}
 85.close-btn {
 86  background: none;
 87  border: none;
 88  color: white;
 89  font-size: 28px;
 90  cursor: pointer;
 91  padding: 0;
 92  width: 40px;
 93  height: 40px;
 94  display: flex;
 95  align-items: center;
 96  justify-content: center;
 97  border-radius: 50%;
 98  transition: background-color 0.3s;
 99  line-height: 1;
100}
101.close-btn:hover {
102  background-color: rgba(255, 255, 255, 0.2);
103}
104
105/* 可滚动区域 */
106.comparison-scrollable {
107  flex: 1; /* 占据剩余空间 */
108  overflow-y: auto; /* 垂直滚动 */
109  overflow-x: hidden; /* 隐藏水平滚动 */
110}
111
112/* 内容区域 */
113.comparison-content {
114  padding: 0;
115}
116
117/* 表格容器 */
118.comparison-table-container {
119  padding: 20px;
120  overflow-x: auto;
121  background-color: #fefefe;
122  display: block;
123  margin: 0 auto;
124}
125
126/* 表格样式 */
127.theater-comparison-table {
128  width: fit-content;
129  border-collapse: collapse;
130  margin-bottom: 20px;
131  font-size: 14px;
132  display: block;
133  margin: 0 auto;
134}
135.theater-comparison-table th,
136.theater-comparison-table td {
137  padding: 12px 15px;
138  text-align: center !important;
139  border: 1px solid #e1e1e1;
140  vertical-align: middle;
141}
142.theater-comparison-table th {
143  background-color: #f8f9fa;
144  font-weight: 600;
145  color: #333;
146}
147.theater-comparison-table thead {
148  tr {
149    th{
150    background-color: #55a290;
151    color: white;
152    }
153  }
154}
155.theater-comparison-table tr:nth-child(even){
156  background-color: #edf5f3;
157}
158.theater-comparison-table tr:nth-child(even):hover {
159  background-color: #ddece8;
160}
161
162/* 剧目名称列样式 - 允许换行 */
163.play-name {
164  font-weight: 600;
165  text-align: center;
166  white-space: normal !important;
167  word-wrap: break-word;
168  position: sticky;
169  left: 0;
170  z-index: 5;
171  min-width: 120px;
172  max-width: 150px;
173}
174
175/* 评分单元格样式 - 不允许换行 */
176.rating-cell {
177  font-size: 14px !important;
178  color: #2c3e50;
179  min-width: 80px;
180  white-space: nowrap;
181}
182
183/* 评语部分 */
184.reviews-section {
185  padding: 20px 30px;
186  border-top: 1px solid #e1e1e1;
187  background-color: #fafafa;
188}
189.reviews-section h4 {
190  margin-bottom: 20px;
191  color: #333;
192  border-bottom: 2px solid #5567a2;
193  padding-bottom: 8px;
194  font-size: 1.3em;
195  font-weight: 600;
196}
197.theater-review {
198  background: white;
199  padding: 20px;
200  margin-bottom: 20px;
201  border-radius: 5px;
202  box-shadow: 0 2px 10px rgba(144, 85, 162, 0.5);
203  border-left: 4px solid #5567a2;
204}
205.theater-review:last-child {
206  margin-bottom: 0;
207}
208.theater-review h5 {
209  margin: 0 0 15px 0;
210  color: #5567a2;
211  font-size: 1.2em;
212  font-weight: 600;
213  border-bottom: 1px solid #e1e1e1;
214  padding-bottom: 8px;
215}
216.review-item {
217  margin-bottom: 0.75em;
218  line-height: 1.55;
219  color: #555;
220  padding: 0;
221  border-bottom: none;
222}
223.review-item:last-child {
224  margin-bottom: 0;
225}
226.review-item strong {
227  color: #333;
228  font-weight: 600;
229  font-size: 0.95em;
230  display: inline;
231}
232.review-content {
233  color: #555;
234  line-height: 1.55;
235  display: inline;
236}
237
238/* 响应式调整 */
239@media (max-width: 768px) {
240  .comparison-container {
241    width: 98%;
242    height: 90vh; /* 小屏幕时高度更大 */
243  }
244  .comparison-header {
245    padding: 15px 20px;
246  }
247  .comparison-header h3 {
248    font-size: 1.2em;
249  }
250  .comparison-table-container {
251    padding: 10px;
252  }
253  .theater-comparison-table th,
254  .theater-comparison-table td {
255    padding: 8px 10px;
256    font-size: 0.9em;
257  }
258  .play-name {
259    min-width: 100px;
260    max-width: 120px;
261    font-size: 0.9em;
262  }
263  .rating-cell {
264    min-width: 50px;
265    font-size: 0.9em;
266  }
267  .reviews-section {
268    padding: 15px 20px;
269  }
270  .theater-review {
271    padding: 15px;
272  }
273}
274@media (max-width: 480px) {
275  .comparison-trigger {
276    padding: 8px 16px;
277    font-size: 16px;
278    width: 100%;
279  }
280  .comparison-header {
281    gap: 10px;
282    text-align: center;
283  }
284  .play-name {
285    min-width: 50px;
286    font-size: 0.85em;
287  }
288  .comparison-container {
289    height: 95vh; /* 小屏幕几乎全屏 */
290  }
291}
292
293/* 评语 - 包含封面和内容 */
294.review-title {
295  flex: 1;
296}
297.review-title h5 {
298  margin: 0;
299  color: #5567a2;
300  font-size: 1.4em;
301  font-weight: 600;
302  line-height: 1.3;
303}
304.review-content-whole {
305  display: flex;
306  flex-direction: row;
307}
308.review-cover {
309  display: block;
310  flex-shrink: 0;
311  width: 120px;
312  height: 160px;
313  border-radius: 5px;
314  overflow: hidden;
315  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
316}
317.review-cover img {
318  width: 100%;
319  height: 100%;
320  object-fit: cover;
321  display: block;
322}
323.review-scripts {
324  display: block;
325  flex: 1;
326  margin-left: 20px;
327}
328
329/* 小屏幕样式 - 768px以下隐藏封面 */
330@media (max-width: 768px) {
331
332  /* 小屏幕隐藏封面 */  
333  .review-cover {
334    display: none; /* 小屏幕隐藏封面 */
335  }
336  .review-scripts {
337    margin-left: 0;
338  }
339}

可以自己注意改一下颜色样式什么的,我的是适配了我自己的主题~

结束了!

祝大家使用愉快~

第二步优化

这里是 20251121 的分界线。使用以后我发现,由于短代码触发的模态框只是界面显隐,但都记录在网页里,导致我所有增加按钮的页面,都会把按钮点击后打开的页面内的文字记录到网页总字数里,在搜索的时候,也会因为搜索到模态框里面的信息,而让使用了短代码的博文出现在搜索结果里。

因此在调试后,认为最好的解决方式是,创立一个新的对比页面,而短代码放置的按钮只负责触发模态框,模态框里面打开的内容则是对比页面的内容。这样因为对比内容都放在另外的页面里,搜索结果和字数都不会有问题。

JSON 的文件格式还是保留。

哦对,因为数据变多了以后排列顺序会有变化,所以额外增加排序选择~

创建独立页面

创建 content/musical-comparison.md,在文件的信息部分增加如下信息,指定页面标题和网页模板。

yaml
1title= "音乐剧评价汇总"
2layout="musical-comparison"

其次创建 layouts/_default/musical-comparison.html.

不同主题模板的基础格式不同,我放在这里作参考,大家可以根据自己的主题自行调整。

go-html-template
  1{{ define "main" }}
  2<div class="col-xs-12 col-sm-8 col-md-9">
  3  {{ partial "mobile_nav_toggle.html" . }}
  4
  5<div class="play-comparison-body">
  6  <div class="comparison-container">
  7    <div class="comparison-header">
  8      <h3>剧目评分对比</h3>
  9      <button class="close-btn" onclick="if (window.opener) { window.close(); } else { history.back(); }">×</button>
 10    </div>
 11    <div class="comparison-scrollable">
 12    <div class="comparison-content">
 13    
 14      <div class="comparison-table-container">
 15        <table class="theater-comparison-table">
 16        <thead>
 17          <tr>
 18          <th>剧目</th>
 19          {{ $categories := slice "综合" "剧本" "音乐" "舞美" "Bonus" }}
 20          {{ range $category := $categories }}
 21            <th>{{ $category }}</th>
 22          {{ end }}
 23          </tr>
 24        </thead>
 25        <tbody id="comparisonTableBody">
 26          {{ range $play := site.Data.musicals }}
 27          <tr data-title="{{ $play.title }}" data-rating="{{ $play.ratings.综合 }}">
 28          <td class="play-name">{{ $play.title }}</td>
 29            {{ $categories := slice "综合" "剧本" "音乐" "舞美" "Bonus" }}
 30            {{ range $category := $categories }}
 31            <td class="rating-cell" data-rating="{{ if index $play.ratings $category }}{{ index $play.ratings $category }}{{ end }}">
 32            {{ if index $play.ratings $category }}
 33              {{ $rating := index $play.ratings $category }}
 34              {{ $rating | lang.FormatNumber 1 }}
 35            {{ else }} -
 36            {{ end }}
 37          </td>
 38          {{ end }}
 39          </tr>
 40          {{ end }}
 41        </tbody>
 42        </table>
 43      </div>
 44      
 45      <div class="reviews-section">
 46      
 47        <div class="reviews-header">
 48          <h4>详细评语</h4>
 49          <div class="sort-options">
 50            <span class="sort-label">排序:</span>
 51            <div class="sort-buttons">
 52              <button class="sort-btn active" data-sort="default">默认</button>
 53              <button class="sort-btn" data-sort="rating-desc">评分 ↓</button>
 54              <button class="sort-btn" data-sort="rating-asc">评分 ↑</button>
 55            </div>
 56          </div>
 57        </div>
 58        
 59        <div id="reviewsContainer">
 60          {{ range $play := site.Data.musicals }}
 61          <div class="theater-review" data-title="{{ $play.title }}" data-rating="{{ $play.ratings.综合 }}">
 62            <h5>{{ $play.title }}{{ if $play.ratings.综合 }}{{ $play.ratings.综合 | lang.FormatNumber 1 }}/5{{ end }}</h5>
 63            <div class="review-content-whole">
 64              {{ if $play.cover }}
 65                <div class="review-cover">
 66                <img src="{{ $play.cover }}" alt="{{ $play.title }}" onerror="this.style.display='none'">
 67                </div>
 68              {{ end }}
 69              <div class="review-scripts">
 70                <!-- 剧本 -->
 71                {{ if $play.reviews.剧本 }}
 72                  <div class="review-item">
 73                  <strong>剧本:{{ if $play.ratings.剧本 }}{{ $play.ratings.剧本 | lang.FormatNumber 1 }}/5,{{ end }}</strong>
 74                  <span class="review-content">{{ $play.reviews.剧本 }}</span>
 75                  </div>
 76                {{ end }}
 77                <!-- 音乐 -->
 78                {{ if $play.reviews.音乐 }}
 79                  <div class="review-item">
 80                  <strong>音乐:{{ if $play.ratings.音乐 }}{{ $play.ratings.音乐 | lang.FormatNumber 1 }}/5,{{ end }}</strong>
 81                  <span class="review-content">{{ $play.reviews.音乐 }}</span>
 82                  </div>
 83                {{ end }}
 84                <!-- 舞美 -->
 85                {{ if $play.reviews.舞美 }}
 86                  <div class="review-item">
 87                  <strong>舞美:{{ if $play.ratings.舞美 }}{{ $play.ratings.舞美 | lang.FormatNumber 1 }}/5,{{ end }}</strong>
 88                  <span class="review-content">{{ $play.reviews.舞美 }}</span>
 89                  </div>
 90                {{ end }}
 91                <!-- Bonus -->
 92                {{ if $play.reviews.Bonus }}
 93                  <div class="review-item">
 94                  <strong>Bonus:{{ if $play.ratings.Bonus }}{{ $play.ratings.Bonus | lang.FormatNumber 1 }}/5,{{ end }}</strong>
 95                  <span class="review-content">{{ $play.reviews.Bonus }}</span>
 96                  </div>
 97                {{ end }}
 98              </div>
 99            </div>
100          </div>
101          {{ end }}
102        </div>
103      </div>
104
105    </div>
106    </div>
107  </div>
108
109<script>
110// 排序功能
111document.addEventListener('DOMContentLoaded', function() {
112  const sortButtons = document.querySelectorAll('.sort-btn');
113  const reviewsContainer = document.getElementById('reviewsContainer');
114  const tableBody = document.getElementById('comparisonTableBody');
115  // 按钮排序功能
116  sortButtons.forEach(button => {
117  
118  button.addEventListener('click', function() {
119    // 移除其他按钮的active状态
120    sortButtons.forEach(btn => btn.classList.remove('active'));
121    // 添加当前按钮的active状态
122    this.classList.add('active');
123    const sortType = this.getAttribute('data-sort');
124      sortReviews(sortType);
125      sortTable(sortType);
126    });
127  });
128  
129  function sortReviews(sortType) {
130    const reviews = Array.from(reviewsContainer.querySelectorAll('.theater-review'));
131    reviews.sort((a, b) => {
132      const titleA = a.getAttribute('data-title');
133      const titleB = b.getAttribute('data-title');
134      const ratingA = parseFloat(a.getAttribute('data-rating')) || 0;
135      const ratingB = parseFloat(b.getAttribute('data-rating')) || 0;
136      switch(sortType) {
137        case 'rating-desc':
138          return ratingB - ratingA;
139        case 'rating-asc':
140          return ratingA - ratingB;
141        case 'default':
142          default:
143        return titleA.localeCompare(titleB);
144      }
145    });
146    reviewsContainer.innerHTML = '';
147    reviews.forEach(review => {
148      reviewsContainer.appendChild(review);
149    });
150  }
151
152  function sortTable(sortType) {
153    const rows = Array.from(tableBody.querySelectorAll('tr'));
154    rows.sort((a, b) => {
155      const titleA = a.getAttribute('data-title');
156      const titleB = b.getAttribute('data-title');
157      const ratingA = parseFloat(a.getAttribute('data-rating')) || 0;
158      const ratingB = parseFloat(b.getAttribute('data-rating')) || 0;
159      switch(sortType) {
160        case 'rating-desc':
161          return ratingB - ratingA;
162        case 'rating-asc':
163          return ratingA - ratingB;
164        case 'default':
165          default:
166        return titleA.localeCompare(titleB);
167      }
168    });
169    tableBody.innerHTML = '';
170    rows.forEach(row => {
171      tableBody.appendChild(row);
172    });
173  }
174});
175</script>
176
177</div>
178<a id="bottom"></a>  
179
180</div>
181{{ end }}

修改短代码相关内容

主要是需要短代码唤起模态框,在模态框里引用 localhost:1313/musical-comparison 页面的页面元素。

go-html-template
  1<button class="comparison-trigger" onclick="togglePlayComparison()">
  2  {{ .Get "text" | default "查看剧目对比" }}
  3</button>
  4
  5<div id="playComparisonModal" class="comparison-modal">
  6  <div class="modal-content">
  7    <div class="modal-header">
  8      <h3>剧目评分对比</h3>
  9      <button class="close-btn" onclick="togglePlayComparison()">×</button>
 10    </div>
 11    <div class="modal-body">
 12      <div id="comparisonContent" class="modal-scrollable">
 13        <div class="loading">加载中...</div>
 14      </div>
 15    </div>
 16  </div>
 17</div>
 18
 19<script>
 20// 确保DOM加载完成
 21document.addEventListener('DOMContentLoaded', function() {
 22  // 初始化模态框状态
 23  const modal = document.getElementById('playComparisonModal');
 24  const content = document.getElementById('comparisonContent');
 25  // 确保模态框初始状态正确
 26  modal.style.display = 'none';
 27});
 28
 29function togglePlayComparison() {
 30  const modal = document.getElementById('playComparisonModal');
 31  const content = document.getElementById('comparisonContent');
 32  // 使用getComputedStyle确保跨浏览器兼容
 33  const isOpen = window.getComputedStyle(modal).display === 'block';
 34  if (isOpen) {
 35    modal.style.display = 'none';
 36    document.body.style.overflow = 'auto';
 37  } else {
 38    // 确保模态框层级最高
 39    modal.style.display = 'block';
 40    modal.style.zIndex = '10000';
 41    document.body.style.overflow = 'hidden';
 42    // 强制重绘以确保显示
 43    modal.offsetHeight;
 44    if (shouldLoadContent(content)) {
 45      loadComparisonContent(content);
 46    }
 47  }
 48}
 49
 50function shouldLoadContent(content) {
 51  return content.innerHTML.includes('loading') || content.children.length <= 1;
 52}
 53
 54function loadComparisonContent(content) {
 55  content.innerHTML = '<div class="loading">加载中...</div>';
 56  fetch('/musical-comparison/')
 57    .then(response => {
 58      if (!response.ok) throw new Error('Network response was not ok');
 59      return response.text();
 60    })
 61    .then(html => {
 62      const parser = new DOMParser();
 63      const doc = parser.parseFromString(html, 'text/html');
 64      const mainContent = doc.querySelector('.comparison-content');
 65      if (mainContent) {
 66        content.innerHTML = mainContent.innerHTML;
 67        bindSortEvents();
 68      } else {
 69        content.innerHTML = '<p>加载失败,内容格式错误</p>';
 70      }
 71    })
 72    .catch(error => {
 73      console.error('加载内容失败:', error);
 74      content.innerHTML = '<p>加载失败,请刷新页面重试</p>';
 75    });
 76}
 77
 78// 其他函数保持不变...
 79function bindSortEvents() {
 80  const sortButtons = document.querySelectorAll('.sort-btn');
 81  const reviewsContainer = document.getElementById('reviewsContainer');
 82  const tableBody = document.getElementById('comparisonTableBody');
 83  if (!sortButtons.length || !reviewsContainer || !tableBody) return;
 84  sortButtons.forEach(button => {
 85    button.addEventListener('click', function() {
 86      sortButtons.forEach(btn => btn.classList.remove('active'));
 87      this.classList.add('active');
 88      const sortType = this.getAttribute('data-sort');
 89      sortReviews(sortType);
 90      sortTable(sortType);
 91    });
 92  });
 93}
 94
 95function sortReviews(sortType) {
 96  const reviewsContainer = document.getElementById('reviewsContainer');
 97  if (!reviewsContainer) return;
 98  const reviews = Array.from(reviewsContainer.querySelectorAll('.theater-review'));
 99  reviews.sort((a, b) => {
100    const titleA = a.getAttribute('data-title');
101    const titleB = b.getAttribute('data-title');
102    const ratingA = parseFloat(a.getAttribute('data-rating')) || 0;
103    const ratingB = parseFloat(b.getAttribute('data-rating')) || 0;
104    switch(sortType) {
105      case 'rating-desc':
106        return ratingB - ratingA;
107      case 'rating-asc':
108        return ratingA - ratingB;
109      case 'default':
110      default:
111        return titleA.localeCompare(titleB);
112    }
113  });
114  reviewsContainer.innerHTML = '';
115  reviews.forEach(review => {
116    reviewsContainer.appendChild(review);
117  });
118}
119
120function sortTable(sortType) {
121  const tableBody = document.getElementById('comparisonTableBody');
122  if (!tableBody) return;
123  const rows = Array.from(tableBody.querySelectorAll('tr'));
124  rows.sort((a, b) => {
125    const titleA = a.getAttribute('data-title');
126    const titleB = b.getAttribute('data-title');
127    const ratingA = parseFloat(a.getAttribute('data-rating')) || 0;
128    const ratingB = parseFloat(b.getAttribute('data-rating')) || 0;
129    switch(sortType) {
130      case 'rating-desc':
131        return ratingB - ratingA;
132      case 'rating-asc':
133        return ratingA - ratingB;
134      case 'default':
135      default:
136        return titleA.localeCompare(titleB);
137    }
138  });
139  tableBody.innerHTML = '';
140  rows.forEach(row => {
141    tableBody.appendChild(row);
142  });
143}
144
145// 事件监听 - 使用更兼容的方式
146document.addEventListener('click', function(event) {
147  const modal = document.getElementById('playComparisonModal');
148  if (event.target === modal) {
149    togglePlayComparison();
150  }
151});
152
153document.addEventListener('keydown', function(event) {
154  if (event.key === 'Escape') {
155    const modal = document.getElementById('playComparisonModal');
156    if (window.getComputedStyle(modal).display === 'block') {
157      togglePlayComparison();
158    }
159  }
160});
161</script>

修改相关 CSS

懒得单独说修改了,干脆替代式贴过来。

css
  1/*剧目集中展示评分*/
  2
  3/* 触发按钮样式 */
  4.comparison-trigger {
  5  background: linear-gradient(135deg, #55a290 0%, #9055a2 100%);
  6  color: white;
  7  border: none;
  8  display: block;
  9  padding: 10px 20px;
 10  border-radius: 5px;
 11  cursor: pointer;
 12  font-size: 16px;
 13  font-weight: 500;
 14  transition: all 0.3s ease;
 15  box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
 16  margin: 10px auto 40px auto;
 17  width: fit-content;
 18}
 19
 20.comparison-trigger:hover {
 21  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3);
 22}
 23
 24/* 小屏幕调整 */
 25@media (max-width: 768px) {
 26  .comparison-trigger {
 27    width: 100%;
 28  }
 29}
 30
 31/* 模态框样式 */
 32/* 浏览器兼容性修复 */
 33.comparison-modal {
 34  display: none;
 35  position: fixed;
 36  z-index: 10000;
 37  left: 0;
 38  top: 0;
 39  width: 100%;
 40  height: 100%;
 41  background-color: rgba(0, 0, 0, 0.5);
 42  /* 为Edge添加备用背景色 */
 43  background-color: rgba(0, 0, 0, 0.5)\9; /* IE/Edge */
 44  backdrop-filter: blur(5px);
 45  /* 确保在Edge中正确显示 */
 46  -ms-overflow-style: none; /* IE/Edge */
 47}
 48
 49/* 模态框内容容器 */
 50/* 修复Edge中的模态框定位 */
 51.modal-content {
 52  position: absolute;
 53  top: 50%;
 54  left: 50%;
 55  transform: translate(-50%, -50%);
 56  width: 95%;
 57  max-width: 1000px;
 58  height: 80vh;
 59  border-radius: 5px;
 60  display: flex;
 61  flex-direction: column;
 62  animation: modalSlideIn 0.3s ease;
 63  overflow: hidden;
 64  background: transparent;
 65  /* 为Edge添加边框和阴影 */
 66  border: 0px;
 67  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);
 68  /* Edge兼容性 */
 69  -ms-transform: translate(-50%, -50%);
 70}
 71
 72/* 确保模态框在Edge中正确显示 */
 73@media all and (-ms-high-contrast: none), (-ms-high-contrast: active) {
 74  /* IE10+ CSS styles */
 75  .comparison-modal {
 76    background-color: rgba(0, 0, 0, 0.7);
 77  }
 78  .modal-content {
 79    position: fixed;
 80    margin: 0;
 81  }
 82}
 83
 84@keyframes modalSlideIn {
 85  from {
 86    opacity: 0;
 87    transform: translate(-50%, -48%);
 88  }
 89  to {
 90    opacity: 1;
 91    transform: translate(-50%, -50%);
 92  }
 93}
 94
 95/* 模态框头部 */
 96.modal-header {
 97  display: flex;
 98  align-items: center;
 99  padding: 20px 30px;
100  background: linear-gradient(135deg, #55a290 0%, #764ba2 100%);
101  color: white;
102  flex-shrink: 0;
103  z-index: 100;
104}
105
106.modal-header h3 {
107  margin: 0;
108  font-size: 1.5em;
109  color: white;
110  text-align: left;
111  flex:1;
112}
113
114/* 模态框主体 - 确保有滚动 */
115.modal-body {
116  flex: 1;
117  overflow: hidden;
118  display: flex;
119  flex-direction: column;
120  background: white;
121  /* Edge flex兼容性 */
122  -ms-flex: 1 1 auto;
123  padding: 0;
124}
125
126/* 模态框内的滚动容器 */
127.modal-scrollable {
128  flex: 1;
129  overflow-y: auto;
130  overflow-x: hidden;
131  /* Edge兼容性 */
132  -ms-overflow-style: none;
133  -ms-flex: 1 1 auto;
134}
135
136/* 确保独立页面的滚动容器在模态框内也有效 */
137.comparison-scrollable {
138  flex: 1;
139  overflow-y: auto;
140  overflow-x: hidden;
141  height: 100%;
142}
143
144/* 主容器 - 用于独立页面 */
145.comparison-container {
146  position: relative;
147  width: 100%;
148  min-height: 100vh;
149  margin: 0;
150  border-radius: 5px;
151  box-shadow: none;
152  display: flex;
153  flex-direction: column;
154  overflow: hidden;
155  background: white;
156}
157
158/* 头部样式 - 固定高度,不参与滚动 */
159.comparison-header {
160  display: flex;
161  justify-content: space-between;
162  align-items: center;
163  padding: 20px 30px;
164  background: linear-gradient(135deg, #55a290 0%, #764ba2 100%);
165  color: white;
166  flex-shrink: 0;
167}
168
169.comparison-header h3 {
170  margin: 0;
171  font-size: 1.5em;
172  color: white;
173}
174
175.close-btn {
176  background: none;
177  border: none;
178  color: white;
179  font-size: 28px;
180  cursor: pointer;
181  padding: 0;
182  width: 40px;
183  height: 40px;
184  display: flex;
185  align-items: center;
186  justify-content: center;
187  border-radius: 50%;
188  transition: background-color 0.3s;
189  line-height: 1;
190  margin-left: auto;
191}
192
193.close-btn:hover {
194  background-color: rgba(255, 255, 255, 0.2);
195}
196
197/* 可滚动区域 */
198.comparison-scrollable {
199  flex: 1;
200  overflow-y: auto;
201  overflow-x: hidden;
202}
203
204/* 内容区域 */
205.comparison-content {
206  padding: 0;
207}
208
209/* 表格容器 */
210.comparison-table-container {
211  padding: 10px;
212  overflow-x: auto;
213  background-color: #fefefe;
214  display: block;
215  margin: 0 auto;
216}
217
218/* 表格样式 */
219.theater-comparison-table {
220  width: fit-content;
221  border-collapse: collapse;
222  margin-bottom: 20px;
223  font-size: 14px;
224  display: block;
225  margin: 0 auto;
226}
227
228.theater-comparison-table th,
229.theater-comparison-table td {
230  padding: 12px 15px;
231  text-align: center !important;
232  border: 1px solid #e1e1e1;
233  vertical-align: middle;
234}
235
236.theater-comparison-table th {
237  background-color: #f8f9fa;
238  font-weight: 600;
239  color: #333;
240}
241
242.theater-comparison-table thead tr th {
243  background: none;
244  background-color: #55a290;
245  color: white;
246}
247
248.theater-comparison-table tr:nth-child(even){
249  background-color: #edf5f3;
250}
251
252.theater-comparison-table tr:nth-child(even):hover {
253  background-color: #ddece8;
254}
255
256/* 剧目名称列样式 - 允许换行 */
257.play-name {
258  font-weight: 600;
259  text-align: center;
260  white-space: normal !important;
261  word-wrap: break-word;
262  position: sticky;
263  left: 0;
264  z-index: 5;
265  min-width: 120px;
266  max-width: 150px;
267}
268
269/* 评分单元格样式 - 不允许换行 */
270.rating-cell {
271  font-size: 14px !important;
272  color: #2c3e50;
273  min-width: 80px;
274  white-space: nowrap;
275}
276
277/* 评语部分 */
278.reviews-section {
279  padding: 20px 30px;
280  border-top: 1px solid #e1e1e1;
281  background-color: #fafafa;
282}
283
284.reviews-section h4 {
285  margin-bottom: 20px;
286  color: #333;
287  border-bottom: 2px solid #5567a2;
288  padding-bottom: 8px;
289  font-size: 1.3em;
290  font-weight: 600;
291}
292
293.theater-review {
294  background: white;
295  padding: 20px;
296  margin-bottom: 20px;
297  border-radius: 5px;
298  box-shadow: 0 2px 10px rgba(144, 85, 162, 0.5);
299  position: relative;
300  overflow: hidden;
301}
302
303.theater-review::before {
304  content: '';
305  position: absolute;
306  left: 0;
307  top: 0px;
308  bottom: 0px;
309  width: 4px;
310  background: linear-gradient(to bottom, #5567a2 10%, #9055a2 90%);
311  border-radius: 0 2px 2px 0;
312}
313
314.theater-review:last-child {
315  margin-bottom: 0;
316}
317
318.theater-review h5 {
319  margin: 0 0 15px 0;
320  color: #5567a2;
321  font-size: 1.2em;
322  font-weight: 600;
323  border-bottom: 1px solid #e1e1e1;
324  padding-bottom: 8px;
325}
326
327.review-item {
328  margin-bottom: 0.75em;
329  line-height: 1.55;
330  color: #555;
331  padding: 0;
332  border-bottom: none;
333}
334
335.review-item:last-child {
336  margin-bottom: 0;
337}
338
339.review-item strong {
340  color: #333;
341  font-weight: 600;
342  font-size: 0.95em;
343  display: inline;
344}
345
346.review-content {
347  color: #555;
348  line-height: 1.55;
349  display: inline;
350}
351
352/* 响应式调整 */
353@media (max-width: 768px) {
354  .modal-content {
355    width: 98%;
356    height: 90vh;
357  }
358  .comparison-container {
359    width: 100%;
360  }
361  .comparison-header,
362  .modal-header {
363    padding: 15px 20px;
364  }
365  .comparison-header h3,
366  .modal-header h3 {
367    font-size: 1.2em;
368  }
369  .comparison-table-container {
370    padding: 10px;
371  }
372  .theater-comparison-table th,
373  .theater-comparison-table td {
374    padding: 8px 10px;
375    font-size: 0.9em;
376  }
377  .play-name {
378    min-width: 100px;
379    max-width: 120px;
380    font-size: 0.9em;
381  }
382  .rating-cell {
383    min-width: 50px;
384    font-size: 0.9em;
385  }
386  .reviews-section {
387    padding: 15px 20px;
388  }
389  .theater-review {
390    padding: 15px;
391  }
392}
393
394@media (max-width: 480px) {
395  .comparison-trigger {
396    padding: 8px 16px;
397    font-size: 16px;
398    width: 100%;
399  }
400  .comparison-header,
401  .modal-header {
402    gap: 10px;
403    text-align: center;
404  }
405  .play-name {
406    min-width: 50px;
407    font-size: 0.85em;
408  }
409  .modal-content {
410    height: 95vh;
411  }
412}
413
414/* 评语 - 包含封面和内容 */
415.review-content-whole {
416  display: flex;
417  flex-direction: row;
418}
419
420.review-cover {
421  display: block;
422  flex-shrink: 0;
423  width: 120px;
424  height: 160px;
425  border-radius: 5px;
426  overflow: hidden;
427  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
428}
429
430.review-cover img {
431  width: 100%;
432  height: 100%;
433  object-fit: cover;
434  display: block;
435}
436
437.review-scripts {
438  display: block;
439  flex: 1;
440  margin-left: 20px;
441}
442
443/* 小屏幕样式 - 768px以下隐藏封面 */
444
445@media (max-width: 768px) {
446  .review-cover {
447    display: none;
448  }
449  .review-scripts {
450    margin-left: 0;
451  }
452}
453
454/* 排序功能样式 */
455.reviews-header {
456  display: flex;
457  justify-content: space-between;
458  align-items: center;
459  margin-bottom: 20px;
460  flex-wrap: wrap;
461  gap: 15px;
462}
463
464.reviews-header h4 {
465  margin: 0;
466  color: #333;
467  border-bottom: 2px solid #5567a2;
468  padding-bottom: 8px;
469  font-size: 1.3em;
470  font-weight: 600;
471}
472
473.sort-options {
474  display: flex;
475  align-items: center;
476  gap: 10px;
477}
478
479.sort-label {
480  color: #666;
481  font-size: 0.9em;
482  font-weight: 500;
483}
484
485.sort-buttons {
486  display: flex;
487  background: #f5f5f5;
488  border-radius: 6px;
489  padding: 4px;
490  gap: 2px;
491}
492
493.sort-btn {
494  background: transparent;
495  min-width: 70px;
496  border: none;
497  padding: 8px 12px;
498  border-radius: 4px;
499  cursor: pointer;
500  font-size: 0.85em;
501  font-weight: 500;
502  color: #666;
503  transition: all 0.3s ease;
504  white-space: nowrap;
505}
506
507.sort-btn:hover {
508  background: rgba(85, 102, 162, 0.1);
509  color: #5567a2;
510}
511
512.sort-btn.active {
513  background: #5567a2;
514  color: white;
515}
516
517/* 加载状态 */
518.loading {
519  display: flex;
520  justify-content: center;
521  align-items: center;
522  height: 200px;
523  font-size: 1.1em;
524  color: #666;
525}
526
527/* 响应式调整 */
528@media (max-width: 768px) {
529  .reviews-header {
530    flex-direction: column;
531    align-items: flex-start;
532  }
533  .sort-options {
534    width: 100%;
535    justify-content: flex-start;
536  }
537  .sort-buttons {
538    flex: 1;
539    justify-content: space-between;
540  }
541  .sort-btn {
542    flex: 1;
543    text-align: center;
544    font-size: 0.8em;
545    padding: 8px 6px;
546  }
547}
548
549@media (max-width: 480px) {
550  .sort-buttons {
551    gap: 4px;
552  }
553  .sort-btn {
554    padding: 10px;
555  }
556}

好啦!现在就完美了!