错误:#{...} is not allowed in template text

没有检索到摘要

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天在编写一个structs的小程序时,突然出现了 #{...} is not allowed in template text这个错误,

部分源码如下

<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
<h1>Article add Login user can access</h1>
登录用户为:#{loginUser }
</body>
</html>
错误信息:

org.apache.jasper.JasperException: /WEB-INF/Article/add.jsp (line: 12, column: 8) #{...} is not allowed in template text
	at org.apache.jasper.compiler.DefaultErrorHandler.jspError(DefaultErrorHandler.java:42)

查找信息后,在stackfloverflaw网站上,给出了明确的解答

这是原问题链接点击打开链接

原来${...}是jsp的一种标记语言,而#{...}是JSF的一种标记语言,在jsp页面中打印model attribute 必须用${...}


# ======================== Elasticsearch Configuration ========================= # # NOTE: Elasticsearch comes with reasonable defaults for most settings. # Before you set out to tweak and tune the configuration, make sure you # understand what are you trying to accomplish and the consequences. # # The primary way of configuring a node is via this file. This template lists # the most important settings you may want to configure for a production cluster. # # Please consult the documentation for further information on configuration options: # https://www.elastic.co/guide/en/elasticsearch/reference/index.html # # ---------------------------------- Cluster ----------------------------------- # # Use a descriptive name for your cluster: # #cluster.name: my-application cluster.name: douban-cluster # # ------------------------------------ Node ------------------------------------ # # Use a descriptive name for the node: # #node.name: node-1 node.name: windows-node # # Add custom attributes to the node: # #node.attr.rack: r1 # # ----------------------------------- Paths ------------------------------------ # # Path to directory where to store the data (separate multiple locations by comma): # #path.data: /path/to/data path.data: E:\elasticsearch-9.0.1-windows-x86_64\elasticsearch-9.0.1\data # # Path to log files: # #path.logs: /path/to/logs path.logs: E:\elasticsearch-9.0.1-windows-x86_64\elasticsearch-9.0.1\logs # # ----------------------------------- Memory ----------------------------------- # # Lock the memory on startup: # #bootstrap.memory_lock: true # # Make sure that the heap size is set to about half the memory available # on the system and that the owner of the process is allowed to use this # limit. # # Elasticsearch performs poorly when the system is swapping the memory. # # ---------------------------------- Network ----------------------------------- # # By default Elasticsearch is only accessible on localhost. Set a different # address here to expose this node on the network: # #network.host: 192.168.0.1 network.host: 0.0.0.0 # By default Elasticsearch listens for HTTP traffic on the first free port it # finds starting at 9200. Set a specific HTTP port here: # #http.port: 9200 http.port: 9200 # # For more information, consult the network module documentation. # # --------------------------------- Discovery ---------------------------------- # # Pass an initial list of hosts to perform discovery when this node is started: # The default list of hosts is ["127.0.0.1", "[::1]"] # # discovery.seed_hosts: ["host1", "host2"] # discovery.type: single-node # Bootstrap the cluster using an initial set of master-eligible nodes: # # # For more information, consult the discovery and cluster formation module documentation. # # ---------------------------------- Various ----------------------------------- # # Allow wildcard deletion of indices: # # action.destructive_requires_name: false #----------------------- BEGIN SECURITY AUTO CONFIGURATION ----------------------- # # The following settings, TLS certificates, and keys have been automatically # generated to configure Elasticsearch security features on 26-05-2025 02:15:52 # # -------------------------------------------------------------------------------- # Enable security features xpack.security.enabled: false xpack.security.enrollment.enabled: true # Enable encryption for HTTP API client connections, such as Kibana, Logstash, and Agents xpack.security.http.ssl: enabled: true keystore.path: certs/http.p12 # Enable encryption and mutual authentication between cluster nodes xpack.security.transport.ssl: enabled: true verification_mode: certificate keystore.path: certs/transport.p12 truststore.path: certs/transport.p12 # Create a new cluster with the current node only # Additional nodes can still join the cluster later cluster.initial_master_nodes: ["DESKTOP-QRJI156"] # Allow HTTP API connections from anywhere # Connections are encrypted and require user authentication http.host: 0.0.0.0 # Allow other nodes to join the cluster from anywhere # Connections are encrypted and mutually authenticated #transport.host: 0.0.0.0 #----------------------- END SECURITY AUTO CONFIGURATION ------------------------- 运行出现[2025-05-26T15:23:17,442][ERROR][o.e.b.Elasticsearch ] [windows-node] fatal exception while booting Elasticsearchjava.lang.IllegalArgumentException: setting [cluster.initial_master_nodes] is not allowed when [discovery.type] is set to [single-node] at org.elasticsearch.server@9.0.1/org.elasticsearch.cluster.coordination.ClusterBootstrapService.<init>(ClusterBootstrapService.java:88) at org.elasticsearch.server@9.0.1/org.elasticsearch.cluster.coordination.Coordinator.<init>(Coordinator.java:300) at org.elasticsearch.server@9.0.1/org.elasticsearch.discovery.DiscoveryModule.<init>(DiscoveryModule.java:188) at org.elasticsearch.server@9.0.1/org.elasticsearch.node.NodeConstruction.createDiscoveryModule(NodeConstruction.java:1600) at org.elasticsearch.server@9.0.1/org.elasticsearch.node.NodeConstruction.construct(NodeConstruction.java:1069) See logs for more details. ERROR: Elasticsearch did not exit normally - check the logs at E:\elasticsearch-9.0.1-windows-x86_64\elasticsearch-9.0.1\logs\douban-cluster.log
05-27
<template> <div class="vote-container"> <!-- 消息提示区域 (添加在顶部) --> <div v-if="showAlert" :class="[&#39;alert&#39;, alertType]"> {{ alertMessage }} </div> <!-- 投票人信息 --> <div class="voter-info" v-if="voterName && voterIdCard"> <p>投票人:{{ voterName }}</p> <p>身份证:{{ formattedIdCard }}</p> </div> <!-- 投票统计信息 --> <div class="stats"> <div class="stat"> <h3>经理投票</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (votes.A / 5) * 100 + &#39;%&#39; }"></div> </div> <p>{{ votes.A }} / 5</p> </div> <div class="stat"> <h3>厂长投票</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (votes.B / 5) * 100 + &#39;%&#39; }"></div> </div> <p>{{ votes.B }} / 5</p> </div> <div class="stat"> <h3>副厂长投票</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (votes.C / 15) * 100 + &#39;%&#39; }"></div> </div> <p>{{ votes.C }} / 15</p> </div> <div class="stat"> <h3>总票数</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (totalVotes / 25) * 100 + &#39;%&#39; }"></div> </div> <p>{{ totalVotes }} / 25</p> </div> </div> <!-- 被投票人列表 --> <div class="voters-grid"> <div v-for="voter in voters" :key="voter.id" class="voter-card"> <h4>{{ voter.name }}</h4> <p class="voter-id">ID: {{ voter.id }}</p> <div class="vote-options"> <button @click="castVote(voter, &#39;A&#39;)" :disabled="!canVote(voter, &#39;A&#39;)" :class="{ &#39;selected&#39;: voter.vote === &#39;A&#39;, &#39;disabled&#39;: !canVote(voter, &#39;A&#39;) }" > 经理 </button> <button @click="castVote(voter, &#39;B&#39;)" :disabled="!canVote(voter, &#39;B&#39;)" :class="{ &#39;selected&#39;: voter.vote === &#39;B&#39;, &#39;disabled&#39;: !canVote(voter, &#39;B&#39;) }" > 厂长 </button> <button @click="castVote(voter, &#39;C&#39;)" :disabled="!canVote(voter, &#39;C&#39;)" :class="{ &#39;selected&#39;: voter.vote === &#39;C&#39;, &#39;disabled&#39;: !canVote(voter, &#39;C&#39;) }" > 副厂长 </button> </div> </div> </div> <!-- 操作按钮 --> <div class="action-buttons"> <button @click="submitVotes" :disabled="!hasSelectedCandidates || isSubmitting">提交投票</button> <button @click="resetVotes" :disabled="isSubmitting">重置投票</button> </div> </div> </template> <script setup> import { ref, reactive, computed } from &#39;vue&#39;; import { useRoute } from &#39;vue-router&#39;; import { onMounted } from &#39;vue&#39; const voters = ref([]); //候选人 const activeSurvey = ref({ id: null, //投票ID bt: &#39;&#39;, // 标题 qydcl: &#39;&#39;, dclx: &#39;&#39;, //投票类型 tffs: &#39;&#39; }); console.log() // 添加消息提示状态 const alertMessage = ref(&#39;&#39;); const showAlert = ref(false); const alertType = ref(&#39;&#39;); // &#39;success&#39; 或 &#39;error&#39; // 安全序列化函数 function safeStringify(obj) { const seen = new WeakSet(); return JSON.stringify(obj, (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { // 检测到循环引用,返回占位符或跳过 return "[Circular Reference Removed]"; } seen.add(value); } return value; }); } // onMounted生命周期钩子 onMounted(async () => { // 从sessionStorage获取投票人信息并立即清除 const voterInfo = sessionStorage.getItem(&#39;voterInfo&#39;); if (voterInfo) { const { name, idCard } = JSON.parse(voterInfo); voterName.value = name; voterIdCard.value = idCard; sessionStorage.removeItem(&#39;voterInfo&#39;); } // 加载候选人数据 voters.value = await fetchCandidates(); }); // 获取路由信息 const route = useRoute(); // 添加用于存储投票人信息的变量 const voterName = ref(&#39;&#39;); const voterIdCard = ref(&#39;&#39;); // 格式化身份证显示(安全脱敏) const formattedIdCard = computed(() => { if (!voterIdCard.value) return &#39;&#39;; // 显示前6位和后4位,中间用*代替 return voterIdCard.value.substring(0, 6) + &#39;******&#39; + voterIdCard.value.substring(voterIdCard.value.length - 4); }); onMounted(() => { // 从sessionStorage获取数据并立即清除 const voterInfo = sessionStorage.getItem(&#39;voterInfo&#39;); if (voterInfo) { const { name, idCard } = JSON.parse(voterInfo); voterName.value = name; voterIdCard.value = idCard; // 关键:立即清除存储防止数据残留 sessionStorage.removeItem(&#39;voterInfo&#39;); } }); //获取候选人明细 const fetchCandidates = async () => { try { const response = await fetch(&#39;/api/wechat/getInvestigate&#39;, { method: &#39;POST&#39;, body: JSON.stringify({ id: &#39;9&#39;, dcl: &#39;123&#39; }) }); console.log(&#39;API响应:&#39;, response); const result = await response.json(); if (!result || !result.root) throw new Error(&#39;无效API响应&#39;); // 提取候选人数据 const candidateArray = []; let idCounter = 1; // 自增计数器,名称序号 result.root.forEach(rootItem => { if (!rootItem.childEntList) return; rootItem.childEntList.forEach(candidate => { if (!candidate.dcbt || !candidate.dcxbt) return; candidateArray.push({ originalid: candidate.dcbt, name: candidate.dcxbt, vote: null, id:idCounter++, tmlx: candidate.tmlx, // 投票类型 }); // console.log(&#39;投票类型tmlx&#39;,candidate.tmlx) // console.log(&#39;调查标题dcbt&#39;,candidate.dcbt) }); }); return candidateArray; } catch (error) { console.error(&#39;获取候选人失败:&#39;, error); return []; // 返回空数组保持安全 } }; // 新增计算属性 - 获取已选择的候选人 const selectedCandidates = computed(() => { return voters.value .filter(v => v.vote) // 只过滤已投票的 .map(v => ({ id: v.originalid, // 候选人原始ID voteType: v.vote, // 投票类型(A/B/C) voteValue: v.vote === &#39;A&#39; ? 17 : // 转换类型值 v.vote === &#39;B&#39; ? 18 : 19, name: v.name, // 候选人姓名 tmlx: v.tmlx // 原始类型 })); }); // 检查是否有选择的候选人 const hasSelectedCandidates = computed(() => { return selectedCandidates.value.length > 0; }); // 投票统计 const votes = reactive({ A: 0, B: 0, C: 0 }); // 计算总票数 const totalVotes = computed(() => { return votes.A + votes.B + votes.C; }); // 投票方法 const canVote = (voter, type) => { // 情况1:用户取消当前选择的类型(总是允许) if (voter.vote === type) return true; // 情况2:用户从其他类型转换到当前类型 if (voter.vote && voter.vote !== type) { if (type === &#39;A&#39; && votes.A >= 5) return false; if (type === &#39;B&#39; && votes.B >= 5) return false; if (type === &#39;C&#39; && votes.C >= 15) return false; } // 情况3:用户首次投票 if (!voter.vote) { if (type === &#39;A&#39; && votes.A >= 5) return false; if (type === &#39;B&#39; && votes.B >= 5) return false; if (type === &#39;C&#39; && votes.C >= 15) return false; if (totalVotes.value >= 25) return false; } return true; }; // 投票方法 const castVote = (voter, type) => { // 如果已投票且点击相同类型,取消投票 if (voter.vote === type) { voter.vote = null; votes[type]--; return; } // 如果之前有投票,先取消 if (voter.vote !== null) { votes[voter.vote]--; } // 投新票 voter.vote = type; votes[type]++; }; //投票人信息 // 添加投票人信息数据模型 const voterInfo = reactive({ name: &#39;&#39;, idNumber: &#39;&#39; }); // // 添加基本信息验证 // const isValid = computed(() => { // return voterInfo.name.trim() !== &#39;&#39; && // voterInfo.idNumber.trim() !== &#39;&#39; && // /^\d{17}[\dXx]$/.test(voterInfo.idNumber); // }); // 提交投票 // 防止重复提交 const isSubmitting = ref(false); // 提交投票到API const submitVotes = async () => { // 添加防御性检查 if (!hasSelectedCandidates.value) { showMessage(&#39;请先选择候选人&#39;, &#39;error&#39;); return; } // 防止重复提交 if (isSubmitting.value) return; isSubmitting.value = true; try { // 按接口规范构建JSON数据结构 // const requestData = { // mainData: [ // { fieldName: "bt", fieldValue: activeSurvey.value.bt }, // { fieldName: "tprxm", fieldValue: voterName }, // { fieldName: "tprsfz", fieldValue: voterIdCard }, // { fieldName: "mb", fieldValue: activeSurvey.value.id }, // { fieldName: "dclx", fieldValue: activeSurvey.value.dclx }, // { fieldName: "tffs", fieldValue: activeSurvey.value.tffs }, // ], // workflowRequestTableRecords: selectedCandidates.value.map(candidate => ({ // workflowRequestTableFields: [ // { // fieldName: "dcbt", // fieldValue: candidate.originalid // }, // { // fieldName: "tmlx", // fieldValue: candidate.tmlx // }, // { // fieldName: "dcxx", // fieldValue: candidate.voteValue // 使用转换后的值 // } // ] // })) // }; const mainData = [ { fieldName: "bt", fieldValue: activeSurvey.value.bt }, { fieldName: "tprxm", fieldValue: voterName.value }, { fieldName: "tprsfz", fieldValue: voterIdCard.value }, { fieldName: "mb", fieldValue: activeSurvey.value.id }, { fieldName: "dclx", fieldValue: activeSurvey.value.dclx }, { fieldName: "tffs", fieldValue: activeSurvey.value.tffs } ]; const workflowRequestTableRecords = selectedCandidates.value.map(candidate => ({ workflowRequestTableFields: [ { fieldName: "dcbt", fieldValue: candidate.dcbt }, { fieldName: "tmlx", fieldValue: candidate.tmlx }, { fieldName: "dcxx", fieldValue: candidate.vote === &#39;A&#39; ? 17 : candidate.vote === &#39;B&#39; ? 18 : 19 } ] })); const requestBody = { requestName: activeSurvey.value.bt, // 投票标题 workflowId: 118, // 固定工作流ID mainData, workflowRequestTableRecords }; console.log(&#39;标题bt&#39;, activeSurvey.value.bt ) console.log(&#39;mb&#39;, activeSurvey.value.id ) console.log(&#39;tmlx&#39;, activeSurvey.value.dclx ) // 发送POST请求 const response = await fetch(&#39;/api/wechat/addInvestigateWorkflow1&#39;, { method: &#39;POST&#39;, headers: { &#39;Content-Type&#39;: &#39;application/json&#39; }, body: safeStringify(requestBody)// 使用安全序列化,避免重复引用 }); const result = await response.json(); // ===== 核心逻辑:根据 api_status 判断 ===== if (result.api_status === true) { // 成功处理 const successMsg = result.msg || &#39;投票提交成功!&#39;; showMessage(&#39;投票提交成功!&#39;, &#39;success&#39;); // 存储已投票标识 localStorage.setItem(&#39;voted_&#39; + voterIdCard.value, &#39;true&#39;); } else { // 处理错误情况 if (result.msg === &#39;你已提交或不满足提交条件&#39;) { showMessage(result.msg, &#39;error&#39;); // 特殊处理:用户已投票,存储标识防止重复提交 localStorage.setItem(&#39;voted_&#39; + voterIdCard.value, &#39;true&#39;); } else { // 其他错误情况 const errorMsg = result.msg ? `提交失败: ${result.msg}` : &#39;未知错误,请稍后重试&#39;; showMessage(errorMsg, &#39;error&#39;); } } } catch (error) { console.error(&#39;网络请求失败:&#39;, error); showMessage(&#39;网络错误,请检查连接后重试&#39;, &#39;error&#39;); } finally { isSubmitting.value = false; } }; // 重置投票 const resetVotes = () => { if (confirm(&#39;确定要重置所有投票吗?&#39;)) { voters.value.forEach(voter => { voter.vote = null; }); votes.A = 0; votes.B = 0; votes.C = 0; voterInfo.name = &#39;&#39;; voterInfo.idNumber = &#39;&#39;; } }; //后台响应信息showMessage const showMessage = (message, type = &#39;error&#39;) => { // 更新消息提示状态 alertMessage.value = message; showAlert.value = true; alertType.value = type; // 错误提示停留3秒,成功提示停留2秒 const timeout = type === &#39;error&#39; ? 3000 : 2000; setTimeout(() => { showAlert.value = false; }, timeout); }; </script> <style scoped> /* 移动端垂直布局 */ @media (max-width: 480px) { .input-group { flex-direction: column; } } /* 平板/桌面端水平布局 */ @media (min-width: 768px) { .input-group { flex-direction: row; } } /* 消息提示样式 */ .alert { padding: 15px; margin-bottom: 20px; border-radius: 4px; text-align: center; position: fixed; top: 20px; left: 50%; transform: translateX(-50%); z-index: 1000; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); min-width: 300px; opacity: 0.95; } .alert.error { background-color: #ffebee; color: #b71c1c; border: 1px solid #ffcdd2; } .alert.success { background-color: #e8f5e9; color: #1b5e20; border: 1px solid #c8e6c9; } .vote-container { max-width: 1200px; margin: 0 auto; padding: 20px; } .stats { display: flex; justify-content: space-between; margin-bottom: 30px; background: #f5f7fa; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } .stat { flex: 1; text-align: center; padding: 0 15px; } .progress { height: 20px; background: #e0e0e0; border-radius: 10px; margin: 10px 0; overflow: hidden; } .progress-bar { height: 100%; background: #3498db; transition: width 0.3s; } .voters-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 20px; } .voter-card { background: white; border-radius: 8px; padding: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s; } .voter-card:hover { transform: translateY(-5px); } .voter-id { color: #777; font-size: 0.9rem; margin-bottom: 15px; } .vote-options { display: flex; justify-content: space-between; } .vote-options button { flex: 1; margin: 0 5px; padding: 8px 0; border: none; border-radius: 4px; cursor: pointer; transition: all 0.2s; } .vote-options button:not(.selected):hover { opacity: 0.9; transform: scale(1.05); } .vote-options button:first-child { background: #ff6b6b; color: white; } .vote-options button:nth-child(2) { background: #4ecdc4; color: white; } .vote-options button:last-child { background: #ffd166; color: white; } .selected { border: 2px solid #2c3e50 !important; font-weight: bold; box-shadow: 0 0 2 rgba(61, 60, 60, 0.5); } .disabled { opacity: 0.5 !important; cursor: not-allowed !important; } .action-buttons { margin-top: 30px; display: flex; justify-content: center; gap: 20px; } .action-buttons button { padding: 12px 30px; border: none; border-radius: 6px; cursor: pointer; font-size: 1rem; font-weight: 600; transition: all 0.2s; } .action-buttons button:first-child { background: #3498db; color: white; } .action-buttons button:first-child:disabled { background: #bdc3c7; cursor: not-allowed; } .action-buttons button:last-child { background: #e74c3c; color: white; } .action-buttons button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } </style>代码中 const mainData = [ { fieldName: "bt", fieldValue: activeSurvey.value.bt }, { fieldName: "tprxm", fieldValue: voterName.value }, { fieldName: "tprsfz", fieldValue: voterIdCard.value }, { fieldName: "mb", fieldValue: activeSurvey.value.id }, { fieldName: "dclx", fieldValue: activeSurvey.value.dclx }, { fieldName: "tffs", fieldValue: activeSurvey.value.tffs } ]; const workflowRequestTableRecords = selectedCandidates.value.map(candidate => ({ workflowRequestTableFields: [ { fieldName: "dcbt", fieldValue: candidate.dcbt }, { fieldName: "tmlx", fieldValue: candidate.tmlx }, { fieldName: "dcxx", fieldValue: candidate.vote === &#39;A&#39; ? 17 : candidate.vote === &#39;B&#39; ? 18 : 19 } ] })); const requestBody = { requestName: activeSurvey.value.bt, // 投票标题 workflowId: 118, // 固定工作流ID mainData, workflowRequestTableRecords }; console.log(&#39;标题bt&#39;, activeSurvey.value.bt ) console.log(&#39;mb&#39;, activeSurvey.value.id ) console.log(&#39;tmlx&#39;, activeSurvey.value.dclx )这段代码点击提交打印出来的 bt,mb,tmlx都是空的
06-11
<template> <div class="vote-container"> <!-- 投票人信息 --> <div class="voter-info" v-if="voterName && voterIdCard"> <p>投票人:{{ voterName }}</p> <p>身份证:{{ formattedIdCard }}</p> </div> <!-- 投票统计信息 --> <div class="stats"> <div class="stat"> <h3>经理投票</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (votes.A / 5) * 100 + &#39;%&#39; }"></div> </div> <p>{{ votes.A }} / 5</p> </div> <div class="stat"> <h3>厂长投票</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (votes.B / 5) * 100 + &#39;%&#39; }"></div> </div> <p>{{ votes.B }} / 5</p> </div> <div class="stat"> <h3>副厂长投票</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (votes.C / 15) * 100 + &#39;%&#39; }"></div> </div> <p>{{ votes.C }} / 15</p> </div> <div class="stat"> <h3>总票数</h3> <div class="progress"> <div class="progress-bar" :style="{ width: (totalVotes / 25) * 100 + &#39;%&#39; }"></div> </div> <p>{{ totalVotes }} / 25</p> </div> </div> <!-- 被投票人列表 --> <div class="voters-grid"> <div v-for="voter in voters" :key="voter.id" class="voter-card"> <h4>{{ voter.name }}</h4> <p class="voter-id">ID: {{ voter.id }}</p> <div class="vote-options"> <button @click="castVote(voter, &#39;A&#39;)" :disabled="!canVote(voter, &#39;A&#39;)" :class="{ &#39;selected&#39;: voter.vote === &#39;A&#39;, &#39;disabled&#39;: !canVote(voter, &#39;A&#39;) }" > 经理 </button> <button @click="castVote(voter, &#39;B&#39;)" :disabled="!canVote(voter, &#39;B&#39;)" :class="{ &#39;selected&#39;: voter.vote === &#39;B&#39;, &#39;disabled&#39;: !canVote(voter, &#39;B&#39;) }" > 厂长 </button> <button @click="castVote(voter, &#39;C&#39;)" :disabled="!canVote(voter, &#39;C&#39;)" :class="{ &#39;selected&#39;: voter.vote === &#39;C&#39;, &#39;disabled&#39;: !canVote(voter, &#39;C&#39;) }" > 副厂长 </button> </div> </div> </div> <!-- 操作按钮 --> <div class="action-buttons"> <button @click="submitVotes" :disabled="!hasSelectedCandidates || isSubmitting">提交投票</button> <button @click="resetVotes" :disabled="isSubmitting">重置投票</button> </div> </div> </template> <script setup> import { ref, reactive, computed } from &#39;vue&#39;; import { useRoute } from &#39;vue-router&#39;; import { onMounted } from &#39;vue&#39; const voters = ref([]); //候选人 const activeSurvey = ref({ id: null, //投票ID bt: &#39;&#39;, // 标题 qydcl: &#39;&#39;, dclx: &#39;&#39;, //投票类型 tffs: &#39;&#39; }); // 添加消息提示状态 const alertMessage = ref(&#39;&#39;); const showAlert = ref(false); const alertType = ref(&#39;&#39;); // &#39;success&#39; 或 &#39;error&#39; // 安全序列化函数 function safeStringify(obj) { const seen = new WeakSet(); return JSON.stringify(obj, (key, value) => { if (typeof value === "object" && value !== null) { if (seen.has(value)) { // 检测到循环引用,返回占位符或跳过 return "[Circular Reference Removed]"; } seen.add(value); } return value; }); } // onMounted生命周期钩子 onMounted(async () => { // 从sessionStorage获取投票人信息并立即清除 const voterInfo = sessionStorage.getItem(&#39;voterInfo&#39;); if (voterInfo) { const { name, idCard } = JSON.parse(voterInfo); voterName.value = name; voterIdCard.value = idCard; sessionStorage.removeItem(&#39;voterInfo&#39;); } // 加载候选人数据 voters.value = await fetchCandidates(); }); // 获取路由信息 const route = useRoute(); // 添加用于存储投票人信息的变量 const voterName = ref(&#39;&#39;); const voterIdCard = ref(&#39;&#39;); // 格式化身份证显示(安全脱敏) const formattedIdCard = computed(() => { if (!voterIdCard.value) return &#39;&#39;; // 显示前6位和后4位,中间用*代替 return voterIdCard.value.substring(0, 6) + &#39;******&#39; + voterIdCard.value.substring(voterIdCard.value.length - 4); }); onMounted(() => { // 从sessionStorage获取数据并立即清除 const voterInfo = sessionStorage.getItem(&#39;voterInfo&#39;); if (voterInfo) { const { name, idCard } = JSON.parse(voterInfo); voterName.value = name; voterIdCard.value = idCard; // 关键:立即清除存储防止数据残留 sessionStorage.removeItem(&#39;voterInfo&#39;); } }); //获取候选人明细 const fetchCandidates = async () => { try { const response = await fetch(&#39;/api/wechat/getInvestigate&#39;, { method: &#39;POST&#39;, body: JSON.stringify({ id: &#39;9&#39;, dcl: &#39;123&#39; }) }); console.log(&#39;API响应:&#39;, response); const result = await response.json(); if (!result || !result.root) throw new Error(&#39;无效API响应&#39;); // 提取候选人数据 const candidateArray = []; let idCounter = 1; // 自增计数器,名称序号 result.root.forEach(rootItem => { if (!rootItem.childEntList) return; rootItem.childEntList.forEach(candidate => { if (!candidate.dcbt || !candidate.dcxbt) return; candidateArray.push({ originalid: candidate.dcbt, name: candidate.dcxbt, vote: null, id:idCounter++, tmlx: candidate.tmlx, // 投票类型 }); }); }); return candidateArray; } catch (error) { console.error(&#39;获取候选人失败:&#39;, error); return []; // 返回空数组保持安全 } }; // 新增计算属性 - 获取已选择的候选人 const selectedCandidates = computed(() => { return voters.value .filter(v => v.vote) // 只过滤已投票的 .map(v => ({ id: v.originalid, // 候选人原始ID voteType: v.vote, // 投票类型(A/B/C) voteValue: v.vote === &#39;A&#39; ? 17 : // 转换类型值 v.vote === &#39;B&#39; ? 18 : 19, name: v.name, // 候选人姓名 tmlx: v.tmlx // 原始类型 })); }); // 检查是否有选择的候选人 const hasSelectedCandidates = computed(() => { return selectedCandidates.value.length > 0; }); // 投票统计 const votes = reactive({ A: 0, B: 0, C: 0 }); // 计算总票数 const totalVotes = computed(() => { return votes.A + votes.B + votes.C; }); // 投票方法 const canVote = (voter, type) => { // 情况1:用户取消当前选择的类型(总是允许) if (voter.vote === type) return true; // 情况2:用户从其他类型转换到当前类型 if (voter.vote && voter.vote !== type) { if (type === &#39;A&#39; && votes.A >= 5) return false; if (type === &#39;B&#39; && votes.B >= 5) return false; if (type === &#39;C&#39; && votes.C >= 15) return false; } // 情况3:用户首次投票 if (!voter.vote) { if (type === &#39;A&#39; && votes.A >= 5) return false; if (type === &#39;B&#39; && votes.B >= 5) return false; if (type === &#39;C&#39; && votes.C >= 15) return false; if (totalVotes.value >= 25) return false; } return true; }; // 投票方法 const castVote = (voter, type) => { // 如果已投票且点击相同类型,取消投票 if (voter.vote === type) { voter.vote = null; votes[type]--; return; } // 如果之前有投票,先取消 if (voter.vote !== null) { votes[voter.vote]--; } // 投新票 voter.vote = type; votes[type]++; }; //投票人信息 // 添加投票人信息数据模型 const voterInfo = reactive({ name: &#39;&#39;, idNumber: &#39;&#39; }); // // 添加基本信息验证 // const isValid = computed(() => { // return voterInfo.name.trim() !== &#39;&#39; && // voterInfo.idNumber.trim() !== &#39;&#39; && // /^\d{17}[\dXx]$/.test(voterInfo.idNumber); // }); // 提交投票 // 防止重复提交 const isSubmitting = ref(false); // 提交投票到API const submitVotes = async () => { // 添加防御性检查 if (!hasSelectedCandidates.value) { alert(&#39;请先选择候选人&#39;, &#39;error&#39;); return; } // 防止重复提交 if (isSubmitting.value) return; isSubmitting.value = true; try { // 按接口规范构建JSON数据结构 // const requestData = { // mainData: [ // { fieldName: "bt", fieldValue: activeSurvey.value.bt }, // { fieldName: "tprxm", fieldValue: voterName }, // { fieldName: "tprsfz", fieldValue: voterIdCard }, // { fieldName: "mb", fieldValue: activeSurvey.value.id }, // { fieldName: "dclx", fieldValue: activeSurvey.value.dclx }, // { fieldName: "tffs", fieldValue: activeSurvey.value.tffs }, // ], // workflowRequestTableRecords: selectedCandidates.value.map(candidate => ({ // workflowRequestTableFields: [ // { // fieldName: "dcbt", // fieldValue: candidate.originalid // }, // { // fieldName: "tmlx", // fieldValue: candidate.tmlx // }, // { // fieldName: "dcxx", // fieldValue: candidate.voteValue // 使用转换后的值 // } // ] // })) // }; const mainData = [ { fieldName: "bt", fieldValue: activeSurvey.value.bt }, { fieldName: "tprxm", fieldValue: voterName.value }, { fieldName: "tprsfz", fieldValue: voterIdCard.value }, { fieldName: "mb", fieldValue: activeSurvey.value.id }, { fieldName: "dclx", fieldValue: activeSurvey.value.dclx }, { fieldName: "tffs", fieldValue: activeSurvey.value.tffs } ]; const workflowRequestTableRecords = selectedCandidates.value.map(candidate => ({ workflowRequestTableFields: [ { fieldName: "dcbt", fieldValue: candidate.originalid }, { fieldName: "tmlx", fieldValue: candidate.tmlx }, { fieldName: "dcxx", fieldValue: candidate.vote === &#39;A&#39; ? 17 : candidate.vote === &#39;B&#39; ? 18 : 19 } ] })); const requestBody = { requestName: activeSurvey.value.bt, // 投票标题 workflowId: 118, // 固定工作流ID mainData, workflowRequestTableRecords }; // 发送POST请求 const response = await fetch(&#39;/api/wechat/addInvestigateWorkflow1&#39;, { method: &#39;POST&#39;, headers: { &#39;Content-Type&#39;: &#39;application/json&#39; }, body: safeStringify(requestBody)// 使用安全序列化,避免重复引用 }); const result = await response.json(); // 根据API响应显示相应提示 if (result.code === 200) { // 成功处理 if (result.msg) { showMessage(result.msg, &#39;success&#39;); }else { showMessage(&#39;投票提交成功!&#39;, &#39;success&#39;); } // 存储已投票标识 localStorage.setItem(&#39;voted_&#39; + voterIdCard.value, &#39;true&#39;); } else { // 特殊处理"已提交"消息 if (result.msg === &#39;你已提交或不满足提交条件&#39;) { showMessage(result.msg, &#39;error&#39;); } else { // 其他错误处理 showMessage(`提交失败: ${result.msg || &#39;未知错误&#39;}`, &#39;error&#39;); } } } catch (error) { console.error(&#39;提交失败:&#39;, error); // 检查是否已投票错误(假设后端返回409状态码) if (error.response?.status === 409) { alert(&#39;您已投过票,无法重复提交&#39;); } else { alert(&#39;投票提交失败,请重试&#39;); } } finally { isSubmitting.value = false; } }; // 重置投票 const resetVotes = () => { if (confirm(&#39;确定要重置所有投票吗?&#39;)) { voters.value.forEach(voter => { voter.vote = null; }); votes.A = 0; votes.B = 0; votes.C = 0; voterInfo.name = &#39;&#39;; voterInfo.idNumber = &#39;&#39;; } }; </script> <style scoped> /* 移动端垂直布局 */ @media (max-width: 480px) { .input-group { flex-direction: column; } } /* 平板/桌面端水平布局 */ @media (min-width: 768px) { .input-group { flex-direction: row; } } .vote-container { max-width: 1200px; margin: 0 auto; padding: 20px; } .stats { display: flex; justify-content: space-between; margin-bottom: 30px; background: #f5f7fa; padding: 20px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); } .stat { flex: 1; text-align: center; padding: 0 15px; } .progress { height: 20px; background: #e0e0e0; border-radius: 10px; margin: 10px 0; overflow: hidden; } .progress-bar { height: 100%; background: #3498db; transition: width 0.3s; } .voters-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 20px; } .voter-card { background: white; border-radius: 8px; padding: 15px; box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); transition: transform 0.2s; } .voter-card:hover { transform: translateY(-5px); } .voter-id { color: #777; font-size: 0.9rem; margin-bottom: 15px; } .vote-options { display: flex; justify-content: space-between; } .vote-options button { flex: 1; margin: 0 5px; padding: 8px 0; border: none; border-radius: 4px; cursor: pointer; transition: all 0.2s; } .vote-options button:not(.selected):hover { opacity: 0.9; transform: scale(1.05); } .vote-options button:first-child { background: #ff6b6b; color: white; } .vote-options button:nth-child(2) { background: #4ecdc4; color: white; } .vote-options button:last-child { background: #ffd166; color: white; } .selected { border: 2px solid #2c3e50 !important; font-weight: bold; box-shadow: 0 0 2 rgba(61, 60, 60, 0.5); } .disabled { opacity: 0.5 !important; cursor: not-allowed !important; } .action-buttons { margin-top: 30px; display: flex; justify-content: center; gap: 20px; } .action-buttons button { padding: 12px 30px; border: none; border-radius: 6px; cursor: pointer; font-size: 1rem; font-weight: 600; transition: all 0.2s; } .action-buttons button:first-child { background: #3498db; color: white; } .action-buttons button:first-child:disabled { background: #bdc3c7; cursor: not-allowed; } .action-buttons button:last-child { background: #e74c3c; color: white; } .action-buttons button:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); } </style>帮我修改报错:提交失败: ReferenceError: showMessage is not defined at submitVotes (Vote.vue:386:1) overrideMethod @ hook.js:608 submitVotes @ Vote.vue:390 await in submitVotes callWithErrorHandling @ runtime-core.esm-bundler.js:199 callWithAsyncErrorHandling @ runtime-core.esm-bundler.js:206 invoker @ runtime-dom.esm-bundler.js:729
06-11
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值