以下mrquery.vue代碼中,mrcase中與查詢字串相同者,以背景淡黃字淡藍的式樣展示:
mrquery.vue:
<!-- mrquery.vue -->
<template>
<div class="container">
<!-- 控制面板 -->
<div class="control-panel">
<button @click="fetchData" class="refresh-btn">刷新數據</button>
<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>
<!-- 主内容区域 (50%) -->
<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" v-html="formatDntagValueHTML(value)"></div>
<!-- 本草名称 -->
<div v-else-if="key === '相關本草' && Array.isArray(value)" class="mntag-value" v-html="formatMntagValueHTML(value)"></div>
<!-- 方剂名称 -->
<div v-else-if="key === '相關方劑' && Array.isArray(value)" class="pntag-value" v-html="formatPntagValueHTML(value)"></div>
<!-- 出处名称 -->
<div v-else-if="key === '相關典籍' && Array.isArray(value)" class="sntag-value" v-html="formatSntagValueHTML(value)"></div>
<!-- 人物名称 -->
<div v-else-if="key === '相關人物' && Array.isArray(value)" class="fntag-value" v-html="formatFntagValueHTML(value)"></div>
<!-- 辨證名称 -->
<div v-else-if="key === '相關辨證' && Array.isArray(value)" class="diantag-value" v-html="formatDiantagValueHTML(value)"></div>
<!-- 论治名称 -->
<div v-else-if="key === '相關論治' && Array.isArray(value)" class="tntag-value" v-html="formatTntagValueHTML(value)"></div>
<!-- 效验名称 -->
<div v-else-if="key === '相關效驗' && Array.isArray(value)" class="entag-value" v-html="formatEntagValueHTML(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>
<!-- 查询区块 (50%) -->
<div class="query-area">
<div class="query-header">
<h3>醫案查詢</h3>
</div>
<div class="query-controls">
<div v-for="(query, index) in queryFields" :key="index" class="query-field">
<input type="text" v-model="queryFields[index]" placeholder="輸入查詢字串..." class="query-input">
<button v-if="index > 0" @click="removeQueryField(index)" class="remove-btn">-</button>
<button v-if="index === queryFields.length - 1" @click="addQueryField" class="add-btn">+</button>
</div>
<div class="query-actions">
<button @click="executeQuery" class="query-btn">查詢</button>
<button @click="resetQuery" class="reset-btn">重新查詢</button>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'mrquery',
data() {
return {
api1Data: [],
api2Data: [],
api3Data: [],
api4Data: [],
api5Data: [],
api6Data: [],
api7Data: [],
api8Data: [],
api9Data: [],
mergedData: [],
currentPage: 1,
pageSize: 1,
sortKey: '',
sortOrders: {},
inputPage: 1,
fieldNames: {
'mrcase': '醫案全文',
'mrorigin': '醫案出處',
'mrdoctor': '醫案醫者',
'mrname': '醫案命名',
'mrposter': '醫案提交者',
'mrlasttime': '最後編輯時間',
'mreditnumber': '編輯次數',
'mrreadnumber': '閱讀次數',
'mrpriority': '重要性',
'dntag': '相關病態',
'mntag': '相關本草',
'pntag': '相關方劑',
'sntag': '相關典籍',
'fntag': '相關人物',
'diantag': '相關辨證',
'tntag': '相關論治',
'entag': '相關效驗'
},
inputTimeout: null,
dnNames: [],
mnNames: [],
pnNames: [],
snNames: [],
fnNames: [],
dianNames: [],
tnNames: [],
enNames: [],
stateVersion: '1.0',
isFirstLoad: true,
// 新增查询字段管理
queryFields: [''],
queryConditions: []
};
},
computed: {
filteredData() {
// 使用查询条件过滤数据
if (this.queryConditions.length === 0) {
return this.mergedData;
}
return this.mergedData.filter(item => {
const mrcase = item.mrcase || '';
// 检查是否包含所有查询条件
return this.queryConditions.every(condition =>
condition && mrcase.includes(condition)
);
});
},
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);
return this.sortedData.slice(start, end);
},
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;
}
},
methods: {
saveState() {
const state = {
version: this.stateVersion,
currentPage: this.currentPage,
pageSize: this.pageSize,
queryFields: this.queryFields,
timestamp: new Date().getTime()
};
sessionStorage.setItem('mrqueryState', JSON.stringify(state));
},
restoreState() {
const savedState = sessionStorage.getItem('mrqueryState');
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.queryFields = state.queryFields || [''];
this.inputPage = this.currentPage;
} catch (e) {
sessionStorage.removeItem('mrqueryState');
}
},
clearState() {
sessionStorage.removeItem('mrqueryState');
},
// 新增查询字段管理方法
addQueryField() {
this.queryFields.push('');
this.saveState();
},
removeQueryField(index) {
if (this.queryFields.length > 1) {
this.queryFields.splice(index, 1);
this.saveState();
}
},
executeQuery() {
// 收集非空查询条件
this.queryConditions = this.queryFields
.map(field => field.trim())
.filter(field => field !== '');
// 重置到第一页
this.currentPage = 1;
this.inputPage = 1;
this.saveState();
},
resetQuery() {
// 重置所有查询字段为空
this.queryFields = [''];
// 清空查询条件
this.queryConditions = [];
// 重置到第一页
this.currentPage = 1;
this.inputPage = 1;
// 可选:重新获取数据
// this.fetchData();
this.saveState();
},
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();
// 获取本草标签
const api3Response = await fetch("MNTag/?format=json");
this.api3Data = await api3Response.json();
// 获取典籍标签
const api4Response = await fetch("SNTag/?format=json");
this.api4Data = await api4Response.json();
// 获取方剂标签
const api5Response = await fetch("PNTag/?format=json");
this.api5Data = await api5Response.json();
// 获取人物标签
const api6Response = await fetch("FNTag/?format=json");
this.api6Data = await api6Response.json();
// 获取辨证标签
const api7Response = await fetch("DiaNTag/?format=json");
this.api7Data = await api7Response.json();
// 获取论治标签
const api8Response = await fetch("TNTag/?format=json");
this.api8Data = await api8Response.json();
// 获取效验标签
const api9Response = await fetch("ENTag/?format=json");
this.api9Data = await api9Response.json();
// 处理标签名称用于高亮
this.mnNames = this.api3Data.map(item => item.mnname).filter(name => name && name.trim());
this.mnNames.sort((a, b) => b.length - a.length);
this.pnNames = this.api5Data.map(item => item.pnname).filter(name => name && name.trim());
this.pnNames.sort((a, b) => b.length - a.length);
this.dnNames = this.api2Data.map(item => item.dnname).filter(name => name && name.trim());
this.dnNames.sort((a, b) => b.length - a.length);
this.snNames = this.api4Data.map(item => item.snname).filter(name => name && name.trim());
this.snNames.sort((a, b) => b.length - a.length);
this.fnNames = this.api6Data.map(item => item.fnname).filter(name => name && name.trim());
this.fnNames.sort((a, b) => b.length - a.length);
this.dianNames = this.api7Data.map(item => item.dianname).filter(name => name && name.trim());
this.dianNames.sort((a, b) => b.length - a.length);
this.tnNames = this.api8Data.map(item => item.tnname).filter(name => name && name.trim());
this.tnNames.sort((a, b) => b.length - a.length);
// 效验名称
this.enNames = this.api9Data.map(item => item.enname).filter(name => name && name.trim());
this.enNames.sort((a, b) => b.length - a.length);
// 合并数据
this.mergeData();
// 重置分页
this.currentPage = 1;
this.inputPage = 1;
this.saveState();
} catch (error) {
console.error("獲取數據失敗:", error);
alert("數據加載失敗,請稍後重試");
} finally {
this.isFirstLoad = false;
}
},
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: "未找到匹配的數據" };
});
}
// 处理本草标签
if (newItem.mntag && Array.isArray(newItem.mntag)) {
newItem.mntag = newItem.mntag.map((tagId) => {
const matchedItem = this.api3Data.find(api3Item => api3Item.id === tagId);
return matchedItem || { id: tagId, mnname: "未找到匹配的數據" };
});
}
// 处理方剂标签
if (newItem.pntag && Array.isArray(newItem.pntag)) {
newItem.pntag = newItem.pntag.map((tagId) => {
const matchedItem = this.api5Data.find(api5Item => api5Item.id === tagId);
return matchedItem || { id: tagId, pnname: "未找到匹配的數據" };
});
}
// 处理典籍标签
if (newItem.sntag && Array.isArray(newItem.sntag)) {
newItem.sntag = newItem.sntag.map((tagId) => {
const matchedItem = this.api4Data.find(api4Item => api4Item.id === tagId);
return matchedItem || { id: tagId, snname: "未找到匹配的數據" };
});
}
// 处理人物标签
if (newItem.fntag && Array.isArray(newItem.fntag)) {
newItem.fntag = newItem.fntag.map((tagId) => {
const matchedItem = this.api6Data.find(api6Item => api6Item.id === tagId);
return matchedItem || { id: tagId, fnname: "未找到匹配的數據" };
});
}
// 处理辨证标签
if (newItem.diantag && Array.isArray(newItem.diantag)) {
newItem.diantag = newItem.diantag.map((tagId) => {
const matchedItem = this.api7Data.find(api7Item => api7Item.id === tagId);
return matchedItem || { id: tagId, dianname: "未找到匹配的數據" };
});
}
// 处理论治标签
if (newItem.tntag && Array.isArray(newItem.tntag)) {
newItem.tntag = newItem.tntag.map((tagId) => {
const matchedItem = this.api8Data.find(api8Item => api8Item.id === tagId);
return matchedItem || { id: tagId, tnname: "未找到匹配的數據" };
});
}
// 处理效验标签
if (newItem.entag && Array.isArray(newItem.entag)) {
newItem.entag = newItem.entag.map((tagId) => {
const matchedItem = this.api9Data.find(api9Item => api9Item.id === tagId);
return matchedItem || { id: tagId, enname: "未找到匹配的數據" };
});
}
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;
},
formatValue(value, fieldName) {
if (value === null || value === undefined) return '';
// 在医案全文中高亮所有相关标签
if (fieldName === '醫案全文' && typeof value === 'string') {
let highlighted = this.highlightMatches(value, this.dnNames, 'rgb(212, 107, 8)');
highlighted = this.highlightMatches(highlighted, this.mnNames, 'rgb(0, 128, 0)');
highlighted = this.highlightMatches(highlighted, this.pnNames, 'rgb(128, 0, 128)');
highlighted = this.highlightMatches(highlighted, this.snNames, 'rgb(51, 102, 255)');
highlighted = this.highlightMatches(highlighted, this.fnNames, '#8B4513');
highlighted = this.highlightMatches(highlighted, this.dianNames, 'rgb(255, 0, 0)');
highlighted = this.highlightMatches(highlighted, this.tnNames, '#133e13');
highlighted = this.highlightMatches(highlighted, this.enNames, '#003366');
return highlighted;
}
// 在医案出处中高亮典籍名称
else if (fieldName === '醫案出處' && typeof value === 'string') {
return this.highlightMatches(value, this.snNames, 'rgb(51, 102, 255)');
}
// 在医案医者中高亮人物名称
else if (fieldName === '醫案醫者' && typeof value === 'string') {
return this.highlightMatches(value, this.fnNames, '#8B4513');
}
// 处理URL链接
if (typeof value === 'string' && value.startsWith('http')) {
return `<a href="${value}" target="_blank">${value}</a>`;
}
return value;
},
highlightMatches(text, words, color) {
if (!text || typeof text !== 'string' || words.length === 0) {
return text;
}
const pattern = new RegExp(
words
.map(name => name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('|'),
'gi'
);
return text.replace(pattern, match =>
`<span style="color: ${color}; font-weight: bold;">${this.escapeHtml(match)}</span>`
);
},
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;
},
// 病态标签格式化
formatDntagValueHTML(dntagArray) {
return dntagArray.map(tagObj => {
const name = tagObj.dnname || tagObj.name || '未命名標籤';
return `<span style="color: rgb(212, 107, 8); font-weight: bold;">${this.escapeHtml(name)}</span>`;
}).join(';');
},
// 本草标签格式化
formatMntagValueHTML(mntagArray) {
return mntagArray.map(tagObj => {
const name = tagObj.mnname || tagObj.name || '未命名標籤';
return `<span style="color: rgb(0, 128, 0); font-weight: bold;">${this.escapeHtml(name)}</span>`;
}).join(';');
},
// 方剂标签格式化
formatPntagValueHTML(pntagArray) {
return pntagArray.map(tagObj => {
const name = tagObj.pnname || tagObj.name || '未命名標籤';
return `<span style="color: rgb(128, 0, 128); font-weight: bold;">${this.escapeHtml(name)}</span>`;
}).join(';');
},
// 典籍标签格式化
formatSntagValueHTML(sntagArray) {
return sntagArray.map(tagObj => {
const name = tagObj.snname || tagObj.name || '未命名標籤';
return `<span style="color: rgb(51, 102, 255); font-weight: bold;">${this.escapeHtml(name)}</span>`;
}).join(';');
},
// 人物标签格式化
formatFntagValueHTML(fntagArray) {
return fntagArray.map(tagObj => {
const name = tagObj.fnname || tagObj.name || '未命名標籤';
return `<span style="color: #8B4513; font-weight: bold;">${this.escapeHtml(name)}</span>`;
}).join(';');
},
// 辨证标签格式化
formatDiantagValueHTML(diantagArray) {
return diantagArray.map(tagObj => {
const name = tagObj.dianname || tagObj.name || '未命名標籤';
return `<span style="color: rgb(255, 0, 0); font-weight: bold;">${this.escapeHtml(name)}</span>`;
}).join(';');
},
// 论治标签格式化
formatTntagValueHTML(tntagArray) {
return tntagArray.map(tagObj => {
const name = tagObj.tnname || tagObj.name || '未命名標籤';
return `<span style="color: #133e13; font-weight: bold;">${this.escapeHtml(name)}</span>`;
}).join(';');
},
// 效验标签格式化
formatEntagValueHTML(entagArray) {
return entagArray.map(tagObj => {
const name = tagObj.enname || tagObj.name || '未命名標籤';
return `<span style="color: #003366; font-weight: bold;">${this.escapeHtml(name)}</span>`;
}).join(';');
},
escapeHtml(text) {
const map = {
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": '''
};
return text.replace(/[&<>"']/g, m => map[m]);
}
},
mounted() {
this.restoreState();
if (this.isFirstLoad) {
this.fetchData();
}
},
activated() {
this.restoreState();
if (!this.isFirstLoad) {
this.fetchData();
}
},
deactivated() {
this.saveState();
}
};
</script>
<style scoped>
.container {
max-width: 100%;
margin: 0px;
padding: 0px;
display: flex;
flex-direction: column;
height: 100vh;
}
.control-panel {
margin-bottom: 0px;
display: flex;
flex-wrap: wrap;
gap: 10px;
justify-content: flex-start;
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: 60%;
background: white;
padding: 10px;
z-index: 100;
overflow-y: auto;
box-sizing: border-box;
}
.query-area {
position: fixed;
top: 56px;
bottom: 45px;
right: 0;
width: 40%;
background: #f5f5f5;
z-index: 100;
overflow-y: auto;
padding: 20px;
box-sizing: border-box;
border-left: 1px solid #ddd;
}
.query-header {
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 2px solid #4CAF50;
}
.query-controls {
display: flex;
flex-direction: column;
gap: 15px;
}
.query-field {
display: flex;
gap: 10px;
align-items: center;
}
.query-input {
flex: 1;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 16px;
}
/* 添加/删除按钮样式统一 */
.add-btn, .remove-btn {
width: 40px;
height: 40px;
border-radius: 50%;
font-size: 20px;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
border: none;
}
.add-btn {
background-color: #4CAF50; /* 绿色 */
color: white;
}
.remove-btn {
background-color: #f44336; /* 红色 */
color: white;
}
/* 统一按钮样式大小 */
.query-btn, .reset-btn {
padding: 12px 25px;
font-size: 18px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.3s;
/* 统一尺寸 */
min-width: 120px;
height: 46px;
}
/* 查询按钮保持原色 */
.query-btn {
background-color: #2196F3; /* 蓝色 */
color: white;
}
.query-btn:hover {
background-color: #0b7dda; /* 深蓝色 */
}
/* 重新查询按钮保持原色 */
.reset-btn {
background-color: #f44336; /* 红色 */
color: white;
}
.reset-btn:hover {
background-color: #d32f2f; /* 深红色 */
}
.refresh-btn {
padding: 8px 12px;
background-color: #4CAF50;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
text-align: center;
}
.refresh-btn:hover {
background-color: #45a049;
}
.pagination-controls {
display: flex;
align-items: center;
gap: 5px;
flex-wrap: wrap;
}
.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;
}
.mntag-value {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: #006400;
font-weight: 500;
}
.pntag-value {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: #800080;
font-weight: 500;
}
.sntag-value {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: #0094ff;
font-weight: 500;
}
.fntag-value {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: #8B4513;
font-weight: 500;
}
.diantag-value {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: rgb(255, 0, 0);
font-weight: 500;
}
.tntag-value {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: #133e13;
font-weight: 500;
}
.entag-value {
display: flex;
flex-wrap: wrap;
gap: 8px;
color: #003366;
font-weight: 500;
}
.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;
}
/* 新增重新查询按钮样式 */
.query-actions {
display: flex;
gap: 15px;
margin-top: 20px;
}
</style>
最新发布