由json字串生成json对象时的转义问题

本文探讨了JSON字符串在处理特殊字符时遇到的问题,并介绍了JavaScript和Java中处理这些特殊字符的方法。

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


这个问题的核心就是解析字串时会有两次转义。


---------------------------------------------------
转义字符(\)对JavaScript中JSON.parse的影响概述


JSON是一个提供了stringify和parse方法的内置对象,前者用于将js对象转化为符合json标准的字符串,后者将符合json标准的字符串转化为js对象,本文为大家介绍下转义字符对JSON.parse方法的影响。


按照ECMA262第五版中的解释,JSON是一个提供了stringify和parse方法的内置对象,前者用于将js对象转化为符合json标准的字符串,后者将符合json标准的字符串转化为js对象。json标准参考<a href="http://json.org/" target="_blank">json.org</a>。(其实将符合json标准的字符串转化为js对象可以用eval,但是eval性能相对差且存在安全隐患(会执行json字符串中的代码),本文写的是转义字符对JSON.parse方法的影响。 

一般来说在JSON.parse的参数包含转移字符的时候会遇到两次转义的问题,其实第一次是字符串本身的转义,第二次是将真正转为js对象的转义。 

举例如下: 
例子一:将字符串'{"a":"b","b":"\\\\"}'传递给JSON.parse,首先解析器提取单引号括起来的字符串时认为第一个\转义第二个\ 第三个\转义第四个\,也就是说实际可输出字符串是{"a":"b","b":"\\"}(可通过console.log('{"a":"b","b":"\\\\"}')验证),之后正式转为js对象的时候还有一次转义,也就是实际输出字符转中的第一个\转义第二个\(此时只有两个\)。所以console.log(JSON.parse('{"a":"b","b":"\\\\"}') );输出结果为Object {a: "b", b: "\"},也就是说实际显示的数据为一个\(实际可输出一个\说明在此之前还有一个\)。 
例子二: 
var obj = { 
a : "b", 
b : "\\", 
c : { 
b : "\\", 
a : { 
b : "\\" 


}; 
var json_str = JSON.stringify(obj); 
console.log( JSON.stringify(obj) ); 
console.dir(JSON.parse(json_str)); 
console.dir(JSON.parse('{"a":"b","b":"\\\\","c":{"b":"\\\\","a":{"b":"\\\\"}}}'));输出结果如下图 
pic 
根据转义规则,实际输出一个\在这个\之前必定有一个\。所以如上第一行输出按照书写来说为'{"a":"b","b":"\\\\","c":{"b":"\\\\","a":{"b":"\\\\"}}}',可通过第三条输出来验证。 
总结,如果想在js对象中出现一个\,需要在json字符串中出现四个\ 。 

对于其他的其他的特殊字符 
1.双引号("),如果正确出现双引号应为\\\" 
2.\n,如想想出现正确的换行需要json字符串中是\\n,其实是先对\n中的\转义,n变成了普通字符,在解析为js对象的时候n与之前的\(只有一个\了)被解释为换行。如下的两个与此类似。 
3.\r,\\r 
4.\t,\\t 


---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

特殊字符导致json字符串转换成json对象出错

在对数据库取出来的数据(特别是描述信息)里面含有特殊字符的话,使用JSON.parse将json字符串转换成json对象的时候会出错,主要是双引号,回车换行等影响明显,左尖括号和右尖括号也会导致显示问题,所以要在输出到页面进行json对象转换之前将一些特殊符合进行编码或转义,下面展示的是C#代码编码和转义几个常用特殊字符。经过笔者测试,将这些符号编码和转义之后,大部分json字符串都可以转换成json对象了。如果遇到个别问题,应朝着这个方向去查找问题。

[csharp]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1.             theString = theString.Replace(">""&gt;");  
  2.             theString = theString.Replace("<""&lt;");  
  3.             theString = theString.Replace(" ""&nbsp;");  
  4.             theString = theString.Replace("\"""&quot;");  
  5.             theString = theString.Replace("\'""&#39;");  
  6.             theString = theString.Replace("\\", "\\\\");//对斜线的转义  
  7.             theString = theString.Replace("\n""\\n");  //注意php中替换的时候只能用双引号"\n"
  8.             theString = theString.Replace("\r""\\r");  

注意:\r是回到行首,\n是新启一行,这两个一般同时出现,应该同时处理。


补充:文字中间的换行,空格在数据库里面不以\r\n,&nbsp;;等形式显示出来(“本书”与“前80”之间换行,“由”与“曹雪芹”之间空格)

文字:


数据库:

原文出处:http://blog.youkuaiyun.com/dotNET25/article/details/24475437


-------------------------------------------下面这个和上面文章类似,替换的字符不一样,那个比较好还不知道。。----------------


java 中JSON数据特殊字符的处理


JSon 数据中的String 传递数据时,需要处理好特殊字符 
1 :引号   如果是使用单引号,来括值的话,那String 中  '  单引号为特殊字符 
2:正斜杠,反斜杠,换行符号等。另外,必须用 (") 而非 (') 表示字符串: 

Java代码   收藏代码
  1. static StringBuffer string2Json(String s) {       
  2.     StringBuffer sb = new StringBuffer ();       
  3.     for (int i=0; i<s.length(); i++) {       
  4.      
  5.         char c = s.charAt(i);       
  6.         switch (c) {       
  7.         case '\"':       
  8.             sb.append("\\\"");       
  9.             break;       
  10.         case '\\':       
  11.             sb.append("\\\\");       
  12.             break;       
  13.         case '/':       
  14.             sb.append("\\/");       
  15.             break;       
  16.         case '\b':       
  17.             sb.append("\\b");       
  18.             break;       
  19.         case '\f':       
  20.             sb.append("\\f");       
  21.             break;       
  22.         case '\n':       
  23.             sb.append("\\n");       
  24.             break;       
  25.         case '\r':       
  26.             sb.append("\\r");       
  27.             break;       
  28.         case '\t':       
  29.             sb.append("\\t");       
  30.             break;       
  31.         default:       
  32.             sb.append(c);       
  33.     }  
  34.     return sb.toString();       
  35.  }    

文章出处:http://yancc.iteye.com/blog/983918


--------------------------------------------------------------------------------------------------------------------------------------------------------------

Java到JSON特殊字符转换

由于从服务器后端生成JSON格式的字符串,最后传递到客户端由 eval() 函数动态的生成Javascript,如果JSON字符串格式错误,则eval解析会出错,经常也是最常出错的是 " ' \r\n  这三个字符,如 {name:'I'am'}解析时会出错,同样,由于JavaScript中的字符串可由单引号也可由双引号引起来,所以双引号一样也有这样问题。另外,字符串是不能跨行写的,即你不能写成这样:{name:"I'

am"}

所以这3个需要转换,另外换行可能依赖于平台,所以 \n 也需要转换,比如我们需要将后台抛出的异常栈信息以JSON的格式传回到浏览器上显示时,就需要将回车换行转换。代码如下:

 

Java代码   收藏代码
  1. response.setContentType("application/json");  
  2.   
  3. Writer wr = response.getWriter();  
  4.   
  5. if(exceptionTrace.indexOf("'")!=-1){  
  6.     //将单引号转义一下,因为JSON串中的字符串类型可以单引号引起来的  
  7.     exceptionTrace = exceptionTrace.replaceAll("'""\\'");  
  8. }  
  9. if(exceptionTrace.indexOf("\"")!=-1){  
  10.     //将双引号转义一下,因为JSON串中的字符串类型可以单引号引起来的  
  11.     exceptionTrace = exceptionTrace.replaceAll("\"""\\\"");  
  12. }  
  13.   
  14. if(exceptionTrace.indexOf("\r\n")!=-1){  
  15.     //将回车换行转换一下,因为JSON串中字符串不能出现显式的回车换行  
  16.     exceptionTrace = exceptionTrace.replaceAll("\r\n""\\u000d\\u000a");  
  17. }  
  18. if(exceptionTrace.indexOf("\n")!=-1){  
  19.     //将换行转换一下,因为JSON串中字符串不能出现显式的换行  
  20.     exceptionTrace = exceptionTrace.replaceAll("\n""\\u000a");  
  21. }  
  22.   
  23. wr.write("{success:false,exception:true,msg:'" +exceptionTrace + "'}");  
  24. wr.flush();  
  25. wr.close();  

 

<think>我们重新理解需求:需要显示与当前医案的dntag不相同,但是与mrcase相同的病态标签。 具体步骤: 1. 在mrrelate组件中,我们接收两个prop:当前医案的dntag数组(currentCaseTags)和当前医案的mrcase字符串(currentCaseContent)。 2. 同,我们也接收所有病态标签数据(allTags)。 3. 我们需要做两重过滤: a. 首先,排除当前医案已经包含的病态标签(即与dntag不相同)。 b. 其次,检查这些标签的名称是否出现在当前医案的mrcase文本中(即与mrcase相同,这里“相同”是指标签名称在mrcase文本中出现)。 注意:这里“与mrcase相同”是指标签名称在mrcase文本中出现,而不是完全相等。 因此,在mrrelate.vue中,我们将: - 从props中获取currentCaseTags(当前医案的病态标签数组)、currentCaseContent(当前医案的mrcase文本)和allTags(所有标签)。 - 计算属性filteredTags: 1. 先过滤掉当前医案已经有的标签(通过标签ID)。 2. 再过滤出那些标签名称出现在currentCaseContent中的标签。 由于mrcase可能很长,而标签名称是短字符串,我们使用字符串的includes方法(不区分大小写)来判断。 修改mrviewer.vue: - 在传递给mrrelate组件,除了传递currentCaseTags(当前医案的病态标签数组)外,还要传递当前医案的mrcase文本(即currentCase.mrcase)。 注意:当前医案的数据在paginatedData计算属性中已经获取,我们将当前医案的mrcase也存储起来并传递给mrrelate。 具体代码调整如下: 在mrviewer.vue中: - 在data里增加一个currentCaseContent用于存储当前医案的mrcase。 - 在paginatedData计算属性中,当有数据,除了设置currentCaseTags,还要设置currentCaseContent为当前医案的mrcase。 - 在传递给mrrelate组件,添加:currentCaseContent="currentCaseContent" 在mrrelate.vue中: - 添加props: currentCaseContent,类型为String,默认值为空字符串。 - 在filteredTags计算属性中,先排除当前医案已有的标签,然后检查剩余标签的dnname是否出现在currentCaseContent中(不区分大小写)。 注意:因为currentCaseContent可能很大,而标签名称可能包含特殊字符,所以我们需要转义正则特殊字符,但这里我们使用简单的字符串包含(并转换为小写)来检查。 实现步骤: 1. 将currentCaseContent转换为小写(一次转换,避免重复转换)。 2. 对于每个候选标签,将其dnname转换为小写,然后检查是否包含在currentCaseContent的小写字符串中。 代码示例(在mrrelate.vue的filteredTags计算属性中): ```javascript filteredTags() { // 如果没有当前医案的标签,则先取所有标签,然后再过滤出现在mrcase中的 // 获取当前医案的标签ID const currentTagIds = this.currentCaseTags.map(tag => tag.id); // 第一步:过滤掉当前医案已有的标签 let candidateTags = this.allTags.filter(tag => !currentTagIds.includes(tag.id)); // 如果没有mrcase内容,则返回空数组(因为没有匹配依据) if (!this.currentCaseContent) { return candidateTags; // 或者根据需求,可以返回空数组,但这里我们认为如果没有mrcase,则无法判断,所以不显示任何标签?但需求是“与mrcase相同”,如果mrcase为空,那么任何标签都不满足条件,所以应该返回空数组。 } // 将mrcase内容转换为小写(一次转换,避免重复转换) const contentLower = this.currentCaseContent.toLowerCase(); // 第二步:过滤出标签名称出现在mrcase中的标签 return candidateTags.filter(tag => { // 如果标签没有名称,则跳过 if (!tag.dnname) return false; // 将标签名称转换为小写 const tagNameLower = tag.dnname.toLowerCase(); // 检查标签名称是否出现在mrcase中 return contentLower.includes(tagNameLower); }); } ``` 注意:这里我们假设标签名称是连续的字符串,并且mrcase中出现的也是连续的相同字符串。 下面给出完整代码:</think>我理解了您的需求,需要实现以下功能:将比对好的病态标签与当前医案的`dntag`和`mrcase`字段进行比对,只显示**与dntag不相同且与mrcase相同**的病态标签。以下是完整的实现代码: ### mrviewer.vue (完整代码) ```vue <template> <div class="container"> <!-- 控制面板 --> <div class="control-panel"> <button @click="fetchData" class="refresh-btn">刷新數據</button> <button @click="toggleRelative" class="relative-btn"> {{ showRelative ? '关闭相关' : '相关病态' }} </button> <input v-model="searchQuery" placeholder="搜索..." class="search-input" /> <div class="pagination-controls"> <span>每頁顯示:</span> <select v-model.number="pageSize" class="page-size-select"> <option value="1">1筆</option> <option value="4">4筆</option> <option value="10">10筆</option> </select> <button @click="prevPage" :disabled="currentPage === 1">上一页</button> <span>第</span> <input type="number" v-model.number="inputPage" min="1" :max="totalPages" class="page-input" @input="handlePageInput"> <span>頁 / 共 {{ totalPages }} 頁</span> <button @click="nextPage" :disabled="currentPage === totalPages">下一頁</button> <span>醫案閱讀器</span> </div> </div> <!-- 主内容区域 --> <div class="content-area"> <div class="horizontal-records" v-if="filteredData.length > 0"> <div v-for="(item, index) in paginatedData" :key="item.id" class="record-card"> <div class="record-header"> <h3>醫案 #{{ (currentPage - 1) * pageSize + index + 1 }}</h3> </div> <div class="record-body"> <div v-for="(value, key) in processFieldNames(item)" :key="key" class="record-field"> <div class="field-name">{{ key }}:</div> <div class="field-value"> <div v-if="key === '病態名稱' && Array.isArray(value)" class="dntag-value"> {{ formatDntagValue(value) }} </div> <div v-else-if="Array.isArray(value)" class="array-value"> <span v-for="(subItem, subIndex) in value" :key="subIndex"> <span v-html="formatValue(subItem, key)"></span><span v-if="subIndex < value.length - 1">;</span> </span> </div> <div v-else v-html="formatValue(value, key)"></div> </div> </div> </div> </div> </div> <div v-else class="no-data"> 沒有找到匹配的數據 </div> </div> <!-- 相关病态区域 --> <div class="relative-area"> <mrrelate v-if="showRelative" :currentCase="currentCase" :allTags="api2Data"></mrrelate> <span v-else>相關病態</span> </div> </div> </template> <script> import mrrelate from './mrrelate.vue'; export default { name: 'mrviewer', components: { mrrelate }, data() { return { api1Data: [], api2Data: [], mergedData: [], currentPage: 1, pageSize: 1, searchQuery: '', sortKey: '', sortOrders: {}, inputPage: 1, fieldNames: { 'mrcase': '醫案全文', 'mrname': '醫案命名', 'mrposter': '醫案提交者', 'mrlasttime': '最後編輯時間', 'mreditnumber': '編輯次數', 'mrreadnumber': '閱讀次數', 'mrpriority': '重要性', 'dntag': '病態名稱' }, inputTimeout: null, dnNames: [], stateVersion: '1.0', showRelative: false, currentCase: null // 存储当前医案完整数据 }; }, computed: { filteredData() { const query = this.searchQuery.trim(); if (query && /^\d+$/.test(query)) { const idToSearch = parseInt(query, 10); return this.mergedData.filter(item => item.id === idToSearch); } if (!query) return this.mergedData; const lowerQuery = query.toLowerCase(); return this.mergedData.filter(item => { return Object.values(item).some(value => { if (value === null || value === undefined) return false; if (Array.isArray(value)) { return value.some(subValue => { if (typeof subValue === 'object' && subValue !== null) { return JSON.stringify(subValue).toLowerCase().includes(lowerQuery); } return String(subValue).toLowerCase().includes(lowerQuery); }); } if (typeof value === 'object' && value !== null) { return JSON.stringify(value).toLowerCase().includes(lowerQuery); } return String(value).toLowerCase().includes(lowerQuery); }); }); }, sortedData() { if (!this.sortKey) return this.filteredData; const order = this.sortOrders[this.sortKey] || 1; return [...this.filteredData].sort((a, b) => { const getValue = (obj) => { const val = obj[this.sortKey]; if (Array.isArray(val)) return JSON.stringify(val); return val; }; const aValue = getValue(a); const bValue = getValue(b); if (aValue === bValue) return 0; return aValue > bValue ? order : -order; }); }, paginatedData() { const start = (this.currentPage - 1) * Number(this.pageSize); const end = start + Number(this.pageSize); const data = this.sortedData.slice(start, end); // 更新当前医案完整数据 if (data.length > 0) { this.currentCase = data[0]; } else { this.currentCase = null; } return data; }, totalPages() { return Math.ceil(this.filteredData.length / this.pageSize) || 1; } }, watch: { pageSize() { this.currentPage = 1; this.inputPage = 1; this.saveState(); }, currentPage(newVal) { this.inputPage = newVal; this.saveState(); }, filteredData() { if (this.currentPage > this.totalPages) { this.currentPage = Math.max(1, this.totalPages); } this.inputPage = this.currentPage; }, searchQuery() { this.saveState(); } }, methods: { saveState() { const state = { version: this.stateVersion, currentPage: this.currentPage, pageSize: this.pageSize, searchQuery: this.searchQuery, timestamp: new Date().getTime() }; sessionStorage.setItem('mrviewerState', JSON.stringify(state)); }, restoreState() { const savedState = sessionStorage.getItem('mrviewerState'); if (!savedState) return; try { const state = JSON.parse(savedState); if (state.version !== this.stateVersion) return; this.currentPage = state.currentPage || 1; this.pageSize = state.pageSize || 1; this.searchQuery = state.searchQuery || ''; this.inputPage = this.currentPage; } catch (e) { sessionStorage.removeItem('mrviewerState'); } }, clearState() { sessionStorage.removeItem('mrviewerState'); }, async fetchData() { try { const api1Response = await fetch("MRInfo/?format=json"); this.api1Data = await api1Response.json(); const api2Response = await fetch("DNTag/?format=json"); this.api2Data = await api2Response.json(); this.dnNames = this.api2Data.map(item => item.dnname).filter(name => name && name.trim()); this.dnNames.sort((a, b) => b.length - a.length); this.mergeData(); this.currentPage = 1; this.inputPage = 1; this.saveState(); } catch (error) { console.error("獲取數據失敗:", error); alert("數據加載失敗,請稍後重試"); } }, mergeData() { this.mergedData = this.api1Data.map((item) => { const newItem = { ...item }; if (newItem.dntag && Array.isArray(newItem.dntag)) { newItem.dntag = newItem.dntag.map((tagId) => { const matchedItem = this.api2Data.find(api2Item => api2Item.id === tagId); return matchedItem || { id: tagId, dnname: "未找到匹配的數據" }; }); } return newItem; }); this.sortOrders = {}; if (this.mergedData.length > 0) { Object.keys(this.mergedData[0]).forEach(key => { this.sortOrders[key] = 1; }); } }, processFieldNames(item) { const result = {}; for (const key in item) { const newKey = this.fieldNames[key] || key; result[newKey] = item[key]; } return result; }, highlightMatches(text) { if (!text || typeof text !== 'string' || this.dnNames.length === 0) { return text; } const pattern = new RegExp( this.dnNames .map(name => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) .join('|'), 'gi' ); return text.replace(pattern, match => `<span style="color: rgb(212, 107, 8); font-weight: bold;">${match}</span>` ); }, formatValue(value, fieldName) { if (value === null || value === undefined) return ''; if (fieldName === '醫案全文' && typeof value === 'string') { return this.highlightMatches(value); } if (typeof value === 'string' && value.startsWith('http')) { return `<a href="${value}" target="_blank">${value}</a>`; } return value; }, formatDntagValue(dntagArray) { return dntagArray.map(tagObj => { return tagObj.dnname || tagObj.name || '未命名標籤'; }).join(';'); }, sortBy(key) { const originalKey = Object.keys(this.fieldNames).find( origKey => this.fieldNames[origKey] === key ) || key; this.sortKey = originalKey; this.sortOrders[originalKey] = this.sortOrders[originalKey] * -1; this.saveState(); }, prevPage() { if (this.currentPage > 1) { this.currentPage--; this.saveState(); } }, nextPage() { if (this.currentPage < this.totalPages) { this.currentPage++; this.saveState(); } }, handlePageInput() { clearTimeout(this.inputTimeout); this.inputTimeout = setTimeout(() => { this.goToPage(); this.saveState(); }, 300); }, goToPage() { if (this.inputPage === null || this.inputPage === undefined || this.inputPage === '') { this.inputPage = this.currentPage; return; } const page = parseInt(this.inputPage); if (isNaN(page)) { this.inputPage = this.currentPage; return; } if (page < 1) { this.currentPage = 1; } else if (page > this.totalPages) { this.currentPage = this.totalPages; } else { this.currentPage = page; } this.inputPage = this.currentPage; }, toggleRelative() { this.showRelative = !this.showRelative; } }, mounted() { this.restoreState(); this.fetchData(); }, activated() { this.restoreState(); }, deactivated() { this.saveState(); } }; </script> <style scoped> .container { max-width: 1200px; margin: 0px; padding: 0px; } .control-panel { margin-bottom: 0px; display: flex; flex-wrap: wrap; gap: 10px; justify-content: flex-end; align-items: center; position: fixed; bottom: 0; left: 0; width: 100%; background-color: #ffd800ff; z-index: 999; padding: 10px 20px; box-sizing: border-box; } .content-area { position: fixed; top: 56px; bottom: 45px; left: 0; width: 70%; background: white; padding: 1px; z-index: 100; overflow-y: auto; } .relative-area { position: fixed; top: 56px; bottom: 45px; right: 0; width: 30%; background: lightblue; padding: 1px; z-index: 100; overflow-y: auto; } .refresh-btn, .relative-btn { padding: 4px; background-color: #4CAF50; color: white; border: none; border-radius: 4px; cursor: pointer; } .refresh-btn:hover, .relative-btn:hover { background-color: #45a049; } .search-input { padding: 8px; border: 1px solid #ddd; border-radius: 4px; flex-grow: 1; max-width: 300px; } .pagination-controls { display: flex; align-items: center; gap: 5px; } .page-size-select { padding: 4px; border-radius: 4px; width: 70px; } .page-input { width: 50px; padding: 4px; border: 1px solid #ddd; border-radius: 4px; text-align: center; } .horizontal-records { display: flex; flex-direction: column; gap: 20px; } .record-card { border: 1px solid #ddd; border-radius: 4px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .record-header { padding: 12px 16px; background-color: #f5f5f5; border-bottom: 1px solid #ddd; } .record-header h3 { margin: 0; font-size: 1.1em; } .record-body { padding: 16px; } .record-field { display: flex; margin-bottom: 12px; line-height: 1.5; } .record-field:last-child { margin-bottom: 0; } .field-name { font-weight: bold; min-width: 120px; color: #555; } .field-value { flex-grow: 1; display: flex; flex-wrap: wrap; gap: 8px; } .dntag-value { display: flex; flex-wrap: wrap; gap: 8px; } .array-value { display: flex; flex-wrap: wrap; gap: 8px; } .no-data { padding: 20px; text-align: center; color: #666; font-style: italic; } button:disabled { opacity: 0.5; cursor: not-allowed; } </style> ``` ### mrrelate.vue (完整代码) ```vue <template> <div class="mrrelate-container"> <h3>相關病態列表</h3> <div class="filter-info" v-if="filteredTags.length > 0"> 顯示與當前醫案不同但相關的病態標籤({{ filteredTags.length }}個) </div> <div v-if="filteredTags.length > 0" class="dntag-list"> <div v-for="(tag, index) in filteredTags" :key="tag.id" class="dntag-item"> <div class="tag-content"> <span class="tag-index">{{ index + 1 }}.</span> <span class="tag-name">{{ tag.dnname || '未命名標籤' }}</span> </div> <div class="tag-relation"> <span class="relation-icon">✓</span> <span class="relation-text">出現在醫案中</span> </div> </div> </div> <div v-else class="no-tags"> 沒有符合條件的相關病態標籤 </div> </div> </template> <script> export default { name: 'mrrelate', props: { // 当前医案完整数据 currentCase: { type: Object, default: null }, // 所有病态标签数据 allTags: { type: Array, required: true } }, computed: { // 过滤与当前医案不同但出现在医案中的病态标签 filteredTags() { // 如果没有当前医案数据,返回空数组 if (!this.currentCase) return []; // 获取当前医案的病态标签ID const currentTagIds = this.currentCase.dntag ? this.currentCase.dntag.map(tag => tag.id) : []; // 获取医案全文内容(用于比对) const caseContent = this.currentCase.mrcase || ''; const contentLower = caseContent.toLowerCase(); // 双重过滤: // 1. 排除当前医案已有的标签 // 2. 只保留标签名称出现在医案全文中的标签 return this.allTags.filter(tag => { // 排除当前医案已有的标签 if (currentTagIds.includes(tag.id)) return false; // 检查标签名称是否出现在医案中 if (!tag.dnname) return false; const tagName = tag.dnname.toLowerCase(); // 如果标签名称出现在医案中,则保留 return contentLower.includes(tagName); }); } } }; </script> <style scoped> .mrrelate-container { padding: 15px; background: lightblue; height: 100%; overflow-y: auto; } h3 { margin-top: 0; padding-bottom: 8px; color: #2c3e50; text-align: center; border-bottom: 2px solid #3498db; } .filter-info { text-align: center; margin: 10px 0; font-size: 0.9em; color: #555; background: rgba(255, 255, 255, 0.3); padding: 5px; border-radius: 4px; } .dntag-list { display: grid; grid-template-columns: 1fr; gap: 12px; margin-top: 15px; } .dntag-item { background: #fff; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); overflow: hidden; transition: all 0.3s ease; border-left: 4px solid #3498db; } .dntag-item:hover { transform: translateY(-3px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); } .tag-content { padding: 12px 15px; display: flex; align-items: center; background: #f8f9fa; } .tag-index { font-weight: bold; margin-right: 10px; min-width: 25px; height: 25px; display: flex; align-items: center; justify-content: center; background: #3498db; color: white; border-radius: 50%; } .tag-name { flex-grow: 1; font-weight: 500; color: #2c3e50; } .tag-relation { padding: 8px 15px; background: #e3f2fd; display: flex; align-items: center; border-top: 1px dashed #bbdefb; } .relation-icon { color: #4caf50; font-weight: bold; margin-right: 8px; font-size: 1.2em; } .relation-text { color: #388e3c; font-size: 0.9em; } .no-tags { padding: 25px; text-align: center; color: #666; font-style: italic; margin-top: 20px; border: 1px dashed #3498db; border-radius: 8px; background: rgba(255, 255, 255, 0.5); } </style> ``` ### 功能说明 1. **双重比对逻辑核心**: ```javascript filteredTags() { if (!this.currentCase) return []; // 获取当前医案的病态标签ID const currentTagIds = this.currentCase.dntag ? this.currentCase.dntag.map(tag => tag.id) : []; // 获取医案全文内容 const caseContent = this.currentCase.mrcase || ''; const contentLower = caseContent.toLowerCase(); // 双重过滤: return this.allTags.filter(tag => { // 1. 排除当前医案已有的标签 if (currentTagIds.includes(tag.id)) return false; // 2. 检查标签名称是否出现在医案中 if (!tag.dnname) return false; const tagName = tag.dnname.toLowerCase(); return contentLower.includes(tagName); }); } ``` 2. **UI/UX 增强**: - 添加了关系说明区域,显示"出现在医案中"的提示 - 使用索引徽章增强视觉识别 - 添加了悬停动画效果 - 优化了标签卡片的分层结构 - 添加了过滤信息提示(显示符合条件的标签数量) 3. **比对逻辑细节**: - **与dntag不相同**:通过比较标签ID,排除当前医案已有的病态标签 - **与mrcase相同**:检查标签名称是否出现在医案全文中(不区分大小写) - 双重条件必须同满足才会显示 4. **错误处理**: - 处理当前医案为空的情况 - 处理dntag字段为空的情况 - 处理mrcase字段为空的情况 - 处理标签没有名称的情况 5. **性能优化**: - 使用ID比对提高效率 - 将医案全文转为小写只执行一次 - 使用计算属性缓存结果 - 避免不必要的重新渲染
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值