You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

418 lines
11 KiB

2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
2 weeks ago
  1. <template>
  2. <div class="articleandvote-section">
  3. <!-- 文章组件 -->
  4. <div class="article-section">
  5. <div>
  6. <h2 class="article-title"> {{ ActiveAndVote.articleTitle }} </h2>
  7. <div class="article-content">
  8. <p>{{ ActiveAndVote.articleContent }}</p>
  9. </div>
  10. </div>
  11. </div>
  12. <!-- 投票组件 -->
  13. <div class="voting-title">{{ ActiveAndVote.voteTitle }}</div>
  14. <div v-for="(option,index) in optionLists" :key="option.id" class="progressContainer">
  15. <el-progress
  16. v-if="!isResultShown"
  17. class="centered-progress"
  18. :percentage="100"
  19. :stroke-width="40"
  20. :format="() => option.optionContent"
  21. :text-inside="true"
  22. :class="{ active: selectedOptions.includes(option.id) }"
  23. @click="selectOption(option.id,index)"
  24. >
  25. </el-progress>
  26. <div v-if="isResultShown" class="progressWrapper">
  27. <el-progress
  28. class="left-progress"
  29. :percentage="getPercentage(option.count + 25)"
  30. :stroke-width="40"
  31. :format="() => option.optionContent"
  32. :text-inside="true"
  33. />
  34. <span class="count-right">{{ option.count + 25 }}</span>
  35. </div>
  36. </div>
  37. <button
  38. class="confirm-button"
  39. :class="{ 'disabled-style': ActiveAndVote.votePollStatus === 2 }"
  40. :disabled="ActiveAndVote.votePollStatus === 2 || selectedOptions.length === 0"
  41. @click="showConfirmModal">
  42. 确认投票
  43. </button>
  44. <!-- 提示框 -->
  45. <div v-if="showModal" class="modal-overlay">
  46. <div class="modal-content">
  47. <p>今日投票次数剩余{{ userVoteIndexNow }} 是否继续投票</p>
  48. <div class="modal-buttons">
  49. <button class="modal-button yes" @click="submitVote"></button>
  50. <button class="modal-button no" @click="closeModal"></button>
  51. </div>
  52. </div>
  53. </div>
  54. <!-- 底部组件 -->
  55. <div class="voting-info">
  56. <div style="display: flex; align-items: center;">
  57. <p style="margin-right: 350px;">当前共有{{ totalVotes }}人参与</p>
  58. <a href="#" class="homily-link">{{ ActiveAndVote.username }}</a><a>&nbsp;创建</a>
  59. </div>
  60. <p>此投票于{{ ActiveAndVote.deadlineTime }}结束</p>
  61. </div>
  62. </div>
  63. </template>
  64. <script setup>
  65. import { ref, reactive, computed } from 'vue'
  66. import {apiGetVote,apiAddVoteRecord,apiGetVoteIndex} from '@/apis/vote.js'
  67. import { ElMessage } from 'element-plus';
  68. // 进度条是否显示
  69. const isResultShown = ref(false)
  70. // 添加投票记录
  71. const record = reactive({
  72. userId: 0,
  73. articleId: 0,
  74. voteId: 0,
  75. OptionId: [],
  76. voteIndex: 1
  77. })
  78. // 初始投票数
  79. const totalVotes = ref(0);
  80. const selectNum = ref(0);
  81. // 文章与投票活动信息
  82. const ActiveAndVote = reactive({
  83. voteId: 0,
  84. optionList:[],
  85. participationNumber:0
  86. });
  87. // 投票选项信息
  88. const optionLists = reactive([{
  89. id:0,
  90. optionContent:"",
  91. count:0
  92. }])
  93. // 获取文章及其投票活动
  94. record.articleId = 12 // 模拟数据,此处为文章id
  95. const getVote = async () => {
  96. try {
  97. const res = await apiGetVote(record.articleId)
  98. if (res.data.code === 0) {
  99. Object.assign(ActiveAndVote, res.data.data) // 赋值
  100. optionLists.splice(0) // 清空旧数据
  101. optionLists.push(...ActiveAndVote.optionList) // 批量添加新数据
  102. record.voteId = ActiveAndVote.voteId // 赋值
  103. selectNum.value = ActiveAndVote.optionList.length
  104. totalVotes.value = selectNum.value * 25
  105. totalVotes.value += ActiveAndVote.participationNumber; // 赋值
  106. console.log('投票数据加载成功:', ActiveAndVote)
  107. await GetIndex() // 调用函数
  108. } else {
  109. console.error('请求失败:', res.message)
  110. }
  111. } catch (err) {
  112. console.error('网络错误:', err)
  113. }
  114. }
  115. getVote()
  116. // 获取当前用户今天参与本活动的次数
  117. const userVoteIndexNow = ref(3) // 用户今日本活动剩余投票数
  118. record.userId = 1 // 模拟数据,此处为用户id
  119. const GetIndex = async() => {
  120. const res = await apiGetVoteIndex(record.userId,record.voteId)
  121. userVoteIndexNow.value = res.data.data.voteIndex
  122. console.log('用户今日本活动剩余投票数:', userVoteIndexNow.value)
  123. }
  124. // 选择按钮点击事件,将选项的id存到数组
  125. const selectedOptions = ref([]);// 数组,用于存储多个选中的选项ID
  126. const selectOption = (optionId,index) => {
  127. const idx = selectedOptions.value.indexOf(optionId);
  128. // 单选逻辑:只允许选一个
  129. if (ActiveAndVote.multiOption == 0) {
  130. // 先清除之前所有已选中项的 √
  131. selectedOptions.value.forEach(id => {
  132. const prevIndex = optionLists.findIndex(opt => opt.id === id);
  133. if (prevIndex > -1) {
  134. optionLists[prevIndex].optionContent = optionLists[prevIndex].optionContent.replace(/ √$/, '');
  135. }
  136. });
  137. // 将选项id加到存储数组
  138. selectedOptions.value = [optionId];
  139. console.log(selectedOptions.value)
  140. // 给新选中的项添加 √
  141. optionLists[index].optionContent += ' √';
  142. return;
  143. }
  144. // 多选逻辑:最多选十个
  145. if (idx > -1) {
  146. // 已存在则移除
  147. selectedOptions.value.splice(idx, 1);
  148. console.log(selectedOptions.value)
  149. // 移除 √
  150. optionLists[index].optionContent = optionLists[index].optionContent.replace(/ √$/, '');
  151. } else {
  152. // 不存在则添加
  153. if (selectedOptions.value.length >= 10) {
  154. alert('最多只能选择 10 个选项');
  155. return;
  156. }
  157. selectedOptions.value.push(optionId);
  158. console.log(selectedOptions.value)
  159. // 添加 √
  160. optionLists[index].optionContent += ' √';
  161. }
  162. };
  163. // 提示框
  164. // 控制模态框显示
  165. const showModal = ref(false)
  166. // 显示确认模态框
  167. const showConfirmModal = () => {
  168. showModal.value = true;
  169. };
  170. // 关闭模态框
  171. const closeModal = () => {
  172. showModal.value = false;
  173. isResultShown.value = true; // 进度条显示
  174. };
  175. // 进度条
  176. const totalcounts = computed(() => {
  177. const baseCount = ActiveAndVote.optionList.reduce((sum, option) => sum + option.count, 0); //基础票数
  178. const extra = selectNum.value * 25; // 假数据
  179. return baseCount + extra;
  180. });
  181. const getPercentage = (count) => {
  182. if (totalVotes.value === 0) return 0;
  183. const percentage = Math.min(100, Number(((count / totalcounts.value) * 100).toFixed(2)));
  184. return percentage
  185. };
  186. // 提交投票
  187. const submitVote = async() => {
  188. console.log('剩余投票次数',userVoteIndexNow.value,'选择个数',selectedOptions.value.length)
  189. if (userVoteIndexNow.value > 0 && selectedOptions.value.length > 0) {
  190. record.OptionId = computed(() => selectedOptions.value);
  191. record.voteIndex = 4 - userVoteIndexNow.value
  192. // 这里可以发送请求提交投票
  193. console.log('添加投票信息',record.userId,record.articleId,record.voteId,record.OptionId,record.voteIndex)
  194. await apiAddVoteRecord(record)
  195. ElMessage.success('添加成功')
  196. selectedOptions.value = []; // 清空已选选项
  197. showModal.value = false // 关闭模态框
  198. if(userVoteIndexNow.value == 1){
  199. isResultShown.value = true; // 进度条显示
  200. }
  201. await getVote() // 调用函数
  202. } else {
  203. ElMessage.error('没有剩余投票次数或未选择任何选项')
  204. }
  205. };
  206. </script>
  207. <style scoped>
  208. .articleandvote-section {
  209. width: 40%; /* 占屏幕的 */
  210. max-width: 800px; /* 最大宽度限制 */
  211. margin-left: 18%; /* 距离左边屏幕四分之一 */
  212. background-color: #fff;
  213. border: 1px solid #eee;
  214. border-radius: 8px;
  215. padding: 20px;
  216. box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
  217. }
  218. /* 文章区域样式 */
  219. .article-section {
  220. margin-bottom: 20px;
  221. }
  222. .article-title {
  223. font-size: 20px;
  224. font-weight: bold;
  225. margin-bottom: 10px;
  226. color: #333;
  227. }
  228. .article-content {
  229. font-size: 14px;
  230. color: #555;
  231. line-height: 1.5;
  232. }
  233. /* 投票区域样式 */
  234. .voting-title {
  235. font-size: 30px;
  236. font-weight: bold;
  237. margin-bottom: 15px;
  238. color: #333;
  239. text-align: center;
  240. }
  241. .progressContainer {
  242. width: 400px;
  243. margin: 0 auto 20px; /* 让每个进度条容器居中,并有底部间距 */
  244. }
  245. .centered-progress {
  246. cursor: pointer;
  247. font-size: 18px !important; /* 控制字体大小 */
  248. }
  249. /* 修改 Element Plus 进度条内层样式 */
  250. .centered-progress :deep(.el-progress-bar__inner) {
  251. display: flex !important;
  252. align-items: center !important;
  253. justify-content: center !important;
  254. font-size: 18px !important;
  255. color: #000 !important;
  256. }
  257. .centered-progress :deep(.el-progress-bar__innerText) {
  258. margin: 0 !important;
  259. width: 100% !important;
  260. text-align: center !important;
  261. font-size: 18px !important;
  262. color: #000 !important;
  263. }
  264. /* 点击激活后的颜色 */
  265. .centered-progress.active :deep(.el-progress-bar__inner) {
  266. background-color: #FF9900 !important;
  267. }
  268. /* 包含进度条的相对定位容器 */
  269. .progressWrapper {
  270. width: 400px; /* 同进度条宽度 */
  271. position: relative;
  272. }
  273. /* count 数字,绝对定位到右端,中间垂直对齐 */
  274. .count-right {
  275. position: absolute;
  276. right: 0;
  277. top: 50%;
  278. transform: translateY(-50%);
  279. margin-right: 10px; /* 根据需要微调,让数字在灰条内稍留空隙 */
  280. font-weight: normal;
  281. z-index: 1;
  282. }
  283. /* 左对齐文本状态 */
  284. .left-progress :deep(.el-progress-bar__inner) {
  285. display: flex !important;
  286. align-items: center !important;
  287. justify-content: flex-start !important;
  288. padding-left: 8px !important;
  289. }
  290. .left-progress :deep(.el-progress-bar__innerText) {
  291. font-size: 15px !important;
  292. color: #000 !important;
  293. margin: 0 !important;
  294. }
  295. .confirm-button {
  296. background-color: #FF0000; /* 确认按钮背景为红色 */
  297. color: white;
  298. border: none;
  299. padding: 10px 30px;
  300. border-radius: 25px;
  301. cursor: pointer;
  302. font-size: 15px;
  303. width: 25%; /* 确认按钮宽度为选择按钮的四分之一 */
  304. margin: 0 auto; /* 确认按钮居中 */
  305. display: block;
  306. transition: background-color 0.3s;
  307. text-align: center; /* 文字居中 */
  308. }
  309. .confirm-button:hover {
  310. background-color: #CC0000;
  311. transform: translateY(-2px); /* 微微上移 */
  312. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  313. }
  314. .confirm-button.disabled-style {
  315. background-color: gray; /* 灰色背景 */
  316. color: #ccc; /* 灰色文字 */
  317. cursor: not-allowed; /* 鼠标变成禁止符号 */
  318. }
  319. /* 提示框样式 */
  320. /* 提示框背景遮罩 */
  321. .modal-overlay {
  322. position: fixed;
  323. top: 0;
  324. left: 0;
  325. width: 100%;
  326. height: 100%;
  327. background-color: rgba(0, 0, 0, 0.5);
  328. display: flex;
  329. justify-content: center;
  330. align-items: center;
  331. }
  332. /* 提示框主体 */
  333. .modal-content {
  334. background-color: #87CEEB; /* 更明显的浅蓝色背景 */
  335. padding: 30px 40px; /* 增加内边距 */
  336. border-radius: 12px; /* 边角更圆润一些 */
  337. width: 350px;
  338. min-height: 180px;
  339. text-align: center;
  340. box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2); /* 添加阴影效果 */
  341. }
  342. /* 提示文字 */
  343. .modal-content p {
  344. font-size: 25px; /* 文字更大 */
  345. color: #333;
  346. margin-bottom: 20px;
  347. }
  348. .modal-buttons {
  349. display: flex;
  350. justify-content: space-around;
  351. margin-top: 10px;
  352. }
  353. .modal-button {
  354. padding: 10px 20px;
  355. border: none;
  356. border-radius: 25px;
  357. font-size: 14px;
  358. cursor: pointer;
  359. }
  360. .modal-button.yes {
  361. background-color: orange;
  362. color: white;
  363. }
  364. .modal-button.no {
  365. background-color: lightgray;
  366. color: black;
  367. }
  368. /* 底部样式 */
  369. .voting-info {
  370. font-size: 13px;
  371. color: #888;
  372. line-height: 1.6;
  373. }
  374. .homily-link {
  375. color: #4267B2;
  376. text-decoration: none;
  377. font-size: 13px;
  378. }
  379. </style>